Replies: 5 comments 23 replies
-
I haven't looked super in-depth to this proposal and I don't have time to weigh in on this properly, but I definitely 👍 the general idea. I have run into a similar issue: I have a custom Bevy map loader that loads a texture for the tilemap and it needs to set the filtering mode for the texture, but as it stands, it's quite awkward to set that filter because I have to wait for the asset to load in some system before modifying the filtering mode. Like, maybe we could pass in a closure that gets run immediately on the asset after the asset is loaded by the corresponding AssetLoader? let texture_handle = asset_server.load("face.png").map(|x| x.sampler = SamplerDescriptor {
min_filter: FilterMode::Nearest,
mag_filter: FilterMode::Nearest,
..Default::default()
}); This way there's no scheduler interaction or anything and it is really obvious what is happening. Again, didn't spend much time thinking about this, but there's some food for thought. 🙂 |
Beta Was this translation helpful? Give feedback.
-
Yeah handling this nicely seems valuable, but I want to consider "generic" solutions to the problem before trying to build something custom for assets. Every new concept we add incurs cognitive load. Ex: rather than adding "asset stages" or AssetServer-owned systems, we could instead add "event systems": app.add_system(handler.event_system())
fn handler(In(event): In<AssetEvent<Mesh>>, mut meshes: ResMut<Assets<Mesh>>) {
if let AssetEvent::Created { handle } = event {
// do_thing
}
} However I also think that the new EventReader param almost removes the need for "event systems": app.add_system(handler.system())
fn handler(mut events: EventReader<AssetEvent<Mesh>>, mut meshes: ResMut<Assets<Mesh>>) {
for event in events.iter() {
if let AssetEvent::Created { handle } = event {
// do_thing
}
}
} |
Beta Was this translation helpful? Give feedback.
-
EDIT: the original tone of this post was somewhat hostile. I have edited it to make it more constructive and on-topic. @cart event systems are merely a slightly-nicer syntax for what we already have and do not provide the desired benefits. Most of the boilerplate is still required, such as handling the event, and looking up the asset using the handle. For example, let's say I want to do some processing for a specific texture. Here is what it looks like with an event system: // need to store the handle somewhere to be able to find my texture
struct MapTexture(Handle<Texture>);
fn process_map_texture(
In(event): In<AssetEvent<Texture>>,
map_handle: Res<MapTexture>,
mut assets: ResMut<Assets<Texture>>,
// ... maybe I need more system params?
) {
if let AssetEvent::Created { handle } = ev {
if *handle == map_handle.0 {
let texture = assets.get_mut(handle).unwrap();
// i can finally do something with `texture`
}
}
}
fn setup_textures(server: Res<AssetServer>, commands: &mut Commands) {
let handle = server.load("map.png").unwrap();
commands.insert_resource(MapTexture(handle));
} I feel it shouldn't be necessary to deal with events or asset storage at all, to do this. Or require a resource (or another mechanism) for tracking the handle. It seems like a very common use case to want to do some processing on specific assets, when they get loaded (the event is overkill, i don't care about modifications or removals, and i don't care about other assets). Now, here is what it could look like with the syntax proposed in the OP: fn process_map_texture(
// get the asset directly, and the handle just in case we need it
In((texture, handle)): In<(Texture, Handle<Texture>)>,
// ... maybe I need more system params?
) {
// i can just do something with `texture`
}
fn setup_textures(server: Res<AssetServer>) {
let handle = server.load("map.png").unwrap();
server.set_processor(handle, process_map_texture.system());
}
But for this asset-processing use case (and this is a common use case), they are just cumbersome to use. I strongly believe we need something specialized, to make it ergonomic. I think it would fit into the spirit of bevy, which is to provide "refreshingly simple" and low-boilerplate APIs. |
Beta Was this translation helpful? Give feedback.
-
It seems to me that there are several categories of things one might want to do with an asset:
(1) is by far the most common use case. Sounds like, in Distill, such parameters are stored in metadata files, which are easy to edit. That sounds very nice and eliminates the need to poke them from code. Seems like an all-round better solution. (2) and (3) are both specialized enough that there wouldn't be a large number of such systems in a project, so they probably wouldn't mind the extra boilerplate. So, seems like an all-powerful code-injection mechanism like I previously proposed might not actually be that useful, to justify the implementation effort and complexity. However, as we aren't (yet?) using Distill with its metadata files, a more ergonomic way to do (1) in existing bevy would be nice. Maybe we should focus the discussion on that specifically? (also, (1) was the use case that originally motivated this discussion, but when writing the OP, i was just thinking "why not make it as powerful and versatile as possible if we can", so I expanded the scope) Are there any alternative ways in Bevy for doing things like setting texture sampler parameters (and other asset metadata? what other asset types have metadata?), without having to write a system to manipulate the asset after it is loaded? |
Beta Was this translation helpful? Give feedback.
-
Has there been any discussion around a jobs/tasks system? I'm imagining an API where you can dynamically submit jobs at runtime that get scheduled along with regular system execution. For this specific use-case you could tell bevy to load an asset and then dispatch a job to process the asset after it has loaded. |
Beta Was this translation helpful? Give feedback.
-
It is common to want to do some processing on an asset as soon as it is loaded, to prepare it for use.
For example, setting texture sampling parameters (relevant discussion).
Currently the way to do that is by having a system that runs every frame (possibly confined in a state), either waiting for an
AssetEvent
, or just polling/checking the handle. This is very unwieldy and a lot of boilerplate. I'd like it to be more ergonomic.It would be nice to be able to simply register a system to be automatically run once, when the asset is done loading. Ideally, it should be able to get the asset/handle directly as an input.
Here is what such a system might look like:
To simplify this further, the asset itself could be provided directly, to avoid the need for the assets resource,
get_mut
, andunwrap()
(as these would be practically necessary boilerplate, probably to be found in every such processing system):If we support this, then, for the sake of ergonomics, all combinations of asset and handle inputs could be supported:
(T, Handle<T>)
(Handle<T>, T)
Handle<T>
T
The above could all be valid input types for systems accepted as asset processors. It would let the user pick what they need.
Registration
Ideally, I'd like it to be possible to register systems to run only on specific assets, not necessarily always on all assets of a given type.
For example, it could be done by registering the processing system with the
AssetLoader
when callingload
:I don't know if registering systems on the
AssetServer
like this (or in general outside of the App builder) can even be feasibly supported; I am just illustrating what I'd ideally like the UX to be.Adding the default processor is something that could be done from the App builder.
However, I don't think registering a system only for specific asset handles is something that can be done from the App builder, but it is a valuable use case to be supported, so we need to figure something out for that.
I have no idea what the implementation of this feature would be.
In
parameter syntax seems like such a natural fit for this...Discussion welcome!
Beta Was this translation helpful? Give feedback.
All reactions