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

Upstreaming Bevy ECS Changes #71

Closed
cart opened this issue Aug 15, 2020 · 4 comments
Closed

Upstreaming Bevy ECS Changes #71

cart opened this issue Aug 15, 2020 · 4 comments

Comments

@cart
Copy link

cart commented Aug 15, 2020

My recently announced Bevy ECS project uses a forked version of hecs, which adds the following features on top:

(copied directly from the blog post)

  • Function Systems: Hecs actually has no concept of a "system" at all. You just run queries directly on the World. Bevy adds the ability to define portable, schedulable systems using normal Rust functions.
  • Resources: Hecs has no concept of unique/global data. When building games, this is often needed. Bevy adds a Resource collection and resource queries
  • Parallel Scheduler: Hecs is single threaded, but it was designed to allow parallel schedulers to be built on top. Bevy ECS adds a custom dependency-aware scheduler that builds on top of the "Function Systems" mentioned above.
  • Optimization: Hecs is already plenty fast, but by modifying some of its internal data access patterns, we were able to improve performance significantly. This moved it from "fast enough" to "the fastest" (see the benchmark above to compare Bevy ECS to vanilla Hecs).
  • Query Wrappers: The Query Bevy ECS exports is actually a wrapper around Hecs Queries. It provides safe, scoped access to the World in a multi-threaded context and improves the ergonomics of iteration.
  • Change Detection: Automatically (and efficiently) tracks component add/remove/update operations and exposes them in the Query interface.
  • Stable Entity IDs: Almost every ECS (including Hecs) uses unstable entity ids that cannot be used for serialization (scenes / save files) or networking. In Bevy ECS, entity ids are globally unique and stable. You can use them in any context!

Most of the performance improvements came from removing Entity from the iterator and instead returning it via queries. I suspect it allowed rust to inline something (or otherwise optimize it) in a way that it couldn't before. I also tweaked manual inlining in a few places.

I'd also like to note that Stable Entity IDs do come at a performance cost, and the current version uses random u32's, which bevy users have proven to have a high collision risk. That being said the collision risk is a solvable problem and the performance cost is "worth it" to me. (although entity ids are still a hot discussion, as you can probably see in that thread). You can see the difference illustrated in the performance graphs in the blog post above.

I'd love to upstream whatever features you want from Bevy ECS, so its mainly a question of "what do you want" / "what fits the hecs scope".

I would also like to at least discuss the possibility of Bevy eventually consuming upstream hecs directly. That way improvements to hecs could make it into Bevy, and Bevy developers would be encouraged to contribute to hecs. I see a few things that could block that:

  • I had to make a number of "type visibility" changes to maintain a clean separation between bevy_ecs logic and hecs logic. These may or may not make sense for hecs upstream
  • I'm currently embedding a number of additional Query implementations in query.rs. If you don't want these types, then I'd need to move them to bevy_ecs. I've noticed that optimizations sometimes break down when moving code across crates. If theres no way to force optimal performance across crate boundaries, I'd be hesitant to take that performance hit.
  • There are a number of features embedded directly into the Bevy hecs fork that would either need to be adopted in full, or abstracted out somehow (which could be very difficult):
    • Stable Entity Ids
    • Change tracking
@cart
Copy link
Author

cart commented Aug 15, 2020

I would also like to go on the record to say that hecs has been an absolute pleasure to work with. Its simple, fast, and the api is clean. You've done some seriously great work here!

@Ralith
Copy link
Owner

Ralith commented Aug 15, 2020

Thanks for the writeup! I'm happy to hear that hecs was so useful to you, and I'm excited at the prospect of pulling in improvements and potentially combining efforts in the future. I'd hoped from the beginning that hecs would be useful for work like yours and it's satisfying to see that borne out.

A guiding principle for hecs' design is that any demonstrably useful ECS functionality which is not built in should be painlessly implementable in an external crate, and that as much stuff as possible should be in the latter set, with exceptions for the trivial or near universally applicable. This preserves the simplicity which makes hecs unique and uniquely reusable.

As you observe, this does entail some potentially hard API design challenges, but they're challenges I'm interested in. hecs has some initial work in that direction (e.g. the explicit archetype-related APIs) but it's very much early stages, since I haven't wanted to build out APIs without concrete use cases driving them--which bevy now provides. In that spirit, I'm definitely open to making more stuff visible, though we should take care to clearly distinguish end-user-relevant stuff from extension interfaces.

So, for each change, let's consider whether it's best merged into hecs, or whether we should explore ways to let it live outside:

  • Systems, resources, schedulers:
    AFAIK these work great outside of hecs already. Let me know if there's something missing!

  • Optimizations:
    Obviously a core hecs issue, and very welcome. A dedicated PR with just the inlining changes could be merged quickly.

    Most of the performance improvements came from removing Entity from the iterator and instead returning it via queries

    Can you elaborate on this a bit, or highlight the commit(s) that introduced this change? I don't quite follow.

    I've noticed that optimizations sometimes break down when moving code across crates

    In extreme, this should always be fixable by enabling full LTO. Typically I suspect careful application of #[inline] will suffice.

  • Change Detection:
    Can you discuss the approach you took here? I could go either way on it depending on how it's best implemented. External implementation would presumably require exposing some of the Query guts, but I'm open to that. On the other hand, it's not a terribly obscure use case, and maybe it's simple enough not to justify the effort of keeping it isolated?

  • Stable Entity IDs:
    This one gives me pause. How large are the drawbacks of an explicit external HashMap for stable IDs (which bevy would bake in and hide from users) rather than baking it right into the ECS?

@Ralith
Copy link
Owner

Ralith commented Sep 19, 2020

Looks like the one thing I was leery about got rolled back in bevyengine/bevy#504, so I'm extra enthusiastic for this now!

@Ralith
Copy link
Owner

Ralith commented Feb 26, 2021

Closing as we've intentionally diverged per offline discussion, since serving both projects' requirements with a single library would be a hard research problem. Looking forward to continuing to exchange ideas, though! There's already some stuff I want to crib from Bevy ECS V2...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants