Skip to content

Reserve entities from async #18003

Open
Open
@andriyDev

Description

@andriyDev

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

  1. The asset system needs to support creating new asset handles in an AssetLoader in order to load dependencies (e.g., a material asset needs to load a separate PNG asset and include that handle in the material). Assets as Entities #11266 aims to make asset handles a wrapper around an Entity, therefore we need to be able to reserve an entity in async contexts.
  2. If you want to asynchronously generate some entities, you must use CommandQueue. The main limitation it has is that you cannot spawn entities and get its id - you can only queue a command that uses the ID internally. This makes it difficult to generate relationships between entities without just putting everything in one big command.

What solution would you like?

We should allow entities to be reserved asynchronously. We mostly have this already as systems can reserve entities multithreaded. One thing that's missing is being able to get an Arc for the Entities (or similar) to pass to async code.

Risks

  1. We "flush" Entities to turn the reserved IDs into allocated IDs. This means we either need to be really clever with how we flush, or we need to just lock everything whenever we do a flush. This will likely have some synchronization overhead that may affect non-async code.
  2. Exclusive world access uses a "fast path" for allocating entities: Entities::alloc. We can't really use this anymore, since asynchronous code could have reserved the entity we would just be allocating. This is a performance concern even for exclusive world access. Note we likely can't "specialize" whether Entities is aliased (e.g., if there is no async users, use the fast path), since something like assets-as-entities will need to perpetually hold a reference (i.e., Arc) so the AssetServer can pass it to async at any point.

Alternatives

Rather than try to make Entities work fully multithreaded, we can instead make reserving an entity an async operation. In other words, we could have something like Res<AsyncEntityReserver>. You would pass this into an async closure and call reserver.reserve_entity().await. This would enqueue a request for an entity. In Bevy, we'd have a system that looks at this queue allocates an entity from the system using regular commands, and then responds with that entity's ID back to the async closure (i.e., through an async channel).

In regards to Assets-as-entities, this means that loading a dependency in an AssetLoader is now an async operation, which is quite sad (load_context.loader().load("some_other_asset.txt").await). In addition, loading assets this way would be slower, requiring waiting a frame to elapse before moving loading forward.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-FeatureA new feature, making something new possibleD-ComplexQuite challenging from either a design or technical perspective. Ask for help!S-Needs-DesignThis issue requires design work to think about how it would best be accomplishedX-BlessedHas a large architectural impact or tradeoffs, but the design has been endorsed by decision makers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions