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

ecs: ergonomic query.iter(), remove locks, add QuerySets #741

Merged
merged 1 commit into from
Oct 30, 2020

Conversation

cart
Copy link
Member

@cart cart commented Oct 28, 2020

Summary

  • query.iter() is now a real iterator!
    // This means you can do this:
    for (a, b) in query.iter() { }
    
    // Instead of this:
    for (a, b) in &mut query.iter() {}
    
    // Or this:
    for (a, b) in query.iter().iter() {}
    
    // Good riddance!
  • query.entity() is now "flat".
    // This means you can do this:
    if let Ok((a, b)) = query.entity(entity) {}
    
    // Instead of this:
    if let Ok(mut result) = query.entity(entity) {
      if let Some((a, b)) = result.get() { }
    }
    
    // Boilerplate be gone!
  • All locks and safety checks removed from direct Query accesses. This means query.iter() and query.entity() are exactly as fast as their World counterparts (which became lock-less in the last bevy_ecs update). This is because ...
  • Query safety is now enforced in the query system function signature. Querys are not allowed to conflict, by default. If you need conflicting Queries in your system, wrap them in the new QuerySet type. This provides lifetime safety that prevents illegal query accesses:
    // this system will now fail when constructing the schedule because `a` and `b` conflict
    fn system(a: Query<&mut A>, b: Query<(&mut A, &B)>) {
    }
    
    // you can use QuerySets to work around this safely
    fn system(set: QuerySet<(Query<&mut A>, Query<(&mut A, &B)>)>) {
      for a in set.q0_mut().iter_mut() {
        // the borrow checker will fail here because `set` is already borrowed mutably
        // this would have failed at runtime in previous bevy_ecs versions, but now we fail at compile time!  
        for (a, b) in set.q1_mut().iter_mut() {
        } 
      }
    }
  • Systems can now run even more parallel-ly than they could before. Systems that have mutability conflicts on the same archetype can now still run in parallel provided the individual components don't have mutability conflicts
  • query.par_iter() and query.par_iter_mut() were added to the top level Query type
  • replaced the access() -> Option<Access>, borrow(), and release() methods on Fetch with a single, much simpler and flexible access() -> QueryAccess
  • Impl ExactSizeIterator for QueryIter, but only when the Query can cheaply return an exact size without a full scan (uses the new UnfilteredFetch trait)
  • Rewrote QueryIter to be much simpler. This results in much more stable and predictable optimizations. This should prevent certain permutations of systems (and sets of systems) from falling off the "fast/optimized path", as called out here: Establishing More Reliable Benchmarks #655 (comment)

@cart cart added the A-ECS Entities, components, systems, and events label Oct 28, 2020
@cart
Copy link
Member Author

cart commented Oct 28, 2020

benchmark results:

image

image

image

(any omitted benchmarks from ecs_bench_suite had no significant changes)

The most interesting outcomes:

  1. Retrieving individual query results in systems is crazy fast now. I included a comparison to legion to illustrate the benefits of the lock-less approach. Legion cannot validate SubWorld calls ahead of time, so it must validate each one. Bevy "pre-validates" query access, which means it can skip the safety checks for anything that immediately matches the query:
    • get_component/bevy: direct world access using world.get::<Component>(entity)
    • get_component/bevy_query: retrieving a component from Query<&mut Component> using query.entity(entity)
    • get_component/legion: direct world access using let entry = world.entry(entity); entry.get_component_mut::<A>()
    • get_component/legion_system: SubWorld access using let entry = world.entry(entity); entry.get_component_mut::<A>()
  2. Some nice improvements to frag_iter results

@cart cart force-pushed the query_iter branch 2 times, most recently from a7e7bf1 to 9a08483 Compare October 28, 2020 03:16
@Moxinilian Moxinilian added C-Code-Quality A section of code that is hard to understand or change C-Performance A change motivated by improving speed, memory usage or compile times labels Oct 28, 2020
@BoxyUwU
Copy link
Member

BoxyUwU commented Oct 29, 2020

The docs on ParallelExecutor need to be updated to talk about the parallelism change I think

@cart
Copy link
Member Author

cart commented Oct 30, 2020

done!

@cart cart merged commit 1d4a95d into bevyengine:master Oct 30, 2020
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-Code-Quality A section of code that is hard to understand or change C-Performance A change motivated by improving speed, memory usage or compile times
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants