bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.04k stars 3.44k forks source link

Add change ticks to asset references #14444

Open viridia opened 1 month ago

viridia commented 1 month ago

What problem does this solve or what need does it fill?

This is something I've run into in my reactive experiments, but it is also directly relevant to @cart 's discussion on next-gen UI. I want to be able to have a reactive context that depends on an Asset, and be able to treat this the same way that I depend on an ECS component or resource. Specifically, given a (possibly type-erased) asset handle, I want to be able to poll for changes using the change tick by comparing it against the current world tick.

Currently assets have an event-based change detection system that is very different from the change detection on components and resources. This difference makes it a challenge to have a unified approach that integrates changes from all the various kinds of data dependencies.

What solution would you like?

The solution that I am considering seems fairly straightforward: within the asset server, the entry which holds an asset (and which asset handles refer to) would add a ticks field, just like components and resources have; and this field would be updated whenever the asset was loaded, reloaded or otherwise changed state. Just as there are APIs for getting the current asset load state, there would be corresponding APIs for getting the change ticks.

I could then use the return value to poll whether an asset had changed.

This new API would not replace the old event-based change detection system, but would augment it.

What alternative(s) have you considered?

I've considered alternate approaches but they are all quite complex and have non-trivial performance impact. For example, one could keep a separate hash map of all asset handles, which then maps to a tick count, and then keep that map up to date by listening to all asset load events. Conversely, one might be able to make a map of only the assets one is interested in, but this requires inventing some kind of "handle-to-a-handle" so that entries from the map can be dropped when there's no longer interest in watching them. Both of these approaches are highly problematic.

Another approach is to copy the asset you want to depend on into a resource, and then have your reactive UI depend on the resource. The obvious problem here is that it introduces considerable extra boilerplate.

alice-i-cecile commented 1 month ago

Does https://github.com/bevyengine/bevy/pull/5080 solve your needs?

viridia commented 1 month ago

It will work but it's not the ideal solution. I need to track assets of different type within the same map; this is easiest if the APIs involve can work in a type-erased manner. Otherwise, each map entry has to be a dyn trait object which wraps the generic data needed to poll the state.

viridia commented 1 month ago

Historical note: this was how I handled resources before we added .get_resource_change_ticks_by_id() - basically I had a TrackedResource<R> trait that would poll a given resource for changes. That is no longer needed with the newer API.

cart commented 1 month ago

Worth noting that assets-as-entities would give us this "for free" as assets would be components, which would have change ticks.

viridia commented 1 month ago

@cart If assets-as-entities were available today, there are a lot of improvements I could make to my code.

One example is "tabular" assets: my game engine uses a lot of shared, reusable assets which are stored in tables, such that the table is stored in a single file. This means that although the asset is conceptually multiple items, it is loaded as a single Asset; and the granularity of change detection is the entire collection.

My game uses an instanced terrain system where the terrain is made up of modular chunks that can be instanced (I think Diablo does something similar). For loading/performance reasons, I don't store each chunk as a separate asset, but rather a single asset which contains a "contours table". Unfortunately, this means that in the editor, when I edit a terrain chunk, it makes the entire asset as changed, which means that the whole world gets rebuilt - all terrain meshes and flora instances. To get around this, I have to maintain my own change detection / dirty bit system that has a finer granularity, so that individual terrain chunks can be marked as changed.

In an assets-as-entities world, I presume that the collection could be a hierarchy of entities, that is, it could have child entities which have their own separate change bits.

cart commented 1 month ago

In an assets-as-entities world, I presume that the collection could be a hierarchy of entities, that is, it could have child entities which have their own separate change bits.

It would mean that you could have a hierarchy of entities, although whether we embrace that in such a way that the asset loader itself could create that hierarchy / only modify things that have changed is a big open question. I'm thinking we might want to keep it simple / have a one-entity-per-asset model.

My general thought at the moment is:

  1. Embrace the one-entity-per-asset model (much like the current Assets<T> storage where each asset has a single AssetId/type)
  2. Allow developers to (post-asset-load) manually tack on additional components to that asset entity (and/or build hierarchies under it).

But I'm definitely down to discuss how this should work. It is also worth thinking about how sub-assets / labeled assets relate to each other in the context of assets-as-entities (ex: do we support / use entity hierarchies).