-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
base: main
Are you sure you want to change the base?
asset hooks #12077
Conversation
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. |
…ergonomics and total functionality.
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. |
There was a problem hiding this 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...
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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. |
/// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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); |
fn load_hook(&mut self, asset_hooks: &mut AssetHooks, world: &mut World) { | ||
asset_hooks.trigger::<A>(self, world); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); | |
} |
} | ||
#[allow(clippy::unused_unit)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
#[allow(clippy::unused_unit)] | |
} | |
#[allow(clippy::unused_unit)] |
} | ||
/// Safe wrapper around asset pointers to allow for passing into systems. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
/// Safe wrapper around asset pointers to allow for passing into systems. | |
} | |
/// Safe wrapper around asset pointers to allow for passing into systems. |
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
pub fn register_asset_hook<A: Asset, Out, Marker>( | ||
&self, | ||
hook: impl IntoSystem<AssetWrapper<A>, Out, Marker> + Sync + Send + 'static + Clone, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
let loaded_asset: LoadedAsset<A> = asset.into(); | ||
|
||
let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(); |
loaded_asset | ||
.value | ||
.load_hook(&mut server.data.hooks.write(), world); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loaded_asset | |
.value | |
.load_hook(&mut server.data.hooks.write(), world); | |
loaded_asset | |
.value | |
.run_hooks(&mut server.data.hooks.write(), world); |
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 |
There was a problem hiding this 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>>); |
There was a problem hiding this comment.
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>
.
if !self.0.has::<AssetHookVec<A>>() { | ||
self.0.set::<AssetHookVec<A>>(Vec::new()); | ||
} |
There was a problem hiding this comment.
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()
.
let mut system: F::System = IntoSystem::into_system(f.clone()); | ||
system.initialize(world); | ||
let asset_wrapper = AssetWrapper::new(asset); |
There was a problem hiding this comment.
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.
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
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.