Skip to content
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

Store resources as components on singleton entities #17485

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

alice-i-cecile
Copy link
Member

@alice-i-cecile alice-i-cecile commented Jan 21, 2025

Objective

Various ECS features (hooks, observers, immutable components, relationships) don't work with resources.
Users (and engine devs) rightfully expect that they would, and are frustrated that they don't.

While we could replicate these features (and all future ECS features) to support resources, this is a serious source of complexity and slows down implementation.

Additionally, various patterns (networking and serialization in particular) would like to operate generically over data, regardless of whether it's a resource or a component.

Solution

  • make Resource require a Component trait bound, and make the Resource derive also derive Component
  • add a ResourceEntity<R> component, which marks an entity as holding a resource of type R. This component is "unique": only entity with this component (for a given R) can exist at once (using hooks)
  • add a IsResource marker component, used for all resource entities, regardless of their type. This will mostly be useful to allow inspectors to sort resources into their own list.
  • store the resource data of type R as a component on R
  • add a ComponentId -> Entity lookup for resources to the World
  • spawn an entity with the right data when inserting a resource
  • despawn the matching entity when removing a resource
  • look up the matching entity when fetching resource data from the World
  • rewrite the internal logic of Res and ResMut to look up entity data
  • rewrite dynamic system building logic to ensure that it works under the new model
  • migrate dynamic resource logic
  • remove or seriously reduce ReflectResource: now redundant as ReflectComponent can be used
  • remove the now-unused internals for working with resources

Note: we fully expect this to slow down resource look-ups relative to the previous tightly optimized implementation. While we can likely claw back some of that performance (see future work), we think that the simpler code base and added features are worth it.

Controversial choices

Please argue with these if you disagree!

  1. The Resource derive also derives Component. This is somewhat implicit, and of questionable Rust style. This is much less breaking for existing users, and less confusing for beginners.
  2. The Resource trait (and all existing APIs) continue to exist. As discussed in this HackMD, we think that this better communicates intent and eases learning, even though all of the methods could just use bare entity-component APIs
  3. Components on resource entities will show up in queries by default. While the new default query filters feature would let us exclude them, being able to operate generically is useful for code that can be flexibly used as either a resource or component.
  4. The resource marker components are publicly constructable, and rely on hooks to ensure correctness. We could instead refuse to expose any constructors, and avoid the use of hooks by auditing and testing all call sites.
  5. Resource data is stored directly as R on the resource entity, instead of inside of a wrapper type. This allows users to operate abstractly over types which can be used as either resources or components more easily.

Future work

  1. By storing the Entity of each resource type in the ECS itself (possibly as part of a components-as-entities push), we could make looking up resources substantially faster, as we wouldn't need to query for matching entities.
  2. We could remove or greatly strip down the ReflectResource trait, as all resources are now components.

Testing

TODO

Migration Guide

TODO

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Complex Quite challenging from either a design or technical perspective. Ask for help! X-Controversial There is active debate or serious implications around merging this PR M-Needs-Release-Note Work that should be called out in the blog due to impact S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jan 21, 2025
@ItsDoot ItsDoot added the S-Needs-Benchmarking This set of changes needs performance benchmarking to double-check that they help label Jan 21, 2025
@ItsDoot
Copy link
Contributor

ItsDoot commented Jan 21, 2025

There are performance implications here w.r.t using World::try_query/_filtered that should be benchmarked, even if we decide not to tackle them in this PR.

@alice-i-cecile
Copy link
Member Author

[17:38]Alice 🌹: After a nice cup of tea and a bike ride, I think that trying to avoid caching the ComponentId -> Entity lookup for resources is silly and going to bite us in terms of perf
[17:40]Alice 🌹: So that means:

Don't use component hooks, instead make the marker types unconstructable.
Add a field to world for the resource_map: HashMap<ComponentId, Entity> lookup
Write to that field when inserting and removing resources
Read from that field during lookups
[17:40]Alice 🌹: No queries needed
[17:40]Alice 🌹: It's good to keep around the marker components (at least the generic resource one), but that'll be entirely an end-user / inspector nicety

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide M-Needs-Release-Note Work that should be called out in the blog due to impact S-Needs-Benchmarking This set of changes needs performance benchmarking to double-check that they help S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants