Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use workflows for model spawning #238

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

luca-della-vedova
Copy link
Member

@luca-della-vedova luca-della-vedova commented Sep 5, 2024

New feature implementation

Implemented feature

This PR brings model loading from a fairly complex state machine involving several marker components and implicit system ordering to workflow. This unlocks the following:

  • Proper dependency tracking: Arbitrary hierarchies of nested models will now be loaded as part of the root model, if any of the dependencies fails the user will know which dependency fails and the whole model spawning will be aborted.
  • Flexibility with callbacks: We can use a callback based system to perform actions on a model as soon as it is spawned, removing the need to use marker components and systems querying them for this purpose.
  • "Quicker" model loading. Since we can do model loading in a series of blocking workflows they all can be executed in a sequence potentially in a single frame without having to wait multiple systems to go through command flushes and change detection (that a marker component based approach would need).
  • Safer component adding / removing to models that are being spawned, which removes the need for the ModelTrashCan that we introduced before to avoid panics, we can now just do a normal despawn_recursive()

Implementation description

High level

A new workflow for model loading with a command implementation has been added, its inputs are the entity that the model should be spawned in, as well as its asset source.

// Before
let model = Model {
    [...]
};
commands.spawn(model);

// After, we still need to add all the required components, the service only reads an `AssetSource` and spawns a model accordingly
let source = AssetSource[...]
let model = Model {
    source: source.clone(),
    [...]
};
let id = commands.spawn(model).id();
commands.spawn_model((id, source).into());

Workflow details

spawn_model takes a ModelLoadingRequest as an input, which can be default initialized from a (Entity, AssetSource), but also uses a builder pattern so users can provide a Callback<Entity, ()> that will be called on the model if it spawned correctly. This is currently used for workcell editor mode, where we want to do a custom action on the model (for example post-process its hierarchy).

let req = ModelLoadingRequest::new(model_id, source).then(flatten_models);
commands.spawn_model(req);

The workflow itself propagates its request throughout and returns a ModelLoadingResult:

pub type ModelLoadingResult = Result<ModelLoadingRequest, Option<ModelLoadingError>>;

The result will contain :

  • Ok(the original request) if successful.
  • Err(None) if the workflow was aborted but without an error. Currently this happens only if the model was not spawned because it already contains a scene with the requested AssetSource, this could happen if a user called spawn_model several times on the same entity with the same source and avoids unnecessary work.
  • Err(Some(err))` if it fails for any other reason, asset not found, failed parsing. When this is triggered we add a marker component for model failure and cleanup the model scene.

Marker components for state

Marker components are not fully gone, when a model spawning is requested a component with its Promise will be added to the entity.

/// Component added to models that are being loaded
#[derive(Component, Deref, DerefMut)]
pub struct ModelLoadingState(Promise<ModelLoadingResult>);

This component can be used in queries to know whether any models are still pending spawning, it is currently used in the SDF exporter to trigger saving when all models either finished or failed loading. When the following query is empty we know that no models are currently being loaded. This could potentially be removed if we made site loading a workflow which itself will only complete when all its models finished loading, however I left this out of this PR to avoid blowing up the size of this refactor.

missing_models: Query<(), With<ModelLoadingState>>,

Furthermore, a marker component could be added (I just removed it, check #238 (comment)) when the workflow fails to load a model:

/// Marker component added to models that failed loading
#[derive(Component)]
pub struct ModelFailedLoading;

This component is currently only used to detect which models failed loading and should be retried when a new Gazebo app API key is set:

pub fn reload_failed_models_with_new_api_key(
mut commands: Commands,
mut api_key_events: EventReader<SetFuelApiKey>,
failed_models: Query<(Entity, &AssetSource), With<ModelFailedLoading>>,
) {

But it could also be used, for example, to generate an export log when a site is exported to SDF (for example, reporting to the user that a certain set of models couldn't be loaded and hence might be missing from the world).

luca-della-vedova and others added 18 commits August 6, 2024 09:12
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadellavr@gmail.com>
Signed-off-by: Luca Della Vedova <lucadellavr@gmail.com>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
@luca-della-vedova
Copy link
Member Author

ad502f0 contains an example of removing the marker component, it will mean that users have to rely on a less obvious component to keep track of which model failed loading but saves some code, I'm happy with both options.

Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Review
Development

Successfully merging this pull request may close these issues.

1 participant