Generalized Assets as Entities #18414
Replies: 1 comment 5 replies
-
Ok I've thought about this for a bit and I've been teetering back and forth. First, I think this idea is interesting. It trades off all the complexities of remote entity allocation with entity mapping. It's neat! Even more, there's no need to depend on bsn here. We could just do this with However there are two problems. One small and one big. The small issue is that it means we can no longer load assets from a thread. Since this solution depends on the fact that we can entity-map later, we can't have adhoc asset loading (e.g., a worker thread that is queuing up entities to be spawned using The second and bigger problem is that we can't entity-map through Arcs. If your asset internally holds Arcs which themselves hold asset handles, those asset handles cannot be entity-mapped later. MeshletMesh is an example of an asset that stores Arcs, and it also seems possible that you might even want your asset to be a wrapper around Some extra thoughts: I have no qualms about
|
Beta Was this translation helpful? Give feedback.
-
For assets as entities (#11266), we need an asset to live in an entity and be identified by that entity. However, for nested assets, this gets complicated. Say an asset type has sub-assets that it references by the asset handle. If that handle/id becomes an entity, how does the loader reserve the
Entity
without&Entities
. This is possible (See #18380), but it's costly.Clarifications
To be perfectly clear, I'm going to define some ideas up front:
health_left
is a normal component,health_stats
is a link to a asset entity with asset components likemax_health
andregeneration
, etc.Maybe you disagreed with how I called
max_health
an asset earlier. That's my point. With assets as entities, there's a very blurry line between serializable components and assets. So lets split them up entirely.Proposal: Loadable Components
Let's generalize asset loading and saving to work with any serializable component.
We can stick this on any entity with
T
, and just like that, we know where to save and load it. To load or save one of these entity's components, we can use bevy's existing saving and loading system almost verbatim.This does two really exciting things:
First, users can make their own save system super easily.
I'm skipping some details, but you get the point. Bevy's existing save/load infrastructure goes into
ResMut<SaveServer>
.Second, assets, including nested assets, are just scenes. Nothing more, nothing less. That means they can soak up all the benefits of bsn. (Though they don't have to. Custom asset loaders are still trivially possible. But if a component is serializable, it can be in a bsn asset loader, so that's a good default. Plus, it means no more having to do the boiler plate of creating another asset loader for your new asset type.)
I'm glossing over some fairly obvious details here, for example, if
T
is removed but not replaced, we should get rid of theSerializingLocation
, etc.If you're wondering how Arc'd assets (#15813) fits in here, I think that's tangential to this. It's very much compatible here, but with this generalization, not all serializable components should be put in an arc.
Proposal: Garbage collected components
One of the big features of bevy's asset system is that when one is no longer needed, it is freed automatically. We can't afford to drop this feature. (Just look at the ecosystem of Unity plugins that try to figure out which assets can be unloaded.)
Instead of a custom
Handle
system for exclusively assets, let's generalize this too.Now, we simply periodically check the garbage collected components for if their base is shared, and if not, we remove the component. If there are no other
GarbageCollected
components, we can despawn the entity.Putting it all together
With all of this implemented, we can do the following:
Entity
of the first load.)GarbageCollected
to the entity for automated release. (If a user wants to do this manually, they don't have to add it, and we can automate adding it otherwise.)GarbageCollected
.GarbageCollected
here too (or anywhere).GarbageCollected
will removeT
, and theSerializingLocation
can be used with an observer to start saving the modified data.SerializingLocation
to the other entity (the one we cloned the component onto earlier).SerializingLocation
and associated observer will save the new component to it's new location.Now, the asset system is more than just images and meshes. We can use it for save data, settings/player preferences, player/item/etc stats, and so much more. Plus we can do things like, when a
Mesh
is added to an entity, we use hooks to calculate its AABB on the fly. Or, for unique, procedurally generated meshes, we don't need an asset entity. Just throw theMesh
,Mesh3d
,MyMaterial
, (and the rest) on the same entity.Extension Assets
One final possibility is extension assets.
Instead of loading as their own scene, these load and prepare a closure that modifies an existing scene. (Or something like that). For example, an
Item
may be an asset, andPrice
may be an extension asset onItem
. We can load aPrice
which triggers loading theItem
associated with it. When they're both done, they would be merged. In this case, the result would be an entity withItem
,Price
,SerializingLocation<Item>
,SerializingLocation<Price>
.This is invaluable for modding, but not much use otherwise. (Why not just include
Price
in the item's bsn file?)Conclusion
This design offers extreme ergonomics that I find really appealing. It also simplifies assets as entities and breaks them down into very approachable chunks for implementation. What makes a traditional asset special is that it can be saved, loaded, and garbage collected. This proposal splits those up into their own features allowing the ecs to benefit from these asset patterns and allowing the asset system to benefit from the ecs.
It also doesn't hurt that this solves a lot of design questions about how to use an Entity has an asset handle without
&Entities
.Beta Was this translation helpful? Give feedback.
All reactions