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

asset hooks #12077

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

asset hooks #12077

wants to merge 7 commits into from

Conversation

MalekiRe
Copy link
Contributor

@MalekiRe MalekiRe commented Feb 24, 2024

Objective

Need to perform transformations on gLTFs and other assets in the future, in order to parse it properly and spawn entities, components based on the data before it's loaded.

Solution

This pr allows any third party crate to register multiple transformations to apply to assets before they actually get loaded.


Changelog

This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section.

Added

fn register_asset_hook<A: Asset, Out, Marker>(
&mut self,
hook: impl IntoSystem<AssetWrapper, Out, Marker> + Sync + Send + 'static + Clone,
) ;
to register asset hooks.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-Assets Load files from disk to use for things like images, models, and sounds labels Feb 24, 2024
@alice-i-cecile
Copy link
Member

Generally a cool idea! I think this is a nice architecture. Needs more docs, tests and an example however: Bevy's asset code is already really tricky to learn and change. We should steadily make that better.

@alice-i-cecile alice-i-cecile added the M-Needs-Release-Note Work that should be called out in the blog due to impact label Feb 24, 2024
@CooCooCaCha
Copy link

CooCooCaCha commented Feb 24, 2024

Would it align more with overall design of the asset system if sub-asset processing was specified in meta files?

My understanding is each piece of data in a gltf file has a path, so you should be able to specify processing steps and settings per-mesh, per-texture, etc.

Thats how I imagined the asset system would work. A loader loads a file, which spawns other loaders/processors.

Copy link
Contributor

@UkoeHB UkoeHB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This poor bevy_asset/server/mod.rs file is getting kinda big...

Comment on lines +289 to +291
/// Registers a hook that gives a &mut Asset to the user, along with any arbitrary system
/// parameters. This runs before the asset is loaded into the world. You can register multiple
/// hooks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Registers a hook that gives a &mut Asset to the user, along with any arbitrary system
/// parameters. This runs before the asset is loaded into the world. You can register multiple
/// hooks.
/// Registers a system hook that runs after any asset of type `A` is loaded, but before it is added to the world.
///
/// The system hook's `In` parameter contains an [`AssetWrapper`], which holds a reference
/// to the asset. A panic will occur if the asset wrapper is saved anywhere (the reference is only valid
/// in the system invocation).
///
/// You can register multiple hooks.

Comment on lines +238 to +239
/// Exists such that we can determine the type statically for the loading hook from an asset that has an erased type.
fn load_hook(&mut self, asset_hooks: &mut AssetHooks, world: &mut World);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Exists such that we can determine the type statically for the loading hook from an asset that has an erased type.
fn load_hook(&mut self, asset_hooks: &mut AssetHooks, world: &mut World);
/// Runs [`AssetHooks`](AssetHook) for the asset in this container, if any exist.
fn run_hooks(&mut self, asset_hooks: &mut AssetHooks, world: &mut World);

Comment on lines +253 to +255
fn load_hook(&mut self, asset_hooks: &mut AssetHooks, world: &mut World) {
asset_hooks.trigger::<A>(self, world);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn load_hook(&mut self, asset_hooks: &mut AssetHooks, world: &mut World) {
asset_hooks.trigger::<A>(self, world);
}
fn run_hooks(&mut self, asset_hooks: &mut AssetHooks, world: &mut World) {
asset_hooks.run::<A>(self, world);
}

Comment on lines +81 to +82
}
#[allow(clippy::unused_unit)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
#[allow(clippy::unused_unit)]
}
#[allow(clippy::unused_unit)]

Comment on lines +101 to +102
}
/// Safe wrapper around asset pointers to allow for passing into systems.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
/// Safe wrapper around asset pointers to allow for passing into systems.
}
/// Safe wrapper around asset pointers to allow for passing into systems.

Comment on lines +132 to +135
fn add_asset_hook<A, Out, Marker, F>(&mut self, f: F)
where
A: Asset,
F: IntoSystem<AssetWrapper<A>, Out, Marker> + Sync + Send + 'static + Clone,
Copy link
Contributor

@UkoeHB UkoeHB Feb 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn add_asset_hook<A, Out, Marker, F>(&mut self, f: F)
where
A: Asset,
F: IntoSystem<AssetWrapper<A>, Out, Marker> + Sync + Send + 'static + Clone,
fn add_asset_hook<A, (), Marker, F>(&mut self, f: F)
where
A: Asset,
F: IntoSystem<AssetWrapper<A>, (), Marker> + Sync + Send + 'static,

There should not be an output. Also Clone shouldn't be necessary (see fix below).

Comment on lines +248 to +250
pub fn register_asset_hook<A: Asset, Out, Marker>(
&self,
hook: impl IntoSystem<AssetWrapper<A>, Out, Marker> + Sync + Send + 'static + Clone,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn register_asset_hook<A: Asset, Out, Marker>(
&self,
hook: impl IntoSystem<AssetWrapper<A>, Out, Marker> + Sync + Send + 'static + Clone,
pub fn register_asset_hook<A: Asset, (), Marker>(
&self,
hook: impl IntoSystem<AssetWrapper<A>, (), Marker> + Sync + Send + 'static,

Comment on lines +141 to +145
let hooks = self.0.get_mut::<AssetHookVec<A>>().unwrap();
hooks.push(Box::new(move |asset, world| {
let mut system: F::System = IntoSystem::into_system(f.clone());
system.initialize(world);
let asset_wrapper = AssetWrapper::new(asset);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let hooks = self.0.get_mut::<AssetHookVec<A>>().unwrap();
hooks.push(Box::new(move |asset, world| {
let mut system: F::System = IntoSystem::into_system(f.clone());
system.initialize(world);
let asset_wrapper = AssetWrapper::new(asset);
let hooks = self.0.get_mut::<AssetHookVec<A>>().unwrap();
let mut system: F::System = IntoSystem::into_system(f);
let mut initialized = false;
hooks.push(Box::new(move |asset, world| {
if !initialized {
system.initialize(world);
initialized = true;
}
let asset_wrapper = AssetWrapper::new(asset);

Avoid cloning.

Comment on lines 704 to 706
let loaded_asset: LoadedAsset<A> = asset.into();

let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let loaded_asset: LoadedAsset<A> = asset.into();
let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
let loaded_asset: LoadedAsset<A> = asset.into();
let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();

Comment on lines +1160 to +1162
loaded_asset
.value
.load_hook(&mut server.data.hooks.write(), world);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
loaded_asset
.value
.load_hook(&mut server.data.hooks.write(), world);
loaded_asset
.value
.run_hooks(&mut server.data.hooks.write(), world);

@mockersf
Copy link
Member

This is a new way to do things that are already possible, without a clear advantage and adding complexity.

And as it modify the gltf assets, this would mean a plugin registering hooks for an asset type could break all other use of that asset type

@mockersf mockersf added the X-Controversial There is active debate or serious implications around merging this PR label Feb 24, 2024
@nicopap
Copy link
Contributor

nicopap commented Feb 24, 2024

Copy link
Contributor

@nicopap nicopap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right way of implementing it. We should first look into improving composability of AssetTransformer before adding this.

In any case, I gave some feedback to help improve this code if it is to be merged.

pub(crate) hooks: RwLock<AssetHooks>,
}

struct TypeMap(hashbrown::HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, the additions to this file should really be moved in a separate module.

I notice that the key type for TypeMap here is exclusively AssetHookVec<A>. I would suggest replacing the Box<dyn Any> by some erased version of AssetHookVec<A>. Maybe by replacing AssetHookVec<A> with a Vec<SystemId>.

Comment on lines +137 to +139
if !self.0.has::<AssetHookVec<A>>() {
self.0.set::<AssetHookVec<A>>(Vec::new());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, followed by the get_mut().unwrap() should be replaced by .entry(value).get_or_default().

Comment on lines +143 to +145
let mut system: F::System = IntoSystem::into_system(f.clone());
system.initialize(world);
let asset_wrapper = AssetWrapper::new(asset);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the initialization logic shouldn't be ran in the closure, but rather in the trigger method. This would reduce code bloat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Assets Load files from disk to use for things like images, models, and sounds C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants