├── LICENSE.md ├── src ├── tuple_macro.rs ├── query_marker.rs ├── resource │ ├── mod.rs │ ├── tuple.rs │ ├── ref_extractor.rs │ ├── cell.rs │ ├── wrap.rs │ ├── atomic_borrow.rs │ ├── fetch.rs │ └── contains.rs ├── executor │ ├── sequential.rs │ ├── parallel │ │ ├── dispatching.rs │ │ ├── mod.rs │ │ └── scheduling.rs │ ├── mod.rs │ └── builder.rs ├── run.rs ├── access_set.rs ├── batch.rs ├── lib.rs ├── system_context.rs ├── resources_interop.rs └── query_bundle.rs ├── CHANGELOG.md ├── LICENSE-MIT ├── Cargo.toml ├── tests ├── builder.rs └── executor.rs ├── examples ├── crate_doc_example.rs └── convoluted.rs ├── .github └── workflows │ └── CI.yml ├── README.md └── LICENSE-APACHE /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Licensed under either of 4 | 5 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 6 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 7 | 8 | at your option. 9 | 10 | ## Contribution 11 | 12 | Unless you explicitly state otherwise, any contribution intentionally submitted 13 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 14 | dual licensed as above, without any additional terms or conditions. 15 | -------------------------------------------------------------------------------- /src/tuple_macro.rs: -------------------------------------------------------------------------------- 1 | macro_rules! expand { 2 | ($macro:ident, $letter:ident) => { 3 | //$macro!($letter); 4 | }; 5 | ($macro:ident, $letter:ident, $($tail:ident),*) => { 6 | $macro!($letter, $($tail),*); 7 | expand!($macro, $($tail),*); 8 | }; 9 | } 10 | 11 | macro_rules! impl_for_tuples { 12 | ($macro:ident) => { 13 | expand!($macro, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A); 14 | }; 15 | } 16 | 17 | macro_rules! count { 18 | () => {0usize}; 19 | ($head:tt $($tail:tt)*) => {1usize + count!($($tail)*)}; 20 | } 21 | -------------------------------------------------------------------------------- /src/query_marker.rs: -------------------------------------------------------------------------------- 1 | use hecs::Query; 2 | use std::marker::PhantomData; 3 | 4 | /// A zero-sized `Copy` type used to describe queries of a system, and prepare them 5 | /// via methods of [`SystemContext`](struct.SystemContext.html). 6 | /// 7 | /// It cannot be instantiated directly. See [`System`](trait.System.html) for instructions 8 | /// on how to call systems outside of an executor, as plain functions. 9 | pub struct QueryMarker(PhantomData) 10 | where 11 | Q0: Query; 12 | 13 | impl QueryMarker 14 | where 15 | Q0: Query, 16 | { 17 | pub(crate) fn new() -> Self { 18 | Self(PhantomData) 19 | } 20 | } 21 | 22 | impl Clone for QueryMarker 23 | where 24 | Q0: Query, 25 | { 26 | fn clone(&self) -> Self { 27 | QueryMarker::new() 28 | } 29 | } 30 | 31 | impl Copy for QueryMarker where Q0: Query {} 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased](https://github.com/Ratysz/yaks/compare/0.1.0..HEAD) 8 | ### Added 9 | - `resources-interop` feature: when enabled, allows `Executor::run()` to also 10 | accept `Resources` struct from the `resources` crate in place of resources argument. 11 | - `SystemContext::reserve_entity()`, `::contains()`, `::archetypes()`, 12 | and `::archetype_generation()`, mirroring similar methods of `hecs::World`. 13 | - CI badge. 14 | ### Changed 15 | - `Executor::run()` now uses `rayon::scope_fifo()`. 16 | - Minor doc tweaks. 17 | - Fixed changelog dates. 18 | - Internal refactors. 19 | ### Removed 20 | - `test` feature. 21 | 22 | ## [0.1.0](https://github.com/Ratysz/yaks/compare/0.0.0-aplha1..0.1.0) - 2020-06-06 23 | ### Changed 24 | - Full rewrite. 25 | 26 | ## [0.0.0-aplha1](https://github.com/Ratysz/yaks/releases/tag/0.0.0-aplha1) - 2020-01-14 27 | ### Added 28 | - Initial release. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexander Sepity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yaks" 3 | version = "0.1.0" 4 | description = "Minimalistic framework for automatic multithreading of hecs via rayon" 5 | authors = ["Alexander Sepity "] 6 | edition = "2018" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/Ratysz/yaks" 9 | readme = "README.md" 10 | keywords = ["hecs", "parallel", "ecs", "entity", "component"] 11 | categories = ["concurrency", "game-engines"] 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | 16 | [badges] 17 | maintenance = { status = "actively-developed" } 18 | 19 | [features] 20 | default = ["parallel"] 21 | # If disabled, forces everything to work on a single thread. 22 | parallel = ["crossbeam-channel", "fixedbitset", "rayon"] 23 | # If enabled, allows `Executor::run()` to also accept `resources::Resources`. 24 | resources-interop = ["resources"] 25 | 26 | [dependencies] 27 | crossbeam-channel = { version = "0.5.0", optional = true } 28 | fixedbitset = { version = "0.3.0", optional = true } 29 | hecs = "0.3.0" 30 | parking_lot = "0.11.0" 31 | paste = "1.0.0" 32 | rayon = { version = "1.3.0", optional = true } 33 | resources = { version = "1.1.0", features = ["fetch"], optional = true } 34 | 35 | [dev-dependencies] 36 | rand = "0.7.3" -------------------------------------------------------------------------------- /src/resource/mod.rs: -------------------------------------------------------------------------------- 1 | //! Resource flow: 2 | //! - resources argument is passed to `Executor::::run()`, 3 | //! - tuple of references to types in `Tuple` is extracted 4 | //! from the argument (`RefExtractor`), 5 | //! - the references, together with `AtomicBorrow`s from the executor, 6 | //! are wrapped into `ResourceCell`s (`ResourceWrap`), 7 | //! - when each system in the executor is ran, a subset tuple of references matching 8 | //! that of the system's resources argument is fetched from the cells, setting runtime 9 | //! borrow checking (`Fetch` for the whole tuple, `Contains` for each of it's elements), 10 | //! - the subset tuple of references is passed into the system's boxed closure, 11 | //! - after closure returns, the borrows are "released", resetting runtime 12 | //! borrow checking (`Fetch` and `Contains` again), 13 | //! - after all of the systems have been ran, the cells are dropped. 14 | 15 | mod atomic_borrow; 16 | mod cell; 17 | mod contains; 18 | mod fetch; 19 | mod ref_extractor; 20 | mod tuple; 21 | mod wrap; 22 | 23 | use cell::ResourceCell; 24 | use contains::Contains; 25 | 26 | pub use atomic_borrow::AtomicBorrow; 27 | pub use fetch::Fetch; 28 | pub use ref_extractor::RefExtractor; 29 | pub use tuple::ResourceTuple; 30 | pub use wrap::ResourceWrap; 31 | -------------------------------------------------------------------------------- /src/executor/sequential.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | 3 | use super::SystemClosure; 4 | use crate::{ExecutorBuilder, ResourceTuple, SystemContext, SystemId}; 5 | 6 | pub struct ExecutorSequential<'closures, Resources> 7 | where 8 | Resources: ResourceTuple, 9 | { 10 | systems: Vec<(SystemId, Box>)>, 11 | } 12 | 13 | impl<'closures, Resources> ExecutorSequential<'closures, Resources> 14 | where 15 | Resources: ResourceTuple, 16 | { 17 | pub fn build(builder: ExecutorBuilder<'closures, Resources, Handle>) -> Self { 18 | let ExecutorBuilder { mut systems, .. } = builder; 19 | let mut systems: Vec<_> = systems 20 | .drain() 21 | .map(|(id, system)| (id, system.closure)) 22 | .collect(); 23 | systems.sort_by(|(a, _), (b, _)| a.cmp(b)); 24 | ExecutorSequential { systems } 25 | } 26 | 27 | pub fn force_archetype_recalculation(&mut self) {} 28 | 29 | pub fn run(&mut self, world: &World, wrapped: Resources::Wrapped) { 30 | for (id, closure) in &mut self.systems { 31 | closure( 32 | SystemContext { 33 | system_id: Some(*id), 34 | world, 35 | }, 36 | &wrapped, 37 | ); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/builder.rs: -------------------------------------------------------------------------------- 1 | use yaks::{Executor, SystemContext}; 2 | 3 | fn dummy_system(_: SystemContext, _: (), _: ()) {} 4 | 5 | #[test] 6 | #[should_panic(expected = "system 0 already exists")] 7 | fn duplicate_handle() { 8 | Executor::<()>::builder() 9 | .system_with_handle(dummy_system, 0) 10 | .system_with_handle(dummy_system, 0) 11 | .build(); 12 | } 13 | 14 | #[test] 15 | #[should_panic(expected = "system 0 already exists")] 16 | fn duplicate_handle_with_deps() { 17 | Executor::<()>::builder() 18 | .system_with_handle(dummy_system, 0) 19 | .system_with_handle_and_deps(dummy_system, 0, vec![0]) 20 | .build(); 21 | } 22 | 23 | #[test] 24 | #[should_panic(expected = "could not resolve dependencies of system 1: no system 2 found")] 25 | fn invalid_dependency() { 26 | Executor::<()>::builder() 27 | .system_with_handle(dummy_system, 0) 28 | .system_with_handle_and_deps(dummy_system, 1, vec![2]) 29 | .build(); 30 | } 31 | 32 | #[test] 33 | #[should_panic( 34 | expected = "could not resolve dependencies of a handle-less system: no system 1 found" 35 | )] 36 | fn invalid_dependency_no_handle() { 37 | Executor::<()>::builder() 38 | .system_with_handle(dummy_system, 0) 39 | .system_with_deps(dummy_system, vec![1]) 40 | .build(); 41 | } 42 | 43 | #[test] 44 | #[should_panic(expected = "system 1 depends on itself")] 45 | fn self_dependency() { 46 | Executor::<()>::builder() 47 | .system_with_handle(dummy_system, 0) 48 | .system_with_handle_and_deps(dummy_system, 1, vec![1]) 49 | .build(); 50 | } 51 | -------------------------------------------------------------------------------- /src/resource/tuple.rs: -------------------------------------------------------------------------------- 1 | use super::{AtomicBorrow, ResourceCell}; 2 | 3 | /// Specifies how a tuple behaves when used as the generic parameter of an executor. 4 | pub trait ResourceTuple { 5 | type Wrapped: Send + Sync; 6 | type BorrowTuple: Send + Sync; 7 | const LENGTH: usize; 8 | 9 | fn instantiate_borrows() -> Self::BorrowTuple; 10 | } 11 | 12 | impl ResourceTuple for () { 13 | type Wrapped = (); 14 | type BorrowTuple = (); 15 | const LENGTH: usize = 0; 16 | 17 | fn instantiate_borrows() -> Self::BorrowTuple {} 18 | } 19 | 20 | impl ResourceTuple for (R0,) 21 | where 22 | R0: Send + Sync, 23 | { 24 | type Wrapped = (ResourceCell,); 25 | type BorrowTuple = (AtomicBorrow,); 26 | const LENGTH: usize = 1; 27 | 28 | fn instantiate_borrows() -> Self::BorrowTuple { 29 | (AtomicBorrow::new(),) 30 | } 31 | } 32 | 33 | macro_rules! swap_to_atomic_borrow { 34 | ($anything:tt) => { 35 | AtomicBorrow 36 | }; 37 | (new $anything:tt) => { 38 | AtomicBorrow::new() 39 | }; 40 | } 41 | 42 | macro_rules! impl_resource_tuple { 43 | ($($letter:ident),*) => { 44 | impl<$($letter),*> ResourceTuple for ($($letter,)*) 45 | where 46 | $($letter: Send + Sync,)* 47 | { 48 | type Wrapped = ($(ResourceCell<$letter>,)*); 49 | type BorrowTuple = ($(swap_to_atomic_borrow!($letter),)*); 50 | const LENGTH: usize = count!($($letter)*); 51 | 52 | fn instantiate_borrows() -> Self::BorrowTuple { 53 | ($(swap_to_atomic_borrow!(new $letter),)*) 54 | } 55 | } 56 | } 57 | } 58 | 59 | impl_for_tuples!(impl_resource_tuple); 60 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | 3 | use crate::{QueryBundle, SystemContext}; 4 | 5 | // TODO improve doc 6 | /// Automatically implemented on all closures and functions than 7 | /// can be used as systems in an executor. 8 | pub trait System<'closure, Resources, Queries, RefSource, Marker> { 9 | /// Zero-cost wrapping function that executes the system. 10 | fn run(&mut self, world: &World, resources: RefSource); 11 | } 12 | 13 | impl<'closure, Closure, Resources, Queries> System<'closure, Resources, Queries, Resources, ()> 14 | for Closure 15 | where 16 | Closure: FnMut(SystemContext, Resources, Queries) + Send + Sync + 'closure, 17 | Resources: Send + Sync, 18 | Queries: QueryBundle, 19 | { 20 | fn run(&mut self, world: &World, resources: Resources) { 21 | self( 22 | SystemContext { 23 | system_id: None, 24 | world, 25 | }, 26 | resources, 27 | Queries::markers(), 28 | ); 29 | } 30 | } 31 | 32 | #[test] 33 | fn smoke_test() { 34 | let world = hecs::World::new(); 35 | 36 | fn dummy_system(_: SystemContext, _: (), _: ()) {} 37 | dummy_system.run(&world, ()); 38 | 39 | let mut counter = 0i32; 40 | fn increment_system(_: SystemContext, value: &mut i32, _: ()) { 41 | *value += 1; 42 | } 43 | increment_system.run(&world, &mut counter); 44 | assert_eq!(counter, 1); 45 | 46 | let increment = 3usize; 47 | fn sum_system(_: SystemContext, (a, b): (&mut i32, &usize), _: ()) { 48 | *a += *b as i32; 49 | } 50 | sum_system.run(&world, (&mut counter, &increment)); 51 | assert_eq!(counter, 4); 52 | sum_system.run(&world, (&mut counter, &increment)); 53 | assert_eq!(counter, 7); 54 | } 55 | -------------------------------------------------------------------------------- /src/resource/ref_extractor.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | 3 | use super::{ResourceTuple, ResourceWrap}; 4 | use crate::Executor; 5 | 6 | // TODO consider exposing. 7 | 8 | /// Specifies how a tuple of references may be extracted from the implementor and used 9 | /// as resources when running an executor. 10 | pub trait RefExtractor: ResourceTuple + Sized { 11 | fn extract_and_run(executor: &mut Executor, world: &World, resources: RefSource); 12 | } 13 | 14 | impl RefExtractor<()> for () { 15 | fn extract_and_run(executor: &mut Executor, world: &World, _: ()) { 16 | executor.inner.run(world, ()); 17 | } 18 | } 19 | 20 | impl RefExtractor<&mut R0> for (R0,) 21 | where 22 | R0: Send + Sync, 23 | { 24 | fn extract_and_run(executor: &mut Executor, world: &World, mut resources: &mut R0) { 25 | let wrapped = resources.wrap(&mut executor.borrows); 26 | executor.inner.run(world, wrapped); 27 | } 28 | } 29 | 30 | impl RefExtractor<(&mut R0,)> for (R0,) 31 | where 32 | R0: Send + Sync, 33 | { 34 | fn extract_and_run(executor: &mut Executor, world: &World, mut resources: (&mut R0,)) { 35 | let wrapped = resources.wrap(&mut executor.borrows); 36 | executor.inner.run(world, wrapped); 37 | } 38 | } 39 | 40 | macro_rules! impl_ref_extractor { 41 | ($($letter:ident),*) => { 42 | impl<'a, $($letter),*> RefExtractor<($(&mut $letter,)*)> for ($($letter,)*) 43 | where 44 | $($letter: Send + Sync,)* 45 | { 46 | fn extract_and_run( 47 | executor: &mut Executor, 48 | world: &World, 49 | mut resources: ($(&mut $letter,)*), 50 | ) { 51 | let wrapped = resources.wrap(&mut executor.borrows); 52 | executor.inner.run(world, wrapped); 53 | } 54 | } 55 | } 56 | } 57 | 58 | impl_for_tuples!(impl_ref_extractor); 59 | -------------------------------------------------------------------------------- /src/resource/cell.rs: -------------------------------------------------------------------------------- 1 | use std::{ptr::NonNull, thread::panicking}; 2 | 3 | use super::AtomicBorrow; 4 | 5 | /// A pointer to a resource, with runtime borrow checking via an `AtomicBorrow`, 6 | /// accessed through a pointer to a cached one in an executor. 7 | pub struct ResourceCell { 8 | cell: NonNull, 9 | borrow: NonNull, 10 | } 11 | 12 | impl ResourceCell { 13 | pub fn new(resource: &mut R0, borrow: &mut AtomicBorrow) -> Self 14 | where 15 | R0: Send + Sync, 16 | { 17 | Self { 18 | cell: NonNull::new(resource).expect("pointers to resources should never be null"), 19 | borrow: NonNull::new(borrow).expect("pointers to AtomicBorrows should never be null"), 20 | } 21 | } 22 | 23 | pub fn borrow(&self) -> &R0 { 24 | assert!( 25 | unsafe { self.borrow.as_ref().borrow() }, 26 | "cannot borrow {} immutably: already borrowed mutably", 27 | std::any::type_name::() 28 | ); 29 | unsafe { self.cell.as_ref() } 30 | } 31 | 32 | #[allow(clippy::mut_from_ref)] 33 | pub fn borrow_mut(&self) -> &mut R0 { 34 | assert!( 35 | unsafe { self.borrow.as_ref().borrow_mut() }, 36 | "cannot borrow {} mutably: already borrowed", 37 | std::any::type_name::() 38 | ); 39 | unsafe { &mut *self.cell.clone().as_ptr() } 40 | } 41 | 42 | pub unsafe fn release(&self) { 43 | self.borrow.as_ref().release(); 44 | } 45 | 46 | pub unsafe fn release_mut(&self) { 47 | self.borrow.as_ref().release_mut(); 48 | } 49 | } 50 | 51 | impl Drop for ResourceCell { 52 | fn drop(&mut self) { 53 | #[cfg(debug_assertions)] 54 | if !panicking() { 55 | assert!( 56 | unsafe { self.borrow.as_ref().is_free() }, 57 | "borrows of {} were not released properly", 58 | std::any::type_name::() 59 | ) 60 | } 61 | } 62 | } 63 | 64 | unsafe impl Send for ResourceCell where R0: Send {} 65 | 66 | unsafe impl Sync for ResourceCell where R0: Sync {} 67 | -------------------------------------------------------------------------------- /src/resource/wrap.rs: -------------------------------------------------------------------------------- 1 | use super::{AtomicBorrow, ResourceCell}; 2 | 3 | /// Specifies how a tuple of references is wrapped into a tuple of cells. 4 | pub trait ResourceWrap { 5 | type Wrapped: Send + Sync; 6 | type BorrowTuple: Send + Sync; 7 | 8 | fn wrap(&mut self, borrows: &mut Self::BorrowTuple) -> Self::Wrapped; 9 | } 10 | 11 | impl ResourceWrap for () { 12 | type Wrapped = (); 13 | type BorrowTuple = (); 14 | 15 | fn wrap(&mut self, _: &mut Self::BorrowTuple) -> Self::Wrapped {} 16 | } 17 | 18 | impl ResourceWrap for &'_ mut R0 19 | where 20 | R0: Send + Sync, 21 | { 22 | type Wrapped = (ResourceCell,); 23 | type BorrowTuple = (AtomicBorrow,); 24 | 25 | fn wrap(&mut self, borrows: &mut Self::BorrowTuple) -> Self::Wrapped { 26 | (ResourceCell::new(self, &mut borrows.0),) 27 | } 28 | } 29 | 30 | impl ResourceWrap for (&'_ mut R0,) 31 | where 32 | R0: Send + Sync, 33 | { 34 | type Wrapped = (ResourceCell,); 35 | type BorrowTuple = (AtomicBorrow,); 36 | 37 | fn wrap(&mut self, borrows: &mut Self::BorrowTuple) -> Self::Wrapped { 38 | (ResourceCell::new(self.0, &mut borrows.0),) 39 | } 40 | } 41 | 42 | macro_rules! swap_to_atomic_borrow { 43 | ($anything:tt) => { 44 | AtomicBorrow 45 | }; 46 | (new $anything:tt) => { 47 | AtomicBorrow::new() 48 | }; 49 | } 50 | 51 | macro_rules! impl_resource_wrap { 52 | ($($letter:ident),*) => { 53 | paste::item! { 54 | impl<$($letter),*> ResourceWrap for ($(&'_ mut $letter,)*) 55 | where 56 | $($letter: Send + Sync,)* 57 | { 58 | type Wrapped = ($(ResourceCell<$letter>,)*); 59 | type BorrowTuple = ($(swap_to_atomic_borrow!($letter),)*); 60 | 61 | #[allow(non_snake_case)] 62 | fn wrap(&mut self, borrows: &mut Self::BorrowTuple) -> Self::Wrapped { 63 | let ($([],)*) = self; 64 | let ($([],)*) = borrows; 65 | ($( ResourceCell::new([], []) ,)*) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | impl_for_tuples!(impl_resource_wrap); 73 | -------------------------------------------------------------------------------- /examples/crate_doc_example.rs: -------------------------------------------------------------------------------- 1 | //! Copy of the crate level documentation & readme example. 2 | 3 | use hecs::{With, Without, World}; 4 | use yaks::{Executor, QueryMarker}; 5 | 6 | fn main() { 7 | let mut world = World::new(); 8 | let mut entities = 0u32; 9 | world.spawn_batch((0..100u32).map(|index| { 10 | entities += 1; 11 | (index,) 12 | })); 13 | world.spawn_batch((0..100u32).map(|index| { 14 | entities += 1; 15 | (index, index as f32) 16 | })); 17 | let mut increment = 5usize; 18 | let mut average = 0f32; 19 | let mut executor = Executor::<(u32, usize, f32)>::builder() 20 | .system_with_handle( 21 | |context, (entities, average): (&u32, &mut f32), query: QueryMarker<&f32>| { 22 | *average = 0.0; 23 | for (_entity, float) in context.query(query).iter() { 24 | *average += *float; 25 | } 26 | *average /= *entities as f32; 27 | }, 28 | "average", 29 | ) 30 | .system_with_handle( 31 | |context, increment: &usize, query: QueryMarker<&mut u32>| { 32 | for (_entity, unsigned) in context.query(query).iter() { 33 | *unsigned += *increment as u32 34 | } 35 | }, 36 | "increment", 37 | ) 38 | .system_with_deps(system_with_two_queries, vec!["increment", "average"]) 39 | .build(); 40 | executor.run(&world, (&mut entities, &mut increment, &mut average)); 41 | } 42 | 43 | #[allow(clippy::type_complexity)] 44 | fn system_with_two_queries( 45 | context: yaks::SystemContext, 46 | (entities, average): (&u32, &f32), 47 | (with_f32, without_f32): ( 48 | QueryMarker>, 49 | QueryMarker>, 50 | ), 51 | ) { 52 | yaks::batch( 53 | &mut context.query(with_f32), 54 | entities / 8, 55 | |_entity, unsigned| { 56 | *unsigned += average.round() as u32; 57 | }, 58 | ); 59 | yaks::batch( 60 | &mut context.query(without_f32), 61 | entities / 8, 62 | |_entity, unsigned| { 63 | *unsigned *= average.round() as u32; 64 | }, 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | 14 | test: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | rust: 19 | - stable 20 | - beta 21 | features: 22 | - --all-features 23 | - --no-default-features 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | 28 | - name: Install ${{ matrix.rust }} Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: ${{ matrix.rust }} 33 | override: true 34 | 35 | - name: Run cargo build --all-targets ${{ matrix.features }} 36 | uses: actions-rs/cargo@v1 37 | with: 38 | command: build 39 | args: --all-targets ${{ matrix.features }} 40 | 41 | - name: Run cargo test ${{ matrix.features }} 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: ${{ matrix.features }} 46 | 47 | rustfmt: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v2 52 | 53 | - name: Install stable Rust with rustfmt 54 | uses: actions-rs/toolchain@v1 55 | with: 56 | profile: minimal 57 | toolchain: stable 58 | override: true 59 | components: rustfmt 60 | 61 | - name: Run cargo fmt --all -- --check 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: fmt 65 | args: --all -- --check 66 | 67 | clippy: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Checkout 71 | uses: actions/checkout@v2 72 | 73 | - name: Install stable Rust with clippy 74 | uses: actions-rs/toolchain@v1 75 | with: 76 | profile: minimal 77 | toolchain: stable 78 | override: true 79 | components: clippy 80 | 81 | - name: Run cargo clippy --all-features 82 | uses: actions-rs/cargo@v1 83 | with: 84 | command: clippy 85 | args: --all-features -- -D warnings 86 | 87 | - name: Run cargo clippy --no-default-features 88 | if: always() 89 | uses: actions-rs/cargo@v1 90 | with: 91 | command: clippy 92 | args: --no-default-features -- -D warnings 93 | -------------------------------------------------------------------------------- /src/resource/atomic_borrow.rs: -------------------------------------------------------------------------------- 1 | // Following file contains modified copy of a fragment of `hecs` library source code. 2 | // Original license note is reproduced in the next comment block. 3 | 4 | // Copyright 2019 Google LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // https://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::sync::atomic::{AtomicUsize, Ordering}; 19 | 20 | pub struct AtomicBorrow(AtomicUsize); 21 | 22 | impl AtomicBorrow { 23 | const UNIQUE_BIT: usize = !(usize::max_value() >> 1); 24 | 25 | pub const fn new() -> Self { 26 | Self(AtomicUsize::new(0)) 27 | } 28 | 29 | pub fn is_free(&self) -> bool { 30 | self.0.load(Ordering::Acquire) == 0 31 | } 32 | 33 | pub fn borrow(&self) -> bool { 34 | let value = self.0.fetch_add(1, Ordering::Acquire).wrapping_add(1); 35 | if value == 0 { 36 | // Wrapped, this borrow is invalid! 37 | core::panic!() 38 | } 39 | if value & AtomicBorrow::UNIQUE_BIT != 0 { 40 | self.0.fetch_sub(1, Ordering::Release); 41 | false 42 | } else { 43 | true 44 | } 45 | } 46 | 47 | pub fn borrow_mut(&self) -> bool { 48 | self.0 49 | .compare_exchange( 50 | 0, 51 | AtomicBorrow::UNIQUE_BIT, 52 | Ordering::Acquire, 53 | Ordering::Relaxed, 54 | ) 55 | .is_ok() 56 | } 57 | 58 | pub fn release(&self) { 59 | let value = self.0.fetch_sub(1, Ordering::Release); 60 | debug_assert!(value != 0, "unbalanced release"); 61 | debug_assert!( 62 | value & AtomicBorrow::UNIQUE_BIT == 0, 63 | "shared release of unique borrow" 64 | ); 65 | } 66 | 67 | pub fn release_mut(&self) { 68 | self.0.store(0, Ordering::Release); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/access_set.rs: -------------------------------------------------------------------------------- 1 | use fixedbitset::FixedBitSet; 2 | use hecs::{Access, Query, World}; 3 | use std::{any::TypeId, collections::HashSet}; 4 | 5 | pub type TypeSet = HashSet; 6 | 7 | pub struct BorrowTypeSet { 8 | pub immutable: TypeSet, 9 | pub mutable: TypeSet, 10 | } 11 | 12 | impl BorrowTypeSet { 13 | // Clippy, this is an internal type that is instantiated in one place, chill. 14 | #[allow(clippy::new_without_default)] 15 | pub fn new() -> Self { 16 | Self { 17 | immutable: TypeSet::new(), 18 | mutable: TypeSet::new(), 19 | } 20 | } 21 | 22 | pub fn condense(self, all_types: &[TypeId]) -> BorrowSet { 23 | let mut set = BorrowSet::with_capacity(all_types.len()); 24 | all_types.iter().enumerate().for_each(|(index, element)| { 25 | if self.immutable.contains(element) { 26 | set.immutable.insert(index); 27 | } 28 | if self.mutable.contains(element) { 29 | set.mutable.insert(index); 30 | } 31 | }); 32 | set 33 | } 34 | } 35 | 36 | pub struct BorrowSet { 37 | pub immutable: FixedBitSet, 38 | pub mutable: FixedBitSet, 39 | } 40 | 41 | impl BorrowSet { 42 | pub fn with_capacity(bits: usize) -> Self { 43 | Self { 44 | immutable: FixedBitSet::with_capacity(bits), 45 | mutable: FixedBitSet::with_capacity(bits), 46 | } 47 | } 48 | 49 | pub fn is_compatible(&self, other: &BorrowSet) -> bool { 50 | self.mutable.is_disjoint(&other.mutable) 51 | && self.mutable.is_disjoint(&other.immutable) 52 | && self.immutable.is_disjoint(&other.mutable) 53 | } 54 | } 55 | 56 | #[derive(Default)] 57 | pub struct ArchetypeSet { 58 | pub immutable: FixedBitSet, 59 | pub mutable: FixedBitSet, 60 | } 61 | 62 | impl ArchetypeSet { 63 | pub fn is_compatible(&self, other: &ArchetypeSet) -> bool { 64 | self.mutable.is_disjoint(&other.mutable) 65 | && self.mutable.is_disjoint(&other.immutable) 66 | && self.immutable.is_disjoint(&other.mutable) 67 | } 68 | 69 | pub fn set_bits_for_query(&mut self, world: &World) 70 | where 71 | Q: Query, 72 | { 73 | self.immutable.clear(); 74 | self.mutable.clear(); 75 | let iterator = world.archetypes(); 76 | let bits = iterator.len(); 77 | self.immutable.grow(bits); 78 | self.mutable.grow(bits); 79 | iterator 80 | .enumerate() 81 | .filter_map(|(index, archetype)| archetype.access::().map(|access| (index, access))) 82 | .for_each(|(archetype, access)| match access { 83 | Access::Read => self.immutable.set(archetype, true), 84 | Access::Write => self.mutable.set(archetype, true), 85 | Access::Iterate => (), 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/resource/fetch.rs: -------------------------------------------------------------------------------- 1 | use super::Contains; 2 | 3 | #[cfg(feature = "parallel")] 4 | use crate::BorrowSet; 5 | 6 | /// Specifies how a tuple of types may be borrowed from a tuple of cells. 7 | pub trait Fetch<'a, T, M0>: Sized { 8 | fn fetch(resources: &'a T) -> Self; 9 | 10 | unsafe fn release(resources: &'a T); 11 | 12 | #[cfg(feature = "parallel")] 13 | fn set_resource_bits(resource_set: &mut BorrowSet); 14 | } 15 | 16 | impl<'a, T, M0, R0> Fetch<'a, T, M0> for &'a R0 17 | where 18 | T: Contains, 19 | R0: 'a, 20 | { 21 | fn fetch(resources: &'a T) -> Self { 22 | T::borrow(resources) 23 | } 24 | 25 | unsafe fn release(resources: &'a T) { 26 | T::release(resources); 27 | } 28 | 29 | #[cfg(feature = "parallel")] 30 | fn set_resource_bits(resource_set: &mut BorrowSet) { 31 | T::set_resource_bit(&mut resource_set.immutable); 32 | } 33 | } 34 | 35 | impl<'a, T, M0, R0> Fetch<'a, T, M0> for &'a mut R0 36 | where 37 | T: Contains, 38 | R0: 'a, 39 | { 40 | fn fetch(resources: &'a T) -> Self { 41 | T::borrow_mut(resources) 42 | } 43 | 44 | unsafe fn release(resources: &'a T) { 45 | T::release_mut(resources); 46 | } 47 | 48 | #[cfg(feature = "parallel")] 49 | fn set_resource_bits(resource_set: &mut BorrowSet) { 50 | T::set_resource_bit(&mut resource_set.mutable); 51 | } 52 | } 53 | 54 | impl<'a, T> Fetch<'a, T, ()> for () { 55 | fn fetch(_: &'a T) -> Self {} 56 | 57 | unsafe fn release(_: &'a T) {} 58 | 59 | #[cfg(feature = "parallel")] 60 | fn set_resource_bits(_: &mut BorrowSet) {} 61 | } 62 | 63 | impl<'a, T, M0, F0> Fetch<'a, T, (M0,)> for (F0,) 64 | where 65 | F0: Fetch<'a, T, M0>, 66 | { 67 | fn fetch(resources: &'a T) -> Self { 68 | (F0::fetch(resources),) 69 | } 70 | 71 | unsafe fn release(resources: &'a T) { 72 | F0::release(resources); 73 | } 74 | 75 | #[cfg(feature = "parallel")] 76 | fn set_resource_bits(resource_set: &mut BorrowSet) { 77 | F0::set_resource_bits(resource_set); 78 | } 79 | } 80 | 81 | macro_rules! impl_fetch { 82 | ($($letter:ident),*) => { 83 | paste::item! { 84 | impl<'a, T, $([],)* $([],)*> Fetch<'a, T, ($([],)*)> 85 | for ($([]),*) 86 | where 87 | $([]: Fetch<'a, T, []>,)* 88 | { 89 | fn fetch(resources: &'a T) -> Self { 90 | ($([]::fetch(resources)),*) 91 | } 92 | 93 | #[allow(non_snake_case)] 94 | unsafe fn release(resources: &'a T) { 95 | $([]::release(resources);)* 96 | } 97 | 98 | #[cfg(feature = "parallel")] 99 | fn set_resource_bits(resource_set: &mut BorrowSet) { 100 | $([]::set_resource_bits(resource_set);)* 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | impl_for_tuples!(impl_fetch); 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `yaks` 2 | [![Latest Version]][crates.io] 3 | [![Documentation]][docs.rs] 4 | [![License]][license link] 5 | [![CI]][CI link] 6 | 7 | [Latest Version]: https://img.shields.io/crates/v/yaks.svg 8 | [crates.io]: https://crates.io/crates/yaks 9 | [Documentation]: https://docs.rs/yaks/badge.svg 10 | [docs.rs]: https://docs.rs/yaks 11 | [License]: https://img.shields.io/crates/l/yaks.svg 12 | [license link]: https://github.com/Ratysz/yaks/blob/master/LICENSE.md 13 | [CI]: https://github.com/Ratysz/yaks/workflows/CI/badge.svg?branch=master 14 | [CI link]: https://github.com/Ratysz/yaks/actions?query=workflow%3ACI 15 | 16 | `yaks` aims to be a minimalistic and performant framework for automatic 17 | multithreading of [`hecs`] via [`rayon`]. 18 | 19 | The goals are, in no particular order: 20 | - safety 21 | - simplicity 22 | - performance 23 | - extensibility 24 | - tight engineering 25 | - minimal dependencies 26 | - effortless concurrency 27 | 28 | [`hecs`]: https://crates.io/crates/hecs 29 | [`rayon`]: https://crates.io/crates/rayon 30 | 31 | # Cargo features 32 | 33 | - `parallel` - enabled by default; can be disabled to force `yaks` to work on a single thread. 34 | Useful for writing the code once, and running it on platforms with or without threading. 35 | - `resources-interop` - when enabled, allows `Executor::run()` to also 36 | accept `Resources` struct from the [`resources`] crate in place of resources argument. 37 | 38 | [`resources`]: https://crates.io/crates/resources 39 | 40 | # Example 41 | 42 | A more elaborate and annotated example can be found [here](examples/convoluted.rs). 43 | 44 | ```rust 45 | use hecs::{With, Without, World}; 46 | use yaks::{Executor, QueryMarker}; 47 | 48 | fn main() { 49 | let mut world = World::new(); 50 | let mut entities = 0u32; 51 | world.spawn_batch((0..100u32).map(|index| { 52 | entities += 1; 53 | (index,) 54 | })); 55 | world.spawn_batch((0..100u32).map(|index| { 56 | entities += 1; 57 | (index, index as f32) 58 | })); 59 | let mut increment = 5usize; 60 | let mut average = 0f32; 61 | let mut executor = Executor::<(u32, usize, f32)>::builder() 62 | .system_with_handle( 63 | |context, (entities, average): (&u32, &mut f32), query: QueryMarker<&f32>| { 64 | *average = 0.0; 65 | for (_entity, float) in context.query(query).iter() { 66 | *average += *float; 67 | } 68 | *average /= *entities as f32; 69 | }, 70 | "average", 71 | ) 72 | .system_with_handle( 73 | |context, increment: &usize, query: QueryMarker<&mut u32>| { 74 | for (_entity, unsigned) in context.query(query).iter() { 75 | *unsigned += *increment as u32 76 | } 77 | }, 78 | "increment", 79 | ) 80 | .system_with_deps(system_with_two_queries, vec!["increment", "average"]) 81 | .build(); 82 | executor.run(&world, (&mut entities, &mut increment, &mut average)); 83 | } 84 | 85 | fn system_with_two_queries( 86 | context: yaks::SystemContext, 87 | (entities, average): (&u32, &f32), 88 | (with_f32, without_f32): ( 89 | QueryMarker>, 90 | QueryMarker>, 91 | ), 92 | ) { 93 | yaks::batch( 94 | &mut context.query(with_f32), 95 | entities / 8, 96 | |_entity, unsigned| { 97 | *unsigned += average.round() as u32; 98 | }, 99 | ); 100 | yaks::batch( 101 | &mut context.query(without_f32), 102 | entities / 8, 103 | |_entity, unsigned| { 104 | *unsigned *= average.round() as u32; 105 | }, 106 | ); 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /src/resource/contains.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "parallel")] 2 | use fixedbitset::FixedBitSet; 3 | 4 | use super::ResourceCell; 5 | 6 | /// Specifies how a specific type may be borrowed from a tuple of cells. 7 | pub trait Contains { 8 | fn borrow(&self) -> &R0; 9 | 10 | #[allow(clippy::mut_from_ref)] 11 | fn borrow_mut(&self) -> &mut R0; 12 | 13 | unsafe fn release(&self); 14 | 15 | unsafe fn release_mut(&self); 16 | 17 | #[cfg(feature = "parallel")] 18 | fn set_resource_bit(bitset: &mut FixedBitSet); 19 | } 20 | 21 | impl Contains for (ResourceCell,) { 22 | fn borrow(&self) -> &R0 { 23 | self.0.borrow() 24 | } 25 | 26 | fn borrow_mut(&self) -> &mut R0 { 27 | self.0.borrow_mut() 28 | } 29 | 30 | unsafe fn release(&self) { 31 | self.0.release(); 32 | } 33 | 34 | unsafe fn release_mut(&self) { 35 | self.0.release_mut(); 36 | } 37 | 38 | #[cfg(feature = "parallel")] 39 | fn set_resource_bit(bitset: &mut FixedBitSet) { 40 | bitset.insert(0); 41 | } 42 | } 43 | 44 | macro_rules! swap_to_unit { 45 | ($anything:tt) => { 46 | () 47 | }; 48 | } 49 | 50 | macro_rules! swap_to_markers { 51 | ($($letter:ident),*) => { 52 | ($( swap_to_unit!($letter), )*) 53 | } 54 | } 55 | 56 | macro_rules! impl_contains { 57 | ($($letter:ident),*) => { 58 | impl_contains!($($letter),* ; $($letter),*); 59 | }; 60 | ($($all:ident),* ; $letter:ident, $($tail:ident),*) => { 61 | #[allow(non_snake_case)] 62 | #[allow(unused_variables)] 63 | impl<$($all),*> Contains<$letter, ($letter, swap_to_markers!($($tail),*))> 64 | for ($(ResourceCell<$all>,)*) 65 | { 66 | fn borrow(&self) -> &$letter { 67 | let ($($all,)*) = self; 68 | $letter.borrow() 69 | } 70 | 71 | fn borrow_mut(&self) -> &mut $letter { 72 | let ($($all,)*) = self; 73 | $letter.borrow_mut() 74 | } 75 | 76 | unsafe fn release(&self) { 77 | let ($($all,)*) = self; 78 | $letter.release(); 79 | } 80 | 81 | unsafe fn release_mut(&self) { 82 | let ($($all,)*) = self; 83 | $letter.release_mut(); 84 | } 85 | 86 | #[cfg(feature = "parallel")] 87 | fn set_resource_bit(bitset: &mut FixedBitSet) { 88 | bitset.insert(count!($($all)*) - (1usize + count!($($tail)*))); 89 | } 90 | } 91 | impl_contains!($($all),* ; $($tail),*); 92 | }; 93 | ($($all:ident),* ; $letter:ident ) => { 94 | #[allow(non_snake_case)] 95 | #[allow(unused_variables)] 96 | impl<$($all),*> Contains<$letter, ($letter, )> 97 | for ($(ResourceCell<$all>,)*) 98 | { 99 | fn borrow(&self) -> &$letter { 100 | let ($($all,)*) = self; 101 | $letter.borrow() 102 | } 103 | 104 | fn borrow_mut(&self) -> &mut $letter { 105 | let ($($all,)*) = self; 106 | $letter.borrow_mut() 107 | } 108 | 109 | unsafe fn release(&self) { 110 | let ($($all,)*) = self; 111 | $letter.release(); 112 | } 113 | 114 | unsafe fn release_mut(&self) { 115 | let ($($all,)*) = self; 116 | $letter.release_mut(); 117 | } 118 | 119 | #[cfg(feature = "parallel")] 120 | fn set_resource_bit(bitset: &mut FixedBitSet) { 121 | bitset.insert(count!($($all)*) - 1usize); 122 | } 123 | } 124 | } 125 | } 126 | 127 | impl_for_tuples!(impl_contains); 128 | -------------------------------------------------------------------------------- /src/executor/parallel/dispatching.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use parking_lot::Mutex; 3 | use rayon::prelude::*; 4 | use std::{collections::HashMap, sync::Arc}; 5 | 6 | use super::SystemClosure; 7 | use crate::{ResourceTuple, SystemContext, SystemId}; 8 | 9 | /// Parallel executor variant, used when all systems are proven to be statically disjoint, 10 | /// and have no dependencies. 11 | pub struct Dispatcher<'closures, Resources> 12 | where 13 | Resources: ResourceTuple, 14 | { 15 | pub systems: HashMap>>>, 16 | } 17 | 18 | impl<'closures, Resources> Dispatcher<'closures, Resources> 19 | where 20 | Resources: ResourceTuple, 21 | { 22 | pub fn run(&mut self, world: &World, wrapped: Resources::Wrapped) { 23 | // All systems are statically disjoint, so they can all be running together at all times. 24 | self.systems.par_iter().for_each(|(id, system)| { 25 | let system = &mut *system 26 | .try_lock() // TODO should this be .lock() instead? 27 | .expect("systems should only be ran once per execution"); 28 | system( 29 | SystemContext { 30 | system_id: Some(*id), 31 | world, 32 | }, 33 | &wrapped, 34 | ); 35 | }); 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::super::ExecutorParallel; 42 | use crate::{ 43 | resource::{AtomicBorrow, ResourceWrap}, 44 | Executor, QueryMarker, 45 | }; 46 | use hecs::World; 47 | 48 | struct A(usize); 49 | struct B(usize); 50 | struct C(usize); 51 | 52 | #[test] 53 | fn trivial() { 54 | ExecutorParallel::<()>::build( 55 | Executor::builder() 56 | .system(|_, _: (), _: ()| {}) 57 | .system(|_, _: (), _: ()| {}), 58 | ) 59 | .unwrap_to_dispatcher(); 60 | } 61 | 62 | #[test] 63 | fn trivial_with_resources() { 64 | ExecutorParallel::<(A, B, C)>::build( 65 | Executor::builder() 66 | .system(|_, _: (), _: ()| {}) 67 | .system(|_, _: (), _: ()| {}), 68 | ) 69 | .unwrap_to_dispatcher(); 70 | } 71 | 72 | #[test] 73 | fn resources_disjoint() { 74 | let world = World::new(); 75 | let mut a = A(0); 76 | let mut b = B(1); 77 | let mut c = C(2); 78 | let mut executor = ExecutorParallel::<(A, B, C)>::build( 79 | Executor::builder() 80 | .system(|_, (a, c): (&mut A, &C), _: ()| { 81 | a.0 += c.0; 82 | }) 83 | .system(|_, (b, c): (&mut B, &C), _: ()| { 84 | b.0 += c.0; 85 | }), 86 | ) 87 | .unwrap_to_dispatcher(); 88 | let mut borrows = ( 89 | AtomicBorrow::new(), 90 | AtomicBorrow::new(), 91 | AtomicBorrow::new(), 92 | ); 93 | let wrapped = (&mut a, &mut b, &mut c).wrap(&mut borrows); 94 | executor.run(&world, wrapped); 95 | assert_eq!(a.0, 2); 96 | assert_eq!(b.0, 3); 97 | } 98 | 99 | #[test] 100 | fn components_disjoint() { 101 | let mut world = World::new(); 102 | world.spawn_batch((0..10).map(|_| (A(0), B(0), C(0)))); 103 | let mut a = A(1); 104 | let mut executor = ExecutorParallel::<(A,)>::build( 105 | Executor::builder() 106 | .system(|ctx, a: &A, q: QueryMarker<(&A, &mut B)>| { 107 | for (_, (_, b)) in ctx.query(q).iter() { 108 | b.0 += a.0; 109 | } 110 | }) 111 | .system(|ctx, a: &A, q: QueryMarker<(&A, &mut C)>| { 112 | for (_, (_, c)) in ctx.query(q).iter() { 113 | c.0 += a.0; 114 | } 115 | }), 116 | ) 117 | .unwrap_to_dispatcher(); 118 | let mut borrow = (AtomicBorrow::new(),); 119 | let wrapped = (&mut a).wrap(&mut borrow); 120 | executor.run(&world, wrapped); 121 | for (_, (b, c)) in world.query::<(&B, &C)>().iter() { 122 | assert_eq!(b.0, 1); 123 | assert_eq!(c.0, 1); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/batch.rs: -------------------------------------------------------------------------------- 1 | use hecs::{Entity, Fetch, Query, QueryBorrow}; 2 | 3 | #[cfg_attr(not(feature = "parallel"), allow(unused_variables))] 4 | /// Distributes over a `rayon` thread pool the work of applying a function to items in a query. 5 | /// See [`hecs::QueryBorrow::iter_batched()`](../hecs/struct.QueryBorrow.html#method.iter_batched). 6 | /// 7 | /// If the default `parallel` feature is disabled the functionality is identical 8 | /// to `query_borrow.iter().for_each(for_each)`. 9 | /// 10 | /// Calling `batch()` standalone will use the global `rayon` thread pool: 11 | /// ```rust 12 | /// # struct Pos; 13 | /// # struct Vel; 14 | /// # impl std::ops::AddAssign<&Vel> for Pos { 15 | /// # fn add_assign(&mut self, _: &Vel) {} 16 | /// # } 17 | /// # let world = hecs::World::new(); 18 | /// # let num_entities = 64; 19 | /// yaks::batch( 20 | /// &mut world.query::<(&mut Pos, &Vel)>(), 21 | /// num_entities / 16, 22 | /// |_entity, (pos, vel)| { 23 | /// *pos += vel; 24 | /// }, 25 | /// ); 26 | /// ``` 27 | /// Alternatively, a specific thread pool can be used via 28 | /// [`rayon::ThreadPool::install()`](../rayon/struct.ThreadPool.html#method.install): 29 | /// ```rust 30 | /// # struct Pos; 31 | /// # struct Vel; 32 | /// # impl std::ops::AddAssign<&Vel> for Pos { 33 | /// # fn add_assign(&mut self, _: &Vel) {} 34 | /// # } 35 | /// # let world = hecs::World::new(); 36 | /// # let num_entities = 64; 37 | /// # #[cfg(feature = "parallel")] 38 | /// # let thread_pool = 39 | /// # { 40 | /// # rayon::ThreadPoolBuilder::new().build().unwrap() 41 | /// # }; 42 | /// # #[cfg(not(feature = "parallel"))] 43 | /// # let thread_pool = 44 | /// # { 45 | /// # struct DummyPool; 46 | /// # impl DummyPool { 47 | /// # fn install(&self, mut closure: impl FnMut()) { 48 | /// # closure(); 49 | /// # } 50 | /// # } 51 | /// # DummyPool 52 | /// # }; 53 | /// thread_pool.install(|| { 54 | /// yaks::batch( 55 | /// &mut world.query::<(&mut Pos, &Vel)>(), 56 | /// num_entities / 16, 57 | /// |_entity, (pos, vel)| { 58 | /// *pos += vel; 59 | /// }, 60 | /// ) 61 | /// }); 62 | /// ``` 63 | /// `batch()` can be called in systems, where it will use whichever thread pool is used by 64 | /// the system or the executor it's in: 65 | /// ```rust 66 | /// # use yaks::{QueryMarker, Executor}; 67 | /// # struct Pos; 68 | /// # struct Vel; 69 | /// # impl std::ops::AddAssign<&Vel> for Pos { 70 | /// # fn add_assign(&mut self, _: &Vel) {} 71 | /// # } 72 | /// # let world = hecs::World::new(); 73 | /// # let mut num_entities = 64; 74 | /// # #[cfg(feature = "parallel")] 75 | /// # let thread_pool = 76 | /// # { 77 | /// # rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap() 78 | /// # }; 79 | /// # #[cfg(not(feature = "parallel"))] 80 | /// # let thread_pool = 81 | /// # { 82 | /// # struct DummyPool; 83 | /// # impl DummyPool { 84 | /// # fn install(&self, mut closure: impl FnMut()) { 85 | /// # closure(); 86 | /// # } 87 | /// # } 88 | /// # DummyPool 89 | /// # }; 90 | /// let mut executor = Executor::<(u32, )>::builder() 91 | /// .system(|context, num_entities: &u32, query: QueryMarker<(&mut Pos, &Vel)>| { 92 | /// yaks::batch( 93 | /// &mut context.query(query), 94 | /// num_entities / 16, 95 | /// |_entity, (pos, vel)| { 96 | /// *pos += vel; 97 | /// }, 98 | /// ) 99 | /// }) 100 | /// .build(); 101 | /// 102 | /// executor.run(&world, &mut num_entities); 103 | /// 104 | /// thread_pool.install(|| { 105 | /// executor.run(&world, &mut num_entities); 106 | /// }); 107 | /// ``` 108 | pub fn batch<'query, 'world, Q, F>( 109 | query_borrow: &'query mut QueryBorrow<'world, Q>, 110 | batch_size: u32, 111 | for_each: F, 112 | ) where 113 | Q: Query + Send + Sync + 'query, 114 | F: Fn(Entity, <::Fetch as Fetch<'query>>::Item) + Send + Sync, 115 | { 116 | #[cfg(feature = "parallel")] 117 | { 118 | use rayon::prelude::{ParallelBridge, ParallelIterator}; 119 | query_borrow 120 | .iter_batched(batch_size) 121 | .par_bridge() 122 | .for_each(|batch| batch.for_each(|(entity, components)| for_each(entity, components))); 123 | } 124 | #[cfg(not(feature = "parallel"))] 125 | { 126 | query_borrow 127 | .iter() 128 | .for_each(|(entity, components)| for_each(entity, components)); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![Latest Version]][crates.io] 2 | //! [![Documentation]][docs.rs] 3 | //! [![License]][license link] 4 | //! [![CI]][CI link] 5 | //! 6 | //! [Latest Version]: https://img.shields.io/crates/v/yaks.svg 7 | //! [crates.io]: https://crates.io/crates/yaks 8 | //! [Documentation]: https://docs.rs/yaks/badge.svg 9 | //! [docs.rs]: https://docs.rs/yaks 10 | //! [License]: https://img.shields.io/crates/l/yaks.svg 11 | //! [license link]: https://github.com/Ratysz/yaks/blob/master/LICENSE.md 12 | //! [CI]: https://github.com/Ratysz/yaks/workflows/CI/badge.svg?branch=master 13 | //! [CI link]: https://github.com/Ratysz/yaks/actions?query=workflow%3ACI 14 | //! 15 | //! `yaks` aims to be a minimalistic and performant framework for automatic 16 | //! multithreading of [`hecs`] via [`rayon`]. 17 | //! 18 | //! The goals are, in no particular order: 19 | //! - safety 20 | //! - simplicity 21 | //! - performance 22 | //! - extensibility 23 | //! - tight engineering 24 | //! - minimal dependencies 25 | //! - effortless concurrency 26 | //! 27 | //! [`hecs`]: https://crates.io/crates/hecs 28 | //! [`rayon`]: https://crates.io/crates/rayon 29 | //! 30 | //! # Cargo features 31 | //! 32 | //! - `parallel` - enabled by default; can be disabled to force `yaks` to work on a single thread. 33 | //! Useful for writing the code once, and running it on platforms with or without threading. 34 | //! - `resources-interop` - when enabled, allows `Executor::run()` to also 35 | //! accept `Resources` struct from the [`resources`] crate in place of resources argument. 36 | //! 37 | //! [`resources`]: https://crates.io/crates/resources 38 | //! 39 | //! # Example 40 | //! 41 | //! A more elaborate and annotated example can be found 42 | //! [here](https://github.com/Ratysz/yaks/blob/0.1.0/examples/convoluted.rs). 43 | //! 44 | //! ```rust 45 | //! use hecs::{With, Without, World}; 46 | //! use yaks::{Executor, QueryMarker}; 47 | //! 48 | //! let mut world = World::new(); 49 | //! let mut entities = 0u32; 50 | //! world.spawn_batch((0..100u32).map(|index| { 51 | //! entities += 1; 52 | //! (index,) 53 | //! })); 54 | //! world.spawn_batch((0..100u32).map(|index| { 55 | //! entities += 1; 56 | //! (index, index as f32) 57 | //! })); 58 | //! let mut increment = 5usize; 59 | //! let mut average = 0f32; 60 | //! let mut executor = Executor::<(u32, usize, f32)>::builder() 61 | //! .system_with_handle( 62 | //! |context, (entities, average): (&u32, &mut f32), query: QueryMarker<&f32>| { 63 | //! *average = 0.0; 64 | //! for (_entity, float) in context.query(query).iter() { 65 | //! *average += *float; 66 | //! } 67 | //! *average /= *entities as f32; 68 | //! }, 69 | //! "average", 70 | //! ) 71 | //! .system_with_handle( 72 | //! |context, increment: &usize, query: QueryMarker<&mut u32>| { 73 | //! for (_entity, unsigned) in context.query(query).iter() { 74 | //! *unsigned += *increment as u32 75 | //! } 76 | //! }, 77 | //! "increment", 78 | //! ) 79 | //! .system_with_deps(system_with_two_queries, vec!["increment", "average"]) 80 | //! .build(); 81 | //! executor.run(&world, (&mut entities, &mut increment, &mut average)); 82 | //! 83 | //! fn system_with_two_queries( 84 | //! context: yaks::SystemContext, 85 | //! (entities, average): (&u32, &f32), 86 | //! (with_f32, without_f32): ( 87 | //! QueryMarker>, 88 | //! QueryMarker>, 89 | //! ), 90 | //! ) { 91 | //! yaks::batch( 92 | //! &mut context.query(with_f32), 93 | //! entities / 8, 94 | //! |_entity, unsigned| { 95 | //! *unsigned += average.round() as u32; 96 | //! }, 97 | //! ); 98 | //! yaks::batch( 99 | //! &mut context.query(without_f32), 100 | //! entities / 8, 101 | //! |_entity, unsigned| { 102 | //! *unsigned *= average.round() as u32; 103 | //! }, 104 | //! ); 105 | //! } 106 | //! ``` 107 | 108 | #![warn(missing_docs)] 109 | 110 | #[macro_use] 111 | mod tuple_macro; 112 | 113 | #[cfg(feature = "parallel")] 114 | mod access_set; 115 | mod batch; 116 | mod executor; 117 | mod query_bundle; 118 | mod query_marker; 119 | mod resource; 120 | #[cfg(feature = "resources-interop")] 121 | mod resources_interop; 122 | mod run; 123 | mod system_context; 124 | 125 | #[cfg(feature = "parallel")] 126 | use access_set::{ArchetypeSet, BorrowSet, BorrowTypeSet, TypeSet}; 127 | use executor::SystemId; 128 | use query_bundle::QueryBundle; 129 | use resource::{Fetch, RefExtractor, ResourceTuple}; 130 | 131 | pub use batch::batch; 132 | pub use executor::{Executor, ExecutorBuilder}; 133 | pub use query_marker::QueryMarker; 134 | pub use run::System; 135 | pub use system_context::SystemContext; 136 | -------------------------------------------------------------------------------- /src/system_context.rs: -------------------------------------------------------------------------------- 1 | use hecs::{ 2 | Archetype, ArchetypesGeneration, Entity, NoSuchEntity, Query, QueryBorrow, QueryOne, World, 3 | }; 4 | 5 | use crate::{QueryMarker, SystemId}; 6 | 7 | /// Thin wrapper over [`hecs::World`](../hecs/struct.World.html), can prepare queries using a 8 | /// [`QueryMarker`](struct.QueryMarker.html). 9 | /// 10 | /// It cannot be instantiated directly. See [`System`](trait.System.html) for instructions 11 | /// on how to call systems outside of an executor, as plain functions. 12 | pub struct SystemContext<'scope> { 13 | pub(crate) system_id: Option, 14 | pub(crate) world: &'scope World, 15 | } 16 | 17 | impl<'scope> SystemContext<'scope> { 18 | /// Returns a debug-printable `SystemId` if the system is ran in an 19 | /// [`Executor`](struct.Executor.html), with printed number reflecting 20 | /// the order of insertion into the [`ExecutorBuilder`](struct.ExecutorBuilder.html). 21 | pub fn id(&self) -> Option { 22 | self.system_id 23 | } 24 | 25 | /// Prepares a query using the given [`QueryMarker`](struct.QueryMarker.html); 26 | /// see [`hecs::World::query()`](../hecs/struct.World.html#method.query). 27 | /// 28 | /// # Example 29 | /// ```rust 30 | /// # use yaks::{SystemContext, QueryMarker}; 31 | /// # struct Pos; 32 | /// # #[derive(Clone, Copy)] 33 | /// # struct Vel; 34 | /// # impl std::ops::AddAssign for Pos { 35 | /// # fn add_assign(&mut self, _: Vel) {} 36 | /// # } 37 | /// # let world = hecs::World::new(); 38 | /// fn some_system( 39 | /// context: SystemContext, 40 | /// _resources: (), 41 | /// query: QueryMarker<(&mut Pos, &Vel)> 42 | /// ) { 43 | /// for (_entity, (pos, vel)) in context.query(query).iter() { 44 | /// *pos += *vel; 45 | /// } 46 | /// }; 47 | /// ``` 48 | pub fn query(&self, _: QueryMarker) -> QueryBorrow<'_, Q> 49 | where 50 | Q: Query + Send + Sync, 51 | { 52 | self.world.query() 53 | } 54 | 55 | /// Prepares a query against a single entity using the given 56 | /// [`QueryMarker`](struct.QueryMarker.html); 57 | /// see [`hecs::World::query_one()`](../hecs/struct.World.html#method.query_one). 58 | /// 59 | /// # Example 60 | /// ```rust 61 | /// # use yaks::{SystemContext, QueryMarker}; 62 | /// # #[derive(Default)] 63 | /// # struct Pos; 64 | /// # #[derive(Clone, Copy, Default, Ord, PartialOrd, Eq, PartialEq)] 65 | /// # struct Vel; 66 | /// # impl std::ops::AddAssign for Pos { 67 | /// # fn add_assign(&mut self, _: Vel) {} 68 | /// # } 69 | /// # let world = hecs::World::new(); 70 | /// fn some_system( 71 | /// context: SystemContext, 72 | /// _resources: (), 73 | /// query: QueryMarker<(&mut Pos, &Vel)> 74 | /// ) { 75 | /// let mut max_velocity = Vel::default(); 76 | /// let mut max_velocity_entity = None; 77 | /// for (entity, (pos, vel)) in context.query(query).iter() { 78 | /// *pos += *vel; 79 | /// if *vel > max_velocity { 80 | /// max_velocity = *vel; 81 | /// max_velocity_entity = Some(entity); 82 | /// } 83 | /// } 84 | /// if let Some(entity) = max_velocity_entity { 85 | /// let mut query_one = context 86 | /// .query_one(query, entity) 87 | /// .expect("no such entity"); 88 | /// let (pos, _vel) = query_one 89 | /// .get() 90 | /// .expect("some components are missing"); 91 | /// *pos = Pos::default(); 92 | /// } 93 | /// }; 94 | /// ``` 95 | pub fn query_one( 96 | &self, 97 | _: QueryMarker, 98 | entity: Entity, 99 | ) -> Result, NoSuchEntity> 100 | where 101 | Q: Query + Send + Sync, 102 | { 103 | self.world.query_one(entity) 104 | } 105 | 106 | /// See [`hecs::World::reserve_entity()`](../hecs/struct.World.html#method.reserve_entity). 107 | pub fn reserve_entity(&self) -> Entity { 108 | self.world.reserve_entity() 109 | } 110 | 111 | /// See [`hecs::World::contains()`](../hecs/struct.World.html#method.contains). 112 | pub fn contains(&self, entity: Entity) -> bool { 113 | self.world.contains(entity) 114 | } 115 | 116 | /// See [`hecs::World::archetypes()`](../hecs/struct.World.html#method.archetypes). 117 | pub fn archetypes(&self) -> impl ExactSizeIterator + '_ { 118 | self.world.archetypes() 119 | } 120 | 121 | /// See [`hecs::World::archetypes_generation()`][ag]. 122 | /// 123 | /// [ag]: ../hecs/struct.World.html#method.archetypes_generation 124 | pub fn archetypes_generation(&self) -> ArchetypesGeneration { 125 | self.world.archetypes_generation() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/executor.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use yaks::{Executor, QueryMarker}; 3 | 4 | struct A(usize); 5 | 6 | struct B(usize); 7 | 8 | struct C(usize); 9 | 10 | #[test] 11 | fn systems_single() { 12 | let world = World::new(); 13 | let mut a = A(0); 14 | let mut b = B(1); 15 | let mut c = C(2); 16 | let mut executor = Executor::<(A, B, C)>::builder() 17 | .system(|_, (a, b, c): (&mut A, &B, &C), _: ()| { 18 | a.0 = b.0 + c.0; 19 | }) 20 | .build(); 21 | executor.run(&world, (&mut a, &mut b, &mut c)); 22 | assert_eq!(a.0, 3); 23 | } 24 | 25 | #[test] 26 | fn systems_two() { 27 | let world = World::new(); 28 | let mut a = A(0); 29 | let mut b = B(1); 30 | let mut c = C(2); 31 | let mut executor = Executor::<(A, B, C)>::builder() 32 | .system(|_, (a, b): (&mut A, &B), _: ()| { 33 | a.0 += b.0; 34 | }) 35 | .system(|_, (a, c): (&mut A, &C), _: ()| { 36 | a.0 += c.0; 37 | }) 38 | .build(); 39 | executor.run(&world, (&mut a, &mut b, &mut c)); 40 | assert_eq!(a.0, 3); 41 | } 42 | 43 | #[test] 44 | fn resources_decoding_single() { 45 | let world = World::new(); 46 | let mut a = A(0); 47 | let mut b = B(1); 48 | let mut c = C(2); 49 | let mut executor = Executor::<(A, B, C)>::builder() 50 | .system(|_, a: &mut A, _: ()| { 51 | a.0 = 1; 52 | }) 53 | .build(); 54 | executor.run(&world, (&mut a, &mut b, &mut c)); 55 | assert_eq!(a.0, 1); 56 | } 57 | 58 | #[test] 59 | fn resources_wrap_single() { 60 | let world = World::new(); 61 | let mut a = A(0); 62 | let mut executor = Executor::<(A,)>::builder() 63 | .system(|_, a: &mut A, _: ()| { 64 | a.0 = 1; 65 | }) 66 | .build(); 67 | executor.run(&world, (&mut a,)); 68 | assert_eq!(a.0, 1); 69 | let mut executor = Executor::<(A,)>::builder() 70 | .system(|_, a: &mut A, _: ()| { 71 | a.0 = 2; 72 | }) 73 | .build(); 74 | executor.run(&world, &mut a); 75 | assert_eq!(a.0, 2); 76 | } 77 | 78 | #[test] 79 | fn queries_decoding_single() { 80 | let mut world = World::new(); 81 | world.spawn((B(1),)); 82 | world.spawn((B(2),)); 83 | let mut a = A(0); 84 | let mut executor = Executor::<(A,)>::builder() 85 | .system(|context, a: &mut A, query: QueryMarker<&B>| { 86 | for (_, b) in context.query(query).iter() { 87 | a.0 += b.0; 88 | } 89 | }) 90 | .build(); 91 | executor.run(&world, &mut a); 92 | assert_eq!(a.0, 3); 93 | } 94 | 95 | #[test] 96 | #[allow(clippy::type_complexity)] 97 | fn queries_decoding_four() { 98 | let mut world = World::new(); 99 | world.spawn((B(1),)); 100 | world.spawn((B(1),)); 101 | world.spawn((A(0), B(1))); 102 | world.spawn((A(0),)); 103 | world.spawn((C(2),)); 104 | world.spawn((B(1), C(2))); 105 | let mut a = A(0); 106 | let mut executor = Executor::<(A,)>::builder() 107 | .system( 108 | |context, 109 | a: &mut A, 110 | (q0, q1, q2, q3): ( 111 | QueryMarker<&B>, 112 | QueryMarker<(&A, &B)>, 113 | QueryMarker<&C>, 114 | QueryMarker<(&B, &C)>, 115 | )| { 116 | for (_, b) in context.query(q0).iter() { 117 | a.0 += b.0; 118 | } 119 | assert_eq!(a.0, 4); 120 | a.0 = 0; 121 | for (_, (_, b)) in context.query(q1).iter() { 122 | a.0 += b.0; 123 | } 124 | assert_eq!(a.0, 1); 125 | a.0 = 0; 126 | for (_, c) in context.query(q2).iter() { 127 | a.0 += c.0; 128 | } 129 | assert_eq!(a.0, 4); 130 | a.0 = 0; 131 | for (_, (b, c)) in context.query(q3).iter() { 132 | a.0 += b.0 + c.0; 133 | } 134 | assert_eq!(a.0, 3); 135 | }, 136 | ) 137 | .build(); 138 | executor.run(&world, &mut a); 139 | } 140 | 141 | #[test] 142 | #[should_panic(expected = "cannot borrow executor::A immutably: already borrowed mutably")] 143 | fn invalid_resources_mutable_immutable() { 144 | let world = World::new(); 145 | let mut a = A(0); 146 | let mut b = B(1); 147 | let mut c = C(2); 148 | let mut executor = Executor::<(A, B, C)>::builder() 149 | .system(|_, _: (&mut A, &A), _: ()| {}) 150 | .build(); 151 | executor.run(&world, (&mut a, &mut b, &mut c)); 152 | } 153 | 154 | #[test] 155 | #[should_panic(expected = "cannot borrow executor::A mutably: already borrowed")] 156 | fn invalid_resources_immutable_mutable() { 157 | let world = World::new(); 158 | let mut a = A(0); 159 | let mut b = B(1); 160 | let mut c = C(2); 161 | let mut executor = Executor::<(A, B, C)>::builder() 162 | .system(|_, _: (&A, &mut A), _: ()| {}) 163 | .build(); 164 | executor.run(&world, (&mut a, &mut b, &mut c)); 165 | } 166 | 167 | #[test] 168 | #[should_panic(expected = "cannot borrow executor::A mutably: already borrowed")] 169 | fn invalid_resources_mutable_mutable() { 170 | let world = World::new(); 171 | let mut a = A(0); 172 | let mut b = B(1); 173 | let mut c = C(2); 174 | let mut executor = Executor::<(A, B, C)>::builder() 175 | .system(|_, _: (&mut A, &mut A), _: ()| {}) 176 | .build(); 177 | executor.run(&world, (&mut a, &mut b, &mut c)); 178 | } 179 | -------------------------------------------------------------------------------- /src/resources_interop.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use resources::{Ref, RefMut, Resource, Resources}; 3 | 4 | use crate::{Executor, QueryBundle, RefExtractor, System, SystemContext}; 5 | 6 | // TODO sprinkle this in doc examples 7 | 8 | impl RefExtractor<&Resources> for () { 9 | fn extract_and_run(executor: &mut Executor, world: &World, _: &Resources) { 10 | executor.run(world, ()); 11 | } 12 | } 13 | 14 | impl RefExtractor<&Resources> for (R0,) 15 | where 16 | R0: Resource, 17 | { 18 | fn extract_and_run(executor: &mut Executor, world: &World, resources: &Resources) { 19 | let mut refs = resources 20 | .fetch::<&mut R0>() 21 | .unwrap_or_else(|error| panic!("{}", error)); 22 | let derefs = (&mut *refs,); 23 | executor.run(world, derefs); 24 | } 25 | } 26 | 27 | macro_rules! impl_ref_extractor { 28 | ($($letter:ident),*) => { 29 | impl<'a, $($letter),*> RefExtractor<&Resources> for ($($letter,)*) 30 | where 31 | $($letter: Resource,)* 32 | { 33 | #[allow(non_snake_case)] 34 | fn extract_and_run( 35 | executor: &mut Executor, 36 | world: &World, 37 | resources: &Resources, 38 | ) { 39 | let ($(mut $letter,)*) = resources 40 | .fetch::<($(&mut $letter, )*)>() 41 | .unwrap_or_else(|error| panic!("{}", error)); 42 | let derefs = ($(&mut *$letter,)*); 43 | executor.run(world, derefs); 44 | } 45 | } 46 | } 47 | } 48 | 49 | impl_for_tuples!(impl_ref_extractor); 50 | 51 | pub trait Fetch<'a> { 52 | type Wrapped; 53 | 54 | fn fetch(resources: &'a Resources) -> Self::Wrapped; 55 | 56 | fn deref(wrapped: &mut Self::Wrapped) -> Self; 57 | } 58 | 59 | impl<'a, R0> Fetch<'a> for &'_ R0 60 | where 61 | R0: Resource, 62 | { 63 | type Wrapped = Ref<'a, R0>; 64 | 65 | fn fetch(resources: &'a Resources) -> Self::Wrapped { 66 | resources.get().unwrap_or_else(|error| panic!("{}", error)) 67 | } 68 | 69 | fn deref(wrapped: &mut Self::Wrapped) -> Self { 70 | unsafe { std::mem::transmute(&**wrapped) } 71 | } 72 | } 73 | 74 | impl<'a, R0> Fetch<'a> for &'_ mut R0 75 | where 76 | R0: Resource, 77 | { 78 | type Wrapped = RefMut<'a, R0>; 79 | 80 | fn fetch(resources: &'a Resources) -> Self::Wrapped { 81 | resources 82 | .get_mut() 83 | .unwrap_or_else(|error| panic!("{}", error)) 84 | } 85 | 86 | fn deref(wrapped: &mut Self::Wrapped) -> Self { 87 | unsafe { std::mem::transmute(&mut **wrapped) } 88 | } 89 | } 90 | 91 | impl<'a, 'closure, Closure, Queries> System<'closure, (), Queries, &'a Resources, Resources> 92 | for Closure 93 | where 94 | Closure: FnMut(SystemContext, (), Queries) + 'closure, 95 | Closure: System<'closure, (), Queries, (), ()>, 96 | Queries: QueryBundle, 97 | { 98 | fn run(&mut self, world: &World, _: &'a Resources) { 99 | self.run(world, ()); 100 | } 101 | } 102 | 103 | impl<'a, 'closure, Closure, A, Queries> System<'closure, A, Queries, &'a Resources, Resources> 104 | for Closure 105 | where 106 | Closure: FnMut(SystemContext, A, Queries) + 'closure, 107 | Closure: System<'closure, A, Queries, A, ()>, 108 | for<'r0> A: Fetch<'r0>, 109 | Queries: QueryBundle, 110 | { 111 | fn run(&mut self, world: &World, resources: &'a Resources) { 112 | let mut refs = A::fetch(resources); 113 | self.run(world, A::deref(&mut refs)); 114 | } 115 | } 116 | 117 | macro_rules! impl_system { 118 | ($($letter:ident),*) => { 119 | impl<'a, 'closure, Closure, $($letter),*, Queries> 120 | System<'closure, ($($letter),*), Queries, &'a Resources, Resources> for Closure 121 | where 122 | Closure: FnMut(SystemContext, ($($letter),*), Queries) + 'closure, 123 | Closure: System<'closure, ($($letter),*), Queries, ($($letter),*), ()>, 124 | $(for<'r> $letter: Fetch<'r>,)* 125 | Queries: QueryBundle, 126 | { 127 | #[allow(non_snake_case)] 128 | fn run(&mut self, world: &World, resources: &'a Resources) { 129 | let ($(mut $letter,)*) = ($($letter::fetch(resources),)*); 130 | self.run(world, ($($letter::deref(&mut $letter),)*)); 131 | } 132 | } 133 | } 134 | } 135 | 136 | impl_for_tuples!(impl_system); 137 | 138 | #[test] 139 | fn smoke_test() { 140 | use crate::Executor; 141 | let mut executor = Executor::<(f32, u32, u64)>::builder() 142 | .system(|_, _: (&mut f32, &u32), _: ()| {}) 143 | .system(|_, _: (&mut f32, &u64), _: ()| {}) 144 | .build(); 145 | let world = hecs::World::new(); 146 | 147 | let (mut a, mut b, mut c) = (1.0f32, 2u32, 3u64); 148 | executor.run(&world, (&mut a, &mut b, &mut c)); 149 | 150 | let mut resources = resources::Resources::new(); 151 | resources.insert(1.0f32); 152 | resources.insert(2u32); 153 | resources.insert(3u64); 154 | executor.run(&world, &resources); 155 | 156 | fn dummy_system(_: SystemContext, _: (), _: ()) {} 157 | dummy_system.run(&world, &resources); 158 | 159 | fn sum_system(_: SystemContext, (a, b): (&mut i32, &usize), _: ()) { 160 | *a += *b as i32; 161 | } 162 | resources.insert(3usize); 163 | resources.insert(1i32); 164 | sum_system.run(&world, &resources); 165 | assert_eq!(*resources.get::().unwrap(), 4); 166 | sum_system.run(&world, &resources); 167 | assert_eq!(*resources.get::().unwrap(), 7); 168 | } 169 | -------------------------------------------------------------------------------- /src/query_bundle.rs: -------------------------------------------------------------------------------- 1 | use hecs::{Component, Query, With, Without}; 2 | 3 | #[cfg(feature = "parallel")] 4 | use hecs::World; 5 | #[cfg(feature = "parallel")] 6 | use std::any::TypeId; 7 | 8 | use crate::QueryMarker; 9 | 10 | #[cfg(feature = "parallel")] 11 | use crate::{ArchetypeSet, BorrowTypeSet}; 12 | 13 | pub trait QueryExt: Query { 14 | #[cfg(feature = "parallel")] 15 | fn insert_component_types(component_type_set: &mut BorrowTypeSet); 16 | 17 | #[cfg(feature = "parallel")] 18 | fn set_archetype_bits(world: &World, archetype_set: &mut ArchetypeSet) 19 | where 20 | Self: Sized, 21 | { 22 | archetype_set.set_bits_for_query::(world); 23 | } 24 | } 25 | 26 | pub trait QueryBundle { 27 | fn markers() -> Self; 28 | 29 | #[cfg(feature = "parallel")] 30 | fn insert_component_types(component_type_set: &mut BorrowTypeSet); 31 | 32 | #[cfg(feature = "parallel")] 33 | fn set_archetype_bits(world: &World, archetype_set: &mut ArchetypeSet); 34 | } 35 | 36 | impl QueryExt for () { 37 | #[cfg(feature = "parallel")] 38 | fn insert_component_types(_: &mut BorrowTypeSet) {} 39 | } 40 | 41 | impl QueryBundle for () { 42 | fn markers() -> Self {} 43 | 44 | #[cfg(feature = "parallel")] 45 | fn insert_component_types(_: &mut BorrowTypeSet) {} 46 | 47 | #[cfg(feature = "parallel")] 48 | fn set_archetype_bits(_: &World, _: &mut ArchetypeSet) {} 49 | } 50 | 51 | impl QueryExt for &'_ C0 52 | where 53 | C0: Component, 54 | { 55 | #[cfg(feature = "parallel")] 56 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 57 | component_type_set.immutable.insert(TypeId::of::()); 58 | } 59 | } 60 | 61 | impl QueryExt for &'_ mut C0 62 | where 63 | C0: Component, 64 | { 65 | #[cfg(feature = "parallel")] 66 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 67 | component_type_set.mutable.insert(TypeId::of::()); 68 | } 69 | } 70 | 71 | impl QueryExt for Option 72 | where 73 | Q0: QueryExt, 74 | { 75 | #[cfg(feature = "parallel")] 76 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 77 | Q0::insert_component_types(component_type_set); 78 | } 79 | } 80 | 81 | impl QueryExt for With 82 | where 83 | C0: Component, 84 | Q0: QueryExt, 85 | { 86 | #[cfg(feature = "parallel")] 87 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 88 | Q0::insert_component_types(component_type_set); 89 | } 90 | } 91 | 92 | impl QueryExt for Without 93 | where 94 | C0: Component, 95 | Q0: QueryExt, 96 | { 97 | #[cfg(feature = "parallel")] 98 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 99 | Q0::insert_component_types(component_type_set); 100 | } 101 | } 102 | 103 | impl QueryBundle for QueryMarker 104 | where 105 | Q0: QueryExt, 106 | { 107 | fn markers() -> Self { 108 | QueryMarker::new() 109 | } 110 | 111 | #[cfg(feature = "parallel")] 112 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 113 | Q0::insert_component_types(component_type_set); 114 | } 115 | 116 | #[cfg(feature = "parallel")] 117 | fn set_archetype_bits(world: &World, archetype_set: &mut ArchetypeSet) { 118 | Q0::set_archetype_bits(world, archetype_set); 119 | } 120 | } 121 | 122 | impl QueryExt for (Q0,) 123 | where 124 | Q0: QueryExt, 125 | { 126 | #[cfg(feature = "parallel")] 127 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 128 | Q0::insert_component_types(component_type_set); 129 | } 130 | } 131 | 132 | impl QueryBundle for (QueryMarker,) 133 | where 134 | Q0: Query + QueryExt, 135 | { 136 | fn markers() -> Self { 137 | (QueryMarker::new(),) 138 | } 139 | 140 | #[cfg(feature = "parallel")] 141 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 142 | Q0::insert_component_types(component_type_set); 143 | } 144 | 145 | #[cfg(feature = "parallel")] 146 | fn set_archetype_bits(world: &World, archetype_set: &mut ArchetypeSet) { 147 | Q0::set_archetype_bits(world, archetype_set); 148 | } 149 | } 150 | 151 | macro_rules! impl_query_ext { 152 | ($($letter:ident),*) => { 153 | impl<$($letter),*> QueryExt for ($($letter,)*) 154 | where 155 | $($letter: QueryExt,)* 156 | { 157 | #[cfg(feature = "parallel")] 158 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 159 | $($letter::insert_component_types(component_type_set);)* 160 | } 161 | } 162 | } 163 | } 164 | 165 | impl_for_tuples!(impl_query_ext); 166 | 167 | macro_rules! impl_query_bundle { 168 | ($($letter:ident),*) => { 169 | impl<$($letter),*> QueryBundle for ($(QueryMarker<$letter>,)*) 170 | where 171 | $($letter: Query + QueryExt,)* 172 | { 173 | fn markers() -> Self { 174 | ($(QueryMarker::<$letter>::new(),)*) 175 | } 176 | 177 | #[cfg(feature = "parallel")] 178 | fn insert_component_types(component_type_set: &mut BorrowTypeSet) { 179 | $($letter::insert_component_types(component_type_set);)* 180 | } 181 | 182 | #[cfg(feature = "parallel")] 183 | fn set_archetype_bits(world: &World, archetype_set: &mut ArchetypeSet) { 184 | $($letter::set_archetype_bits(world, archetype_set);)* 185 | } 186 | } 187 | } 188 | } 189 | 190 | impl_for_tuples!(impl_query_bundle); 191 | -------------------------------------------------------------------------------- /src/executor/parallel/mod.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use parking_lot::Mutex; 3 | use std::{ 4 | collections::{HashMap, HashSet}, 5 | sync::Arc, 6 | }; 7 | 8 | use super::SystemClosure; 9 | use crate::{ArchetypeSet, BorrowSet, ExecutorBuilder, ResourceTuple, SystemId}; 10 | 11 | mod dispatching; 12 | mod scheduling; 13 | 14 | use dispatching::Dispatcher; 15 | use scheduling::{DependantsLength, Scheduler}; 16 | 17 | static DISCONNECTED: &str = "channel should not be disconnected at this point"; 18 | static INVALID_ID: &str = "system IDs should always be valid"; 19 | 20 | /// System closure and scheduling metadata container. 21 | pub struct System<'closure, Resources> 22 | where 23 | Resources: ResourceTuple, 24 | { 25 | pub closure: Arc>>, 26 | pub resource_set: BorrowSet, 27 | pub component_set: BorrowSet, 28 | pub archetype_set: ArchetypeSet, 29 | pub archetype_writer: Box, 30 | pub dependants: Vec, 31 | pub dependencies: usize, 32 | pub unsatisfied_dependencies: usize, 33 | } 34 | 35 | /// Variants of parallel executor, chosen based on properties of systems in the builder. 36 | pub enum ExecutorParallel<'closures, Resources> 37 | where 38 | Resources: ResourceTuple, 39 | { 40 | // TODO consider more granularity: 41 | // scheduler, disjoint scheduler, dispatcher (has to be disjoint either way) 42 | /// Used when all systems are proven to be statically disjoint 43 | /// and have no dependencies. 44 | Dispatching(Dispatcher<'closures, Resources>), 45 | /// Used when systems cannot be proven to be statically disjoint, 46 | /// or have dependencies. 47 | Scheduling(Scheduler<'closures, Resources>), 48 | } 49 | 50 | impl<'closures, Resources> ExecutorParallel<'closures, Resources> 51 | where 52 | Resources: ResourceTuple, 53 | { 54 | pub fn build(builder: ExecutorBuilder<'closures, Resources, Handle>) -> Self { 55 | // This will cache dependencies for later conversion into dependants. 56 | let mut all_dependencies = Vec::new(); 57 | let mut systems_without_dependencies = Vec::new(); 58 | let ExecutorBuilder { 59 | mut systems, 60 | mut all_component_types, 61 | .. 62 | } = builder; 63 | // This guarantees iteration order; TODO probably not necessary?.. 64 | let all_component_types = all_component_types.drain().collect::>(); 65 | let mut systems: HashMap> = systems 66 | .drain() 67 | .map(|(id, system)| { 68 | let dependencies = system.dependencies.len(); 69 | // Remember systems with no dependencies, these will be queued first on run. 70 | if dependencies == 0 { 71 | systems_without_dependencies.push(id); 72 | } 73 | all_dependencies.push((id, system.dependencies)); 74 | ( 75 | id, 76 | System { 77 | closure: Arc::new(Mutex::new(system.closure)), 78 | resource_set: system.resource_set, 79 | component_set: system.component_type_set.condense(&all_component_types), 80 | archetype_set: ArchetypeSet::default(), 81 | archetype_writer: system.archetype_writer, 82 | dependants: vec![], 83 | dependencies, 84 | unsatisfied_dependencies: 0, 85 | }, 86 | ) 87 | }) 88 | .collect(); 89 | // If all systems are independent, it might be possible to use dispatching heuristic. 90 | if systems.len() == systems_without_dependencies.len() { 91 | let mut tested_ids = Vec::new(); 92 | let mut all_disjoint = true; 93 | 'outer: for (id, system) in &systems { 94 | tested_ids.push(*id); 95 | for (id, other) in &systems { 96 | if !tested_ids.contains(id) 97 | && (!system.resource_set.is_compatible(&other.resource_set) 98 | || !system.component_set.is_compatible(&other.component_set)) 99 | { 100 | all_disjoint = false; 101 | break 'outer; 102 | } 103 | } 104 | } 105 | if all_disjoint { 106 | return ExecutorParallel::Dispatching(Dispatcher { 107 | systems: systems 108 | .drain() 109 | .map(|(id, system)| (id, system.closure)) 110 | .collect(), 111 | }); 112 | } 113 | } 114 | // Convert system-dependencies mapping to system-dependants mapping. 115 | for (dependant_id, mut dependencies) in all_dependencies.drain(..) { 116 | for dependee_id in dependencies.drain(..) { 117 | systems 118 | .get_mut(&dependee_id) 119 | .expect(INVALID_ID) 120 | .dependants 121 | .push(dependant_id); 122 | } 123 | } 124 | // Cache amount of dependants the system has. 125 | let mut systems_without_dependencies: Vec<_> = systems_without_dependencies 126 | .drain(..) 127 | .map(|id| { 128 | ( 129 | id, 130 | DependantsLength(systems.get(&id).expect(INVALID_ID).dependants.len()), 131 | ) 132 | }) 133 | .collect(); 134 | // Sort independent systems so that those with most dependants are queued first. 135 | systems_without_dependencies.sort_by(|(_, a), (_, b)| b.cmp(a)); 136 | // This should be guaranteed by the builder's logic anyway. 137 | debug_assert!(!systems_without_dependencies.is_empty()); 138 | let (sender, receiver) = crossbeam_channel::unbounded(); 139 | ExecutorParallel::Scheduling(Scheduler { 140 | systems, 141 | archetypes_generation: None, 142 | systems_without_dependencies, 143 | systems_to_run_now: Vec::new(), 144 | systems_running: HashSet::new(), 145 | systems_just_finished: Vec::new(), 146 | systems_to_decrement_dependencies: Vec::new(), 147 | sender, 148 | receiver, 149 | }) 150 | } 151 | 152 | pub fn force_archetype_recalculation(&mut self) { 153 | match self { 154 | ExecutorParallel::Dispatching(_) => (), 155 | ExecutorParallel::Scheduling(scheduler) => scheduler.archetypes_generation = None, 156 | } 157 | } 158 | 159 | pub fn run(&mut self, world: &World, wrapped: Resources::Wrapped) { 160 | match self { 161 | ExecutorParallel::Dispatching(dispatcher) => dispatcher.run(world, wrapped), 162 | ExecutorParallel::Scheduling(scheduler) => scheduler.run(world, wrapped), 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | fn unwrap_to_dispatcher(self) -> Dispatcher<'closures, Resources> { 168 | use ExecutorParallel::*; 169 | match self { 170 | Dispatching(dispatcher) => dispatcher, 171 | Scheduling(_) => panic!("produced executor is a scheduler"), 172 | } 173 | } 174 | 175 | #[cfg(test)] 176 | fn unwrap_to_scheduler(self) -> Scheduler<'closures, Resources> { 177 | use ExecutorParallel::*; 178 | match self { 179 | Dispatching(_) => panic!("produced executor is a dispatcher"), 180 | Scheduling(scheduler) => scheduler, 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/executor/mod.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use std::collections::HashMap; 3 | 4 | use crate::{RefExtractor, ResourceTuple, SystemContext}; 5 | 6 | mod builder; 7 | 8 | use builder::DummyHandle; 9 | 10 | pub use builder::ExecutorBuilder; 11 | 12 | #[cfg(not(feature = "parallel"))] 13 | mod sequential; 14 | 15 | #[cfg(not(feature = "parallel"))] 16 | use sequential::ExecutorSequential; 17 | 18 | #[cfg(feature = "parallel")] 19 | mod parallel; 20 | 21 | #[cfg(feature = "parallel")] 22 | use crate::TypeSet; 23 | #[cfg(feature = "parallel")] 24 | use parallel::ExecutorParallel; 25 | 26 | type SystemClosure<'closure, Cells> = dyn FnMut(SystemContext, &Cells) + Send + Sync + 'closure; 27 | 28 | #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] 29 | pub struct SystemId(pub(crate) usize); 30 | 31 | /// A sealed container for systems that may be executed in parallel. 32 | /// 33 | /// Systems can be any closure or function that return nothing and have these 3 arguments: 34 | /// - [`SystemContext`](struct.SystemContext.html), 35 | /// - any tuple (up to 16) or a single one of "resources": references or mutable references 36 | /// to `Send + Sync` values not contained in a [`hecs::World`](../hecs/struct.World.html) 37 | /// that the system will be accessing, 38 | /// - any tuple (up to 16) or a single one of [`QueryMarker`](struct.QueryMarker.html) that 39 | /// represent the queries the system will be making. 40 | /// 41 | /// Additionally, closures may mutably borrow from their environment for the lifetime `'closures` 42 | /// of the executor, but must be `Send + Sync`. If none of the systems make any borrows from the 43 | /// environment, said lifetime can simply be `'static`. 44 | /// 45 | /// The generic parameter `Resources` of the executor must be a superset tuple of all resource set 46 | /// tuples of the contained systems. Any type in `Resources` must appear no more than once, 47 | /// however, any number of systems in the executor may have either an immutable or a mutable 48 | /// reference of said type in their signature. For example: if any number of systems require 49 | /// a `&f32` or a `&mut f32`, `Resources` must contain `f32`. 50 | /// 51 | /// It's possible to define an order of execution of the systems by building up a dependency 52 | /// graph when building the executor, see [`ExecutorBuilder::system_with_handle()`][swh]. 53 | /// 54 | /// [swh]: struct.ExecutorBuilder.html#method.system_with_handle 55 | /// 56 | /// Executors are relatively costly to instantiate, and should be cached whenever possible. 57 | /// 58 | /// Executors are not intended to house any and all behavior of the program, they work best 59 | /// when treated as a sort of [`yaks::batch()`](fn.batch.html) for systems; e.g., 60 | /// make one only when the systems in it may actually benefit from being ran concurrently 61 | /// and prefer several small executors over a single large one. 62 | /// 63 | /// See [`::run()`](#method.run), crate examples, and documentation for other items in the library 64 | /// for more details and specific demos. 65 | pub struct Executor<'closures, Resources> 66 | where 67 | Resources: ResourceTuple, 68 | { 69 | pub(crate) borrows: Resources::BorrowTuple, 70 | #[cfg(feature = "parallel")] 71 | pub(crate) inner: ExecutorParallel<'closures, Resources>, 72 | #[cfg(not(feature = "parallel"))] 73 | pub(crate) inner: ExecutorSequential<'closures, Resources>, 74 | } 75 | 76 | impl<'closures, Resources> Executor<'closures, Resources> 77 | where 78 | Resources: ResourceTuple, 79 | { 80 | /// Creates a new [`ExecutorBuilder`](struct.ExecutorBuilder.html). 81 | pub fn builder() -> ExecutorBuilder<'closures, Resources> { 82 | ExecutorBuilder::<'closures, Resources, DummyHandle> { 83 | systems: HashMap::new(), 84 | handles: HashMap::with_capacity(0), 85 | #[cfg(feature = "parallel")] 86 | all_component_types: TypeSet::new(), 87 | } 88 | } 89 | 90 | pub(crate) fn build(builder: ExecutorBuilder<'closures, Resources, Handle>) -> Self { 91 | Self { 92 | borrows: Resources::instantiate_borrows(), 93 | #[cfg(feature = "parallel")] 94 | inner: ExecutorParallel::build(builder), 95 | #[cfg(not(feature = "parallel"))] 96 | inner: ExecutorSequential::build(builder), 97 | } 98 | } 99 | 100 | /// Forces the executor to forget stored [`hecs::ArchetypesGeneration`][1], see 101 | /// [`hecs::World::archetypes_generation()`][2]. 102 | /// 103 | /// **Must** be called before using the executor with a different [`hecs::World`][3] than 104 | /// it was used with earlier - not doing so may cause a panic when a query makes it's borrows. 105 | /// In all other cases, calling this function is unnecessary and detrimental to performance. 106 | /// 107 | /// [1]: ../hecs/struct.ArchetypesGeneration.html 108 | /// [2]: ../hecs/struct.World.html#method.archetypes_generation 109 | /// [3]: ../hecs/struct.World.html 110 | /// 111 | /// # Example 112 | /// ```rust 113 | /// # let mut executor = yaks::Executor::<()>::builder().build(); 114 | /// # let world_a = hecs::World::new(); 115 | /// # let world_b = hecs::World::new(); 116 | /// executor.run(&world_a, ()); 117 | /// executor.run(&world_a, ()); 118 | /// executor.force_archetype_recalculation(); 119 | /// executor.run(&world_b, ()); 120 | /// executor.run(&world_b, ()); 121 | /// executor.force_archetype_recalculation(); 122 | /// executor.run(&world_a, ()); 123 | /// ``` 124 | pub fn force_archetype_recalculation(&mut self) { 125 | self.inner.force_archetype_recalculation(); 126 | } 127 | 128 | /// Executes all of the contained systems once, running as much of them at the same time 129 | /// as their resource use, queries, and dependencies allow. 130 | /// 131 | /// The exact order of execution is not guaranteed, except for systems with defined 132 | /// dependencies (see [`ExecutorBuilder::system_with_handle()`][swh]), or if the default 133 | /// `parallel` feature is disabled (in which case the systems will be executed in order 134 | /// of their insertion into the builder). 135 | /// 136 | /// [swh]: struct.ExecutorBuilder.html#method.system_with_handle 137 | /// 138 | /// The `resources` argument when calling this function must be a tuple of exclusive references 139 | /// to values of types specified by the generic parameter `Resources` of the executor: 140 | /// ```rust 141 | /// # use yaks::Executor; 142 | /// # let world = hecs::World::new(); 143 | /// let mut executor = Executor::<(f32, u32)>::builder().build(); 144 | /// let mut some_f32 = 0f32; 145 | /// let mut some_u32 = 0u32; 146 | /// executor.run(&world, (&mut some_f32, &mut some_u32)); 147 | /// 148 | /// let mut executor = Executor::<(f32, )>::builder().build(); 149 | /// executor.run(&world, (&mut some_f32, )); 150 | /// 151 | /// // Single resource type is also special-cased for convenience. 152 | /// let mut executor = Executor::<(f32, )>::builder().build(); 153 | /// executor.run(&world, &mut some_f32); 154 | /// 155 | /// let mut executor = Executor::<()>::builder().build(); 156 | /// executor.run(&world, ()); 157 | /// ``` 158 | /// In the future, exclusivity requirement might be relaxed for resources that aren't mutated 159 | /// by any of the systems, but doing that as of writing is unfeasible. 160 | /// 161 | /// This function can be called inside a 162 | /// [`rayon::ThreadPool::install()`](../rayon/struct.ThreadPool.html#method.install) block 163 | /// to use that thread pool instead of the global one: 164 | /// ```rust 165 | /// # use yaks::Executor; 166 | /// # let world = hecs::World::new(); 167 | /// # #[cfg(feature = "parallel")] 168 | /// # let thread_pool = 169 | /// # { 170 | /// # rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap() 171 | /// # }; 172 | /// # #[cfg(not(feature = "parallel"))] 173 | /// # let thread_pool = 174 | /// # { 175 | /// # struct DummyPool; 176 | /// # impl DummyPool { 177 | /// # fn install(&self, mut closure: impl FnMut()) { 178 | /// # closure(); 179 | /// # } 180 | /// # } 181 | /// # DummyPool 182 | /// # }; 183 | /// # let mut executor = Executor::<()>::builder().build(); 184 | /// thread_pool.install(|| executor.run(&world, ())); 185 | /// ``` 186 | /// Doing so will cause all [`yaks::batch()`](fn.batch.html) calls inside systems 187 | /// to also use said thread pool. 188 | /// 189 | /// # Panics 190 | /// This function will panic if: 191 | /// - a system within the executor has resource requirements that are incompatible with itself, 192 | /// e.g. `(&mut SomeResource, &SomeResource)`. 193 | /// 194 | /// Additionally, it *may* panic if: 195 | /// - a different [`hecs::World`](../hecs/struct.World.html) is supplied than 196 | /// in a previous call, without first calling 197 | /// [`::force_archetype_recalculation()`](#method.force_archetype_recalculation). 198 | pub fn run(&mut self, world: &World, resources: RefSource) 199 | where 200 | Resources: RefExtractor, 201 | { 202 | Resources::extract_and_run(self, world, resources); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /examples/convoluted.rs: -------------------------------------------------------------------------------- 1 | //! An annotated non-trivial example. Runs with or without the `parallel` feature. 2 | 3 | use hecs::World; 4 | use rand::{rngs::StdRng, Rng, SeedableRng}; 5 | use std::time::{Duration, Instant}; 6 | use yaks::{Executor, QueryMarker, SystemContext}; 7 | 8 | // Each of the tests will be ran this many times. 9 | const ITERATIONS: u32 = 100; 10 | 11 | // A resource used to inform systems of how many entities they're working with, 12 | // without explicitly counting them each time. 13 | struct SpawnedEntities { 14 | no_acceleration: u32, 15 | with_acceleration: u32, 16 | } 17 | 18 | impl SpawnedEntities { 19 | // How many batches will a query be split into with `yaks::batch()`. 20 | const BATCH_TASKS: u32 = 32; 21 | 22 | // Determines how many entities will be in a batch. 23 | pub const fn batch_size_all(&self) -> u32 { 24 | (self.no_acceleration + self.with_acceleration) / Self::BATCH_TASKS 25 | } 26 | 27 | pub const fn batch_size_no_acceleration(&self) -> u32 { 28 | self.no_acceleration / Self::BATCH_TASKS 29 | } 30 | 31 | pub const fn batch_size_with_acceleration(&self) -> u32 { 32 | self.with_acceleration / Self::BATCH_TASKS 33 | } 34 | } 35 | 36 | // Example components and/or resources. 37 | struct Position(f32, f32); 38 | struct Velocity(f32, f32); 39 | struct Acceleration(f32, f32); 40 | struct Color(f32, f32, f32, f32); 41 | 42 | // A system that simulates 2D kinematic motion. 43 | #[allow(clippy::type_complexity)] 44 | fn motion( 45 | // Thin wrapper over `&hecs::World`. 46 | context: SystemContext, 47 | // A resource this system requires. Can be a single one, or any tuple up to 16. 48 | spawned: &SpawnedEntities, 49 | // Queries this system will execute. Can be a single one, or any tuple up to 16. 50 | (no_acceleration, with_acceleration): ( 51 | // `QueryMarker` is a zero-sized type that can be fed into methods of `SystemContext`. 52 | QueryMarker>, 53 | QueryMarker<(&mut Position, &mut Velocity, &Acceleration)>, 54 | ), 55 | ) { 56 | // A helper function that automatically spreads the batches across threads of a 57 | // `rayon::ThreadPool` - either the global one if called standalone, or a specific one 58 | // when used with a `rayon::ThreadPool::install()`. 59 | yaks::batch( 60 | &mut context.query(no_acceleration), 61 | spawned.batch_size_no_acceleration(), 62 | |_entity, (mut pos, vel)| { 63 | pos.0 += vel.0; 64 | pos.1 += vel.1; 65 | }, 66 | ); 67 | // If the default `parallel` feature is disabled this simply iterates in a single thread. 68 | yaks::batch( 69 | &mut context.query(with_acceleration), 70 | spawned.batch_size_with_acceleration(), 71 | |_entity, (mut pos, mut vel, acc)| { 72 | vel.0 += acc.0; 73 | vel.1 += acc.1; 74 | pos.0 += vel.0; 75 | pos.1 += vel.1; 76 | }, 77 | ); 78 | } 79 | 80 | // A system that tracks the highest velocity among all entities. 81 | fn find_highest_velocity( 82 | context: SystemContext, 83 | highest: &mut Velocity, 84 | query: QueryMarker<&Velocity>, 85 | ) { 86 | // This cannot be batched as is because it needs mutable access to `highest`; 87 | // however, it's possible to work around that by using channels and/or `RwLock`. 88 | for (_entity, vel) in context.query(query).iter() { 89 | if vel.0 * vel.0 + vel.1 * vel.1 > highest.0 * highest.0 + highest.1 * highest.1 { 90 | highest.0 = vel.0; 91 | highest.1 = vel.1; 92 | } 93 | } 94 | } 95 | 96 | // A system that recolors entities based on their kinematic properties. 97 | fn color( 98 | context: SystemContext, 99 | (spawned, rng): (&SpawnedEntities, &mut StdRng), 100 | query: QueryMarker<(&Position, &Velocity, &mut Color)>, 101 | ) { 102 | // Of course, it's possible to use resources mutably and still batch queries if 103 | // mutation happens outside batching. 104 | let blue = rng.gen_range(0.0, 1.0); 105 | yaks::batch( 106 | &mut context.query(query), 107 | spawned.batch_size_all(), 108 | |_entity, (pos, vel, mut col)| { 109 | col.0 = pos.0.abs() / 1000.0; 110 | col.1 = vel.1.abs() / 100.0; 111 | col.2 = blue; 112 | }, 113 | ); 114 | } 115 | 116 | // A system that tracks the average color of entities. 117 | fn find_average_color( 118 | context: SystemContext, 119 | (average_color, spawned): (&mut Color, &SpawnedEntities), 120 | query: QueryMarker<&Color>, 121 | ) { 122 | *average_color = Color(0.0, 0.0, 0.0, 0.0); 123 | for (_entity, color) in context.query(query).iter() { 124 | average_color.0 += color.0; 125 | average_color.1 += color.1; 126 | average_color.2 += color.2; 127 | average_color.3 += color.3; 128 | } 129 | let entities = (spawned.no_acceleration + spawned.with_acceleration) as f32; 130 | average_color.0 /= entities; 131 | average_color.1 /= entities; 132 | average_color.2 /= entities; 133 | average_color.3 /= entities; 134 | } 135 | 136 | fn main() { 137 | // Trying to parse a passed argument, if any. 138 | let to_spawn: u32 = std::env::args() 139 | .nth(1) 140 | .ok_or(()) 141 | .and_then(|arg| arg.parse::().map_err(|_| ())) 142 | .unwrap_or(100_000); 143 | // Initializing resources. 144 | let mut rng = StdRng::from_entropy(); 145 | let mut world = World::new(); 146 | let mut average_color = Color(0.0, 0.0, 0.0, 0.0); 147 | let mut highest_velocity = Velocity(0.0, 0.0); 148 | let mut spawned = SpawnedEntities { 149 | no_acceleration: 0, 150 | with_acceleration: 0, 151 | }; 152 | 153 | // Spawning entities. 154 | world.spawn_batch((0..(to_spawn / 2)).map(|_| { 155 | spawned.no_acceleration += 1; 156 | ( 157 | Position(rng.gen_range(-100.0, 100.0), rng.gen_range(-100.0, 100.0)), 158 | Velocity(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0)), 159 | Color(0.0, 0.0, 0.0, 1.0), 160 | ) 161 | })); 162 | assert!(spawned.no_acceleration >= SpawnedEntities::BATCH_TASKS); 163 | world.spawn_batch((0..(to_spawn / 2)).map(|_| { 164 | spawned.with_acceleration += 1; 165 | ( 166 | Position(rng.gen_range(-100.0, 100.0), rng.gen_range(-100.0, 100.0)), 167 | Velocity(rng.gen_range(-10.0, 10.0), rng.gen_range(-10.0, 10.0)), 168 | Acceleration(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0)), 169 | Color(0.0, 0.0, 0.0, 1.0), 170 | ) 171 | })); 172 | assert!(spawned.with_acceleration >= SpawnedEntities::BATCH_TASKS); 173 | println!( 174 | "spawned {} entities", 175 | spawned.no_acceleration + spawned.with_acceleration 176 | ); 177 | let world = &world; 178 | 179 | let mut iterations = 0u32; 180 | 181 | // The `Executor` is the main abstraction provided by `yaks`; it tries to execute 182 | // as much of it's systems at the same time as their borrows allow, while preserving 183 | // given order of execution, if any. 184 | // The generic parameter is the superset of resource sets of all of it's systems. 185 | let mut executor = Executor::<'_, (SpawnedEntities, StdRng, Color, Velocity)>::builder() 186 | // Handles and dependencies are optional, 187 | // can be of any type that is `Eq + Hash + Debug`, 188 | // and are discarded on `build()`. 189 | .system_with_handle(motion, "motion") 190 | // Systems can be defined by either a function or a closure 191 | // with a specific signature; see `ExecutorBuilder::system()` documentation. 192 | // The closures can also mutably borrow from their environment, 193 | // for the lifetime of the executor. 194 | // (Note, systems with no resources or queries have 195 | // no business being in an executor, this is for demonstration only.) 196 | .system(|_context, _resources: (), _queries: ()| iterations += 1) 197 | // The builder will panic if given a system with a handle it already contains, 198 | // a list of dependencies with a system it doesn't contain yet, 199 | // or a system that depends on itself. 200 | .system_with_deps(find_highest_velocity, vec!["motion"]) 201 | // Relative order of execution is guaranteed only for systems with explicit dependencies. 202 | // If the default `parallel` feature is disabled, systems are ran in order of insertion. 203 | .system_with_handle_and_deps(color, "color", vec!["motion"]) 204 | .system_with_deps(find_average_color, vec!["color"]) 205 | // Building is allocating, so executors should be cached whenever possible. 206 | .build(); 207 | 208 | print!("running {} iterations of executor... ", ITERATIONS); 209 | let mut elapsed = Duration::from_millis(0); 210 | for _ in 0..ITERATIONS { 211 | let time = Instant::now(); 212 | // Running the executor requires a tuple of exclusive references to the resources 213 | // specified in it's generic parameter. 214 | // To use a specific `rayon` thread pool rather than the global one this function 215 | // should be called within `rayon::ThreadPool::install()` (which will have 216 | // any `yaks::batch()` calls in systems also use that thread pool). 217 | executor.run( 218 | world, 219 | ( 220 | &mut spawned, 221 | &mut rng, 222 | &mut average_color, 223 | &mut highest_velocity, 224 | ), 225 | ); 226 | elapsed += time.elapsed(); 227 | } 228 | println!("average time: {:?}", elapsed / ITERATIONS); 229 | drop(executor); // Dropping the executor releases the borrow of `iterations`. 230 | assert_eq!(ITERATIONS, iterations); 231 | 232 | // The automatically implemented trait `System` allows easily calling systems 233 | // as plain functions with `::run()`. 234 | use yaks::System; 235 | print!("running {} iterations of functions... ", ITERATIONS); 236 | let mut elapsed = Duration::from_millis(0); 237 | for _ in 0..ITERATIONS { 238 | let time = Instant::now(); 239 | motion.run(world, &spawned); 240 | find_highest_velocity.run(world, &mut highest_velocity); 241 | color.run(world, (&spawned, &mut rng)); 242 | find_average_color.run(world, (&mut average_color, &spawned)); 243 | elapsed += time.elapsed(); 244 | } 245 | println!("average time: {:?}", elapsed / ITERATIONS); 246 | 247 | // The `batch()` helper function can also be used outside of systems, 248 | // since the first argument is simply a `QueryBorrow`. 249 | // Again, calling this within `rayon::ThreadPool::install()` will use that thread pool. 250 | yaks::batch( 251 | &mut world.query::<&mut Color>(), 252 | spawned.batch_size_all(), 253 | |_entity, color| { 254 | color.3 = 0.5; 255 | }, 256 | ); 257 | find_average_color.run(world, (&mut average_color, &spawned)); 258 | assert!((average_color.3 - 0.5).abs() < std::f32::EPSILON); 259 | } 260 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/executor/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Debug, hash::Hash}; 2 | 3 | #[cfg(feature = "parallel")] 4 | use hecs::World; 5 | 6 | use super::SystemClosure; 7 | use crate::{Executor, Fetch, QueryBundle, ResourceTuple, SystemContext, SystemId}; 8 | 9 | #[cfg(feature = "parallel")] 10 | use crate::{ArchetypeSet, BorrowSet, BorrowTypeSet, TypeSet}; 11 | 12 | /// Container for parsed systems and their metadata; 13 | /// destructured in concrete executors' build functions. 14 | pub struct System<'closure, Resources> 15 | where 16 | Resources: ResourceTuple + 'closure, 17 | { 18 | pub closure: Box>, 19 | pub dependencies: Vec, 20 | #[cfg(feature = "parallel")] 21 | pub resource_set: BorrowSet, 22 | #[cfg(feature = "parallel")] 23 | pub component_type_set: BorrowTypeSet, 24 | #[cfg(feature = "parallel")] 25 | pub archetype_writer: Box, 26 | } 27 | 28 | /// A builder for [`Executor`](struct.Executor.html) (and the only way of creating one). 29 | pub struct ExecutorBuilder<'closures, Resources, Handle = DummyHandle> 30 | where 31 | Resources: ResourceTuple, 32 | { 33 | pub(crate) systems: HashMap>, 34 | pub(crate) handles: HashMap, 35 | #[cfg(feature = "parallel")] 36 | pub(crate) all_component_types: TypeSet, 37 | } 38 | 39 | impl<'closures, Resources, Handle> ExecutorBuilder<'closures, Resources, Handle> 40 | where 41 | Resources: ResourceTuple, 42 | Handle: Eq + Hash, 43 | { 44 | fn box_system<'a, Closure, ResourceRefs, Queries, Markers>( 45 | mut closure: Closure, 46 | ) -> System<'closures, Resources> 47 | where 48 | Resources::Wrapped: 'a, 49 | Closure: FnMut(SystemContext<'a>, ResourceRefs, Queries) + Send + Sync + 'closures, 50 | ResourceRefs: Fetch<'a, Resources::Wrapped, Markers> + 'a, 51 | Queries: QueryBundle, 52 | { 53 | let closure = Box::new( 54 | move |context: SystemContext<'a>, resources: &'a Resources::Wrapped| { 55 | let fetched = ResourceRefs::fetch(resources); 56 | closure(context, fetched, Queries::markers()); 57 | unsafe { ResourceRefs::release(resources) }; 58 | }, 59 | ); 60 | let closure = unsafe { 61 | std::mem::transmute::< 62 | Box, 63 | Box, 64 | >(closure) 65 | }; 66 | #[cfg(feature = "parallel")] 67 | { 68 | let mut resource_set = BorrowSet::with_capacity(Resources::LENGTH); 69 | ResourceRefs::set_resource_bits(&mut resource_set); 70 | let mut component_type_set = BorrowTypeSet::new(); 71 | Queries::insert_component_types(&mut component_type_set); 72 | let archetype_writer = Box::new(|world: &World, archetype_set: &mut ArchetypeSet| { 73 | Queries::set_archetype_bits(world, archetype_set) 74 | }); 75 | System { 76 | closure, 77 | dependencies: vec![], 78 | resource_set, 79 | component_type_set, 80 | archetype_writer, 81 | } 82 | } 83 | #[cfg(not(feature = "parallel"))] 84 | System { 85 | closure, 86 | dependencies: vec![], 87 | } 88 | } 89 | 90 | /// Creates a new system from a closure or a function, and inserts it into the builder. 91 | /// 92 | /// The system-to-be must return nothing and have these 3 arguments: 93 | /// - [`SystemContext`](struct.SystemContext.html), 94 | /// - any tuple (up to 16) or a single one of "resources": references or mutable references 95 | /// to `Send + Sync` values not contained in a [`hecs::World`](../hecs/struct.World.html) 96 | /// that the system will be accessing, 97 | /// - any tuple (up to 16) or a single one of [`QueryMarker`](struct.QueryMarker.html) that 98 | /// represent the queries the system will be making. 99 | /// 100 | /// Additionally, closures may mutably borrow from their environment for the lifetime 101 | /// of the executor, but must be `Send + Sync`. 102 | /// 103 | /// All resources the system requires must correspond to a type in the executor's 104 | /// signature; e.g., if any number of systems require a `&f32` or a `&mut f32`, 105 | /// executor's generic parameter must contain `f32`. 106 | /// 107 | /// # Example 108 | /// ```rust 109 | /// # use yaks::{QueryMarker, SystemContext, Executor}; 110 | /// # let world = hecs::World::new(); 111 | /// # struct A; 112 | /// # struct B; 113 | /// # struct C; 114 | /// fn system_0( 115 | /// context: SystemContext, 116 | /// res_a: &A, 117 | /// (query_0, query_1): ( 118 | /// QueryMarker<(&B, &mut C)>, 119 | /// QueryMarker> 120 | /// ), 121 | /// ) { 122 | /// // This system may read resource of type `A`, and may prepare & execute queries 123 | /// // of `(&B, &mut C)` and `hecs::Without`. 124 | /// } 125 | /// 126 | /// fn system_1( 127 | /// context: SystemContext, 128 | /// (res_a, res_b): (&mut A, &B), 129 | /// query_0: QueryMarker<(&mut B, &mut C)>, 130 | /// ) { 131 | /// // This system may read or write resource of type `A`, may read resource of type `B`, 132 | /// // and may prepare & execute queries of `(&mut B, &mut C)`. 133 | /// } 134 | /// 135 | /// let mut increment = 0; 136 | /// // All together, systems require resources of types `A`, `B`, and `C`. 137 | /// let mut executor = Executor::<(A, B, C)>::builder() 138 | /// .system(system_0) 139 | /// .system(system_1) 140 | /// .system(|context, res_c: &C, _queries: ()| { 141 | /// // This system may read resource of type `C` and will not perform any queries. 142 | /// increment += 1; // `increment` will be borrowed by the executor. 143 | /// }) 144 | /// .build(); 145 | /// let (mut a, mut b, mut c) = (A, B, C); 146 | /// executor.run(&world, (&mut a, &mut b, &mut c)); 147 | /// executor.run(&world, (&mut a, &mut b, &mut c)); 148 | /// executor.run(&world, (&mut a, &mut b, &mut c)); 149 | /// drop(executor); // This releases the borrow of `increment`. 150 | /// assert_eq!(increment, 3); 151 | /// ``` 152 | pub fn system<'a, Closure, ResourceRefs, Queries, Markers>(mut self, closure: Closure) -> Self 153 | where 154 | Resources::Wrapped: 'a, 155 | Closure: FnMut(SystemContext<'a>, ResourceRefs, Queries) + Send + Sync + 'closures, 156 | ResourceRefs: Fetch<'a, Resources::Wrapped, Markers> + 'a, 157 | Queries: QueryBundle, 158 | { 159 | let id = SystemId(self.systems.len()); 160 | let system = Self::box_system::<'a, Closure, ResourceRefs, Queries, Markers>(closure); 161 | #[cfg(feature = "parallel")] 162 | { 163 | self.all_component_types 164 | .extend(&system.component_type_set.immutable); 165 | self.all_component_types 166 | .extend(&system.component_type_set.mutable); 167 | } 168 | self.systems.insert(id, system); 169 | self 170 | } 171 | 172 | /// Creates a new system from a closure or a function, and inserts it into 173 | /// the builder with given handle; see [`::system()`](#method.system). 174 | /// 175 | /// Handles allow defining relative order of execution between systems; 176 | /// doing that is optional. They can be of any type that is `Sized + Eq + Hash + Debug` 177 | /// and do not persist after [`::build()`](struct.ExecutorBuilder.html#method.build) - the 178 | /// resulting executor relies on lightweight opaque IDs; 179 | /// see [`SystemContext::id()`](struct.SystemContext.html#method.id). 180 | /// 181 | /// Handles must be unique, and systems with dependencies must be inserted 182 | /// into the builder after said dependencies. 183 | /// If the default `parallel` feature is disabled the systems will be executed in insertion 184 | /// order, which these rules guarantee to be a valid order. 185 | /// 186 | /// Since specifying a dependency between systems forbids them to run concurrently, this 187 | /// functionality should be used only when necessary. In fact, for executors where systems 188 | /// form a single chain of execution it is more performant to call them as functions, 189 | /// in a sequence, inside a single [`rayon::scope()`](../rayon/fn.scope.html) or 190 | /// [`rayon::ThreadPool::install()`](../rayon/struct.ThreadPool.html#method.install) block. 191 | /// 192 | /// # Examples 193 | /// These two executors are identical. 194 | /// ```rust 195 | /// # use yaks::{QueryMarker, SystemContext, Executor}; 196 | /// # let world = hecs::World::new(); 197 | /// # fn system_0(_: SystemContext, _: (), _: ()) {} 198 | /// # fn system_1(_: SystemContext, _: (), _: ()) {} 199 | /// # fn system_2(_: SystemContext, _: (), _: ()) {} 200 | /// # fn system_3(_: SystemContext, _: (), _: ()) {} 201 | /// # fn system_4(_: SystemContext, _: (), _: ()) {} 202 | /// let _ = Executor::<()>::builder() 203 | /// .system_with_handle(system_0, 0) 204 | /// .system_with_handle(system_1, 1) 205 | /// .system_with_handle_and_deps(system_2, 2, vec![0, 1]) 206 | /// .system_with_deps(system_3, vec![2]) 207 | /// .system_with_deps(system_4, vec![0]) 208 | /// .build(); 209 | /// let _ = Executor::<()>::builder() 210 | /// .system_with_handle(system_0, "system_0") 211 | /// .system_with_handle(system_1, "system_1") 212 | /// .system_with_handle_and_deps(system_2, "system_2", vec!["system_1", "system_0"]) 213 | /// .system_with_deps(system_3, vec!["system_2"]) 214 | /// .system_with_deps(system_4, vec!["system_0"]) 215 | /// .build(); 216 | /// ``` 217 | /// The order of execution (with the default `parallel` feature enabled) is: 218 | /// - systems 0 ***and*** 1, 219 | /// - system 4 as soon as 0 is finished ***and*** system 2 as soon as both 0 and 1 are finished, 220 | /// - system 3 as soon as 2 is finished. 221 | /// 222 | /// Note that system 4 may start running before system 1 has finished, and, 223 | /// if it's calculations take long enough, might finish last, after system 3. 224 | /// 225 | /// This executor will behave identically to the two above if the default `parallel` 226 | /// feature is enabled; otherwise, the execution order will be different from theirs, but 227 | /// that won't matter as long as the given dependencies truthfully reflect any 228 | /// relationships the systems may have. 229 | /// ```rust 230 | /// # use yaks::{QueryMarker, SystemContext, Executor}; 231 | /// # let world = hecs::World::new(); 232 | /// # fn system_0(_: SystemContext, _: (), _: ()) {} 233 | /// # fn system_1(_: SystemContext, _: (), _: ()) {} 234 | /// # fn system_2(_: SystemContext, _: (), _: ()) {} 235 | /// # fn system_3(_: SystemContext, _: (), _: ()) {} 236 | /// # fn system_4(_: SystemContext, _: (), _: ()) {} 237 | /// let _ = Executor::<()>::builder() 238 | /// .system_with_handle(system_1, 1) 239 | /// .system_with_handle(system_0, 0) 240 | /// .system_with_deps(system_4, vec![0]) 241 | /// .system_with_handle_and_deps(system_2, 2, vec![0, 1]) 242 | /// .system_with_deps(system_3, vec![2]) 243 | /// .build(); 244 | /// ``` 245 | /// 246 | /// # Panics 247 | /// This function will panic if: 248 | /// - a system with given handle is already present in the builder. 249 | pub fn system_with_handle<'a, Closure, ResourceRefs, Queries, Markers, NewHandle>( 250 | mut self, 251 | closure: Closure, 252 | handle: NewHandle, 253 | ) -> ExecutorBuilder<'closures, Resources, NewHandle> 254 | where 255 | Resources::Wrapped: 'a, 256 | Closure: FnMut(SystemContext<'a>, ResourceRefs, Queries) + Send + Sync + 'closures, 257 | ResourceRefs: Fetch<'a, Resources::Wrapped, Markers> + 'a, 258 | Queries: QueryBundle, 259 | NewHandle: HandleConversion + Debug, 260 | { 261 | let mut handles = NewHandle::convert_hash_map(self.handles); 262 | if handles.contains_key(&handle) { 263 | panic!("system {:?} already exists", handle); 264 | } 265 | let id = SystemId(self.systems.len()); 266 | let system = Self::box_system::<'a, Closure, ResourceRefs, Queries, Markers>(closure); 267 | #[cfg(feature = "parallel")] 268 | { 269 | self.all_component_types 270 | .extend(&system.component_type_set.immutable); 271 | self.all_component_types 272 | .extend(&system.component_type_set.mutable); 273 | } 274 | self.systems.insert(id, system); 275 | handles.insert(handle, id); 276 | ExecutorBuilder { 277 | systems: self.systems, 278 | handles, 279 | #[cfg(feature = "parallel")] 280 | all_component_types: self.all_component_types, 281 | } 282 | } 283 | 284 | /// Creates a new system from a closure or a function, and inserts it into 285 | /// the builder with given dependencies; see [`::system()`](#method.system). 286 | /// 287 | /// Given system will start running only after all systems in given list of dependencies 288 | /// have finished running. 289 | /// 290 | /// This function cannot be used unless the builder already has 291 | /// at least one system with a handle; 292 | /// see [`::system_with_handle()`](#method.system_with_handle). 293 | /// 294 | /// # Panics 295 | /// This function will panic if: 296 | /// - given list of dependencies contains a handle that 297 | /// doesn't correspond to any system in the builder. 298 | pub fn system_with_deps<'a, Closure, ResourceRefs, Queries, Markers>( 299 | mut self, 300 | closure: Closure, 301 | dependencies: Vec, 302 | ) -> Self 303 | where 304 | Resources::Wrapped: 'a, 305 | Closure: FnMut(SystemContext<'a>, ResourceRefs, Queries) + Send + Sync + 'closures, 306 | ResourceRefs: Fetch<'a, Resources::Wrapped, Markers> + 'a, 307 | Queries: QueryBundle, 308 | Handle: Eq + Hash + Debug, 309 | { 310 | let id = SystemId(self.systems.len()); 311 | let mut system = Self::box_system::<'a, Closure, ResourceRefs, Queries, Markers>(closure); 312 | #[cfg(feature = "parallel")] 313 | { 314 | self.all_component_types 315 | .extend(&system.component_type_set.immutable); 316 | self.all_component_types 317 | .extend(&system.component_type_set.mutable); 318 | } 319 | system 320 | .dependencies 321 | .extend(dependencies.iter().map(|dep_handle| { 322 | *self.handles.get(dep_handle).unwrap_or_else(|| { 323 | panic!( 324 | "could not resolve dependencies of a handle-less system: no system {:?} found", 325 | dep_handle 326 | ) 327 | }) 328 | })); 329 | self.systems.insert(id, system); 330 | self 331 | } 332 | 333 | /// Creates a new system from a closure or a function, and inserts it into 334 | /// the builder with given handle and dependencies; see [`::system()`](#method.system). 335 | /// 336 | /// Given system will start running only after all systems in given list of dependencies 337 | /// have finished running. 338 | /// 339 | /// This function cannot be used unless the builder already has 340 | /// at least one system with a handle; 341 | /// see [`::system_with_handle()`](#method.system_with_handle). 342 | /// 343 | /// # Panics 344 | /// This function will panic if: 345 | /// - a system with given handle is already present in the builder, 346 | /// - given list of dependencies contains a handle that 347 | /// doesn't correspond to any system in the builder, 348 | /// - given handle appears in given list of dependencies. 349 | pub fn system_with_handle_and_deps<'a, Closure, ResourceRefs, Queries, Markers>( 350 | mut self, 351 | closure: Closure, 352 | handle: Handle, 353 | dependencies: Vec, 354 | ) -> Self 355 | where 356 | Resources::Wrapped: 'a, 357 | Closure: FnMut(SystemContext<'a>, ResourceRefs, Queries) + Send + Sync + 'closures, 358 | ResourceRefs: Fetch<'a, Resources::Wrapped, Markers> + 'a, 359 | Queries: QueryBundle, 360 | Handle: Eq + Hash + Debug, 361 | { 362 | if self.handles.contains_key(&handle) { 363 | panic!("system {:?} already exists", handle); 364 | } 365 | if dependencies.contains(&handle) { 366 | panic!("system {:?} depends on itself", handle); 367 | } 368 | let id = SystemId(self.systems.len()); 369 | let mut system = Self::box_system::<'a, Closure, ResourceRefs, Queries, Markers>(closure); 370 | #[cfg(feature = "parallel")] 371 | { 372 | self.all_component_types 373 | .extend(&system.component_type_set.immutable); 374 | self.all_component_types 375 | .extend(&system.component_type_set.mutable); 376 | } 377 | system 378 | .dependencies 379 | .extend(dependencies.iter().map(|dep_handle| { 380 | *self.handles.get(dep_handle).unwrap_or_else(|| { 381 | panic!( 382 | "could not resolve dependencies of system {:?}: no system {:?} found", 383 | handle, dep_handle 384 | ) 385 | }) 386 | })); 387 | self.systems.insert(id, system); 388 | self.handles.insert(handle, id); 389 | self 390 | } 391 | 392 | /// Consumes the builder and returns the finalized executor. 393 | pub fn build(self) -> Executor<'closures, Resources> { 394 | Executor::build(self) 395 | } 396 | } 397 | 398 | #[derive(PartialEq, Eq, Hash)] 399 | pub struct DummyHandle; 400 | 401 | pub trait HandleConversion: Sized + Eq + Hash { 402 | fn convert_hash_map(map: HashMap) -> HashMap; 403 | } 404 | 405 | impl HandleConversion for T 406 | where 407 | T: Debug + Eq + Hash, 408 | { 409 | fn convert_hash_map(_: HashMap) -> HashMap { 410 | HashMap::new() 411 | } 412 | } 413 | 414 | impl HandleConversion for T 415 | where 416 | T: Debug + Eq + Hash, 417 | { 418 | fn convert_hash_map(map: HashMap) -> HashMap { 419 | map 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /src/executor/parallel/scheduling.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{Receiver, Sender}; 2 | use hecs::{ArchetypesGeneration, World}; 3 | use rayon::ScopeFifo; 4 | use std::collections::{HashMap, HashSet}; 5 | 6 | use super::{System, DISCONNECTED, INVALID_ID}; 7 | use crate::{ResourceTuple, SystemContext, SystemId}; 8 | 9 | /// Typed `usize` used to cache the amount of dependants the system associated 10 | /// with a `SystemId` has; avoids hashmap lookups while sorting. 11 | #[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] 12 | pub struct DependantsLength(pub usize); 13 | 14 | /// Parallel executor variant, used when systems cannot be proven to be statically disjoint, 15 | /// or have dependencies. 16 | pub struct Scheduler<'closures, Resources> 17 | where 18 | Resources: ResourceTuple, 19 | { 20 | pub systems: HashMap>, 21 | pub archetypes_generation: Option, 22 | pub systems_without_dependencies: Vec<(SystemId, DependantsLength)>, 23 | pub systems_to_run_now: Vec<(SystemId, DependantsLength)>, 24 | pub systems_running: HashSet, 25 | pub systems_just_finished: Vec, 26 | pub systems_to_decrement_dependencies: Vec, 27 | pub sender: Sender, 28 | pub receiver: Receiver, 29 | } 30 | 31 | impl<'closures, Resources> Scheduler<'closures, Resources> 32 | where 33 | Resources: ResourceTuple, 34 | { 35 | pub fn run(&mut self, world: &World, wrapped: Resources::Wrapped) { 36 | rayon::scope_fifo(|scope| { 37 | self.prepare(world); 38 | // All systems have been ran if there are no queued or currently running systems. 39 | while !(self.systems_to_run_now.is_empty() && self.systems_running.is_empty()) { 40 | self.start_all_currently_runnable(scope, world, &wrapped); 41 | self.wait_for_and_process_finished(); 42 | } 43 | }); 44 | debug_assert!(self.systems_to_run_now.is_empty()); 45 | debug_assert!(self.systems_running.is_empty()); 46 | debug_assert!(self.systems_just_finished.is_empty()); 47 | debug_assert!(self.systems_to_decrement_dependencies.is_empty()); 48 | } 49 | 50 | fn prepare(&mut self, world: &World) { 51 | debug_assert!(self.systems_to_run_now.is_empty()); 52 | debug_assert!(self.systems_running.is_empty()); 53 | debug_assert!(self.systems_just_finished.is_empty()); 54 | debug_assert!(self.systems_to_decrement_dependencies.is_empty()); 55 | // Queue systems that don't have any dependencies to run first. 56 | self.systems_to_run_now 57 | .extend(&self.systems_without_dependencies); 58 | if Some(world.archetypes_generation()) == self.archetypes_generation { 59 | // If archetypes haven't changed since last run, reset dependency counters. 60 | for system in self.systems.values_mut() { 61 | debug_assert!(system.unsatisfied_dependencies == 0); 62 | system.unsatisfied_dependencies = system.dependencies; 63 | } 64 | } else { 65 | // If archetypes have changed, recalculate archetype sets for all systems, 66 | // and reset dependency counters. 67 | self.archetypes_generation = Some(world.archetypes_generation()); 68 | for system in self.systems.values_mut() { 69 | (system.archetype_writer)(world, &mut system.archetype_set); 70 | debug_assert!(system.unsatisfied_dependencies == 0); 71 | system.unsatisfied_dependencies = system.dependencies; 72 | } 73 | } 74 | } 75 | 76 | fn start_all_currently_runnable<'run>( 77 | &mut self, 78 | scope: &ScopeFifo<'run>, 79 | world: &'run World, 80 | wrapped: &'run Resources::Wrapped, 81 | ) where 82 | 'closures: 'run, 83 | Resources::BorrowTuple: Send, 84 | Resources::Wrapped: Send + Sync, 85 | { 86 | for (id, _) in &self.systems_to_run_now { 87 | // Check if a queued system can run concurrently with 88 | // other systems already running. 89 | if self.can_start_now(*id) { 90 | // Add it to the currently running systems set. 91 | self.systems_running.insert(*id); 92 | // Pointers and data to send over to a worker thread. 93 | let system = self.systems.get_mut(id).expect(INVALID_ID).closure.clone(); 94 | let sender = self.sender.clone(); 95 | let id = *id; 96 | scope.spawn_fifo(move |_| { 97 | let system = &mut *system 98 | .try_lock() // TODO should this be .lock() instead? 99 | .expect("systems should only be ran once per execution"); 100 | system( 101 | SystemContext { 102 | system_id: Some(id), 103 | world, 104 | }, 105 | wrapped, 106 | ); 107 | // Notify dispatching thread than this system has finished running. 108 | sender.send(id).expect(DISCONNECTED); 109 | }); 110 | } 111 | } 112 | { 113 | // Remove newly running systems from systems-to-run-now set. 114 | // TODO replace with `.drain_filter()` once stable 115 | // https://github.com/rust-lang/rust/issues/43244 116 | let mut i = 0; 117 | while i != self.systems_to_run_now.len() { 118 | if self.systems_running.contains(&self.systems_to_run_now[i].0) { 119 | self.systems_to_run_now.remove(i); 120 | } else { 121 | i += 1; 122 | } 123 | } 124 | } 125 | } 126 | 127 | fn can_start_now(&self, id: SystemId) -> bool { 128 | let system = self.systems.get(&id).expect(INVALID_ID); 129 | for id in &self.systems_running { 130 | let running_system = self.systems.get(id).expect(INVALID_ID); 131 | // A system can't run if the resources it needs are already borrowed incompatibly. 132 | if !system 133 | .resource_set 134 | .is_compatible(&running_system.resource_set) 135 | { 136 | return false; 137 | } 138 | // A system can't run if it could borrow incompatibly any components. 139 | // This can only happen if the system could incompatibly access the same components 140 | // from the same archetype that another system may be using. 141 | if !system 142 | .component_set 143 | .is_compatible(&running_system.component_set) 144 | && !system 145 | .archetype_set 146 | .is_compatible(&running_system.archetype_set) 147 | { 148 | return false; 149 | } 150 | } 151 | true 152 | } 153 | 154 | fn wait_for_and_process_finished(&mut self) { 155 | // Wait until at least one system is finished. 156 | self.systems_just_finished 157 | .push(self.receiver.recv().expect(DISCONNECTED)); 158 | // Handle any other systems that may have finished. 159 | self.systems_just_finished.extend(self.receiver.try_iter()); 160 | // Remove finished systems from set of running systems. 161 | for id in &self.systems_just_finished { 162 | self.systems_running.remove(id); 163 | } 164 | // Gather dependants of finished systems. 165 | for finished in &self.systems_just_finished { 166 | for dependant in &self.systems.get(finished).expect(INVALID_ID).dependants { 167 | self.systems_to_decrement_dependencies.push(*dependant); 168 | } 169 | } 170 | self.systems_just_finished.clear(); 171 | // Figure out which of the gathered dependants have had all their dependencies 172 | // satisfied and queue them to run. 173 | for id in self.systems_to_decrement_dependencies.drain(..) { 174 | let system = &mut self.systems.get_mut(&id).expect(INVALID_ID); 175 | let dependants = DependantsLength(system.dependants.len()); 176 | let unsatisfied_dependencies = &mut system.unsatisfied_dependencies; 177 | *unsatisfied_dependencies -= 1; 178 | if *unsatisfied_dependencies == 0 { 179 | self.systems_to_run_now.push((id, dependants)); 180 | } 181 | } 182 | // Sort queued systems so that those with most dependants run first. 183 | self.systems_to_run_now.sort_by(|(_, a), (_, b)| b.cmp(a)); 184 | } 185 | 186 | #[cfg(test)] 187 | fn wait_for_one_finished(&mut self) { 188 | self.systems_just_finished 189 | .push(self.receiver.recv().expect(DISCONNECTED)); 190 | } 191 | } 192 | 193 | #[cfg(test)] 194 | mod tests { 195 | use super::super::ExecutorParallel; 196 | use crate::{ 197 | resource::{AtomicBorrow, ResourceWrap}, 198 | Executor, QueryMarker, SystemContext, 199 | }; 200 | use hecs::World; 201 | use rayon::{ScopeFifo, ThreadPoolBuilder}; 202 | 203 | struct A(usize); 204 | struct B(usize); 205 | struct C(usize); 206 | 207 | fn dummy_system(_: SystemContext, _: (), _: ()) {} 208 | 209 | fn local_pool_scope_fifo<'scope, F>(closure: F) 210 | where 211 | F: for<'s> FnOnce(&'s ScopeFifo<'scope>) + 'scope + Send, 212 | { 213 | ThreadPoolBuilder::new() 214 | .num_threads(2) 215 | .build() 216 | .unwrap() 217 | .scope_fifo(closure) 218 | } 219 | 220 | #[test] 221 | fn dependencies_single() { 222 | let world = World::new(); 223 | let mut executor = ExecutorParallel::<()>::build( 224 | Executor::builder() 225 | .system_with_handle(dummy_system, 0) 226 | .system_with_handle_and_deps(dummy_system, 1, vec![0]), 227 | ) 228 | .unwrap_to_scheduler(); 229 | let wrapped = (); 230 | local_pool_scope_fifo(|scope| { 231 | executor.prepare(&world); 232 | executor.start_all_currently_runnable(scope, &world, &wrapped); 233 | assert_eq!(executor.systems_running.len(), 1); 234 | executor.wait_for_and_process_finished(); 235 | assert!(executor.systems_running.is_empty()); 236 | 237 | executor.start_all_currently_runnable(scope, &world, &wrapped); 238 | assert_eq!(executor.systems_running.len(), 1); 239 | executor.wait_for_and_process_finished(); 240 | assert!(executor.systems_running.is_empty()); 241 | assert!(executor.systems_to_run_now.is_empty()); 242 | }); 243 | } 244 | 245 | #[test] 246 | fn dependencies_several() { 247 | let world = World::new(); 248 | let mut executor = ExecutorParallel::<()>::build( 249 | Executor::<()>::builder() 250 | .system_with_handle(dummy_system, 0) 251 | .system_with_handle(dummy_system, 1) 252 | .system_with_handle(dummy_system, 2) 253 | .system_with_deps(dummy_system, vec![0, 1, 2]), 254 | ) 255 | .unwrap_to_scheduler(); 256 | let wrapped = (); 257 | local_pool_scope_fifo(|scope| { 258 | executor.prepare(&world); 259 | executor.start_all_currently_runnable(scope, &world, &wrapped); 260 | assert_eq!(executor.systems_running.len(), 3); 261 | executor.wait_for_one_finished(); 262 | executor.wait_for_one_finished(); 263 | executor.wait_for_and_process_finished(); 264 | assert!(executor.systems_running.is_empty()); 265 | 266 | executor.start_all_currently_runnable(scope, &world, &wrapped); 267 | assert_eq!(executor.systems_running.len(), 1); 268 | executor.wait_for_and_process_finished(); 269 | assert!(executor.systems_running.is_empty()); 270 | assert!(executor.systems_to_run_now.is_empty()); 271 | }); 272 | } 273 | 274 | #[test] 275 | fn dependencies_chain() { 276 | let world = World::new(); 277 | let mut executor = ExecutorParallel::<()>::build( 278 | Executor::<()>::builder() 279 | .system_with_handle(dummy_system, 0) 280 | .system_with_handle_and_deps(dummy_system, 1, vec![0]) 281 | .system_with_handle_and_deps(dummy_system, 2, vec![1]) 282 | .system_with_deps(dummy_system, vec![2]), 283 | ) 284 | .unwrap_to_scheduler(); 285 | let wrapped = (); 286 | local_pool_scope_fifo(|scope| { 287 | executor.prepare(&world); 288 | executor.start_all_currently_runnable(scope, &world, &wrapped); 289 | assert_eq!(executor.systems_running.len(), 1); 290 | executor.wait_for_and_process_finished(); 291 | assert!(executor.systems_running.is_empty()); 292 | 293 | executor.start_all_currently_runnable(scope, &world, &wrapped); 294 | assert_eq!(executor.systems_running.len(), 1); 295 | executor.wait_for_and_process_finished(); 296 | assert!(executor.systems_running.is_empty()); 297 | 298 | executor.start_all_currently_runnable(scope, &world, &wrapped); 299 | assert_eq!(executor.systems_running.len(), 1); 300 | executor.wait_for_and_process_finished(); 301 | assert!(executor.systems_running.is_empty()); 302 | 303 | executor.start_all_currently_runnable(scope, &world, &wrapped); 304 | assert_eq!(executor.systems_running.len(), 1); 305 | executor.wait_for_and_process_finished(); 306 | assert!(executor.systems_running.is_empty()); 307 | assert!(executor.systems_to_run_now.is_empty()); 308 | }); 309 | } 310 | 311 | #[test] 312 | fn dependencies_fully_constrained() { 313 | let world = World::new(); 314 | let mut executor = ExecutorParallel::<()>::build( 315 | Executor::<()>::builder() 316 | .system_with_handle(dummy_system, 0) 317 | .system_with_handle_and_deps(dummy_system, 1, vec![0]) 318 | .system_with_handle_and_deps(dummy_system, 2, vec![0, 1]) 319 | .system_with_deps(dummy_system, vec![0, 1, 2]), 320 | ) 321 | .unwrap_to_scheduler(); 322 | let wrapped = (); 323 | local_pool_scope_fifo(|scope| { 324 | executor.prepare(&world); 325 | executor.start_all_currently_runnable(scope, &world, &wrapped); 326 | assert_eq!(executor.systems_running.len(), 1); 327 | executor.wait_for_and_process_finished(); 328 | assert!(executor.systems_running.is_empty()); 329 | 330 | executor.start_all_currently_runnable(scope, &world, &wrapped); 331 | assert_eq!(executor.systems_running.len(), 1); 332 | executor.wait_for_and_process_finished(); 333 | assert!(executor.systems_running.is_empty()); 334 | 335 | executor.start_all_currently_runnable(scope, &world, &wrapped); 336 | assert_eq!(executor.systems_running.len(), 1); 337 | executor.wait_for_and_process_finished(); 338 | assert!(executor.systems_running.is_empty()); 339 | 340 | executor.start_all_currently_runnable(scope, &world, &wrapped); 341 | assert_eq!(executor.systems_running.len(), 1); 342 | executor.wait_for_and_process_finished(); 343 | assert!(executor.systems_running.is_empty()); 344 | assert!(executor.systems_to_run_now.is_empty()); 345 | }); 346 | } 347 | 348 | #[test] 349 | fn resources_incompatible_mutable_immutable() { 350 | let world = World::new(); 351 | let mut executor = ExecutorParallel::<(A,)>::build( 352 | Executor::builder() 353 | .system(|_, _: &A, _: ()| {}) 354 | .system(|_, a: &mut A, _: ()| a.0 += 1), 355 | ) 356 | .unwrap_to_scheduler(); 357 | let mut a = A(0); 358 | let mut a = &mut a; 359 | let mut borrows = (AtomicBorrow::new(),); 360 | let wrapped = a.wrap(&mut borrows); 361 | local_pool_scope_fifo(|scope| { 362 | executor.prepare(&world); 363 | executor.start_all_currently_runnable(scope, &world, &wrapped); 364 | assert_eq!(executor.systems_running.len(), 1); 365 | executor.wait_for_and_process_finished(); 366 | assert!(executor.systems_running.is_empty()); 367 | 368 | executor.start_all_currently_runnable(scope, &world, &wrapped); 369 | assert_eq!(executor.systems_running.len(), 1); 370 | executor.wait_for_and_process_finished(); 371 | assert!(executor.systems_running.is_empty()); 372 | assert!(executor.systems_to_run_now.is_empty()); 373 | }); 374 | assert_eq!(a.0, 1); 375 | } 376 | 377 | #[test] 378 | fn resources_incompatible_mutable_mutable() { 379 | let world = World::new(); 380 | let mut executor = ExecutorParallel::<(A,)>::build( 381 | Executor::builder() 382 | .system(|_, a: &mut A, _: ()| a.0 += 1) 383 | .system(|_, a: &mut A, _: ()| a.0 += 1), 384 | ) 385 | .unwrap_to_scheduler(); 386 | let mut a = A(0); 387 | let mut a = &mut a; 388 | let mut borrows = (AtomicBorrow::new(),); 389 | let wrapped = a.wrap(&mut borrows); 390 | local_pool_scope_fifo(|scope| { 391 | executor.prepare(&world); 392 | executor.start_all_currently_runnable(scope, &world, &wrapped); 393 | assert_eq!(executor.systems_running.len(), 1); 394 | executor.wait_for_and_process_finished(); 395 | assert!(executor.systems_running.is_empty()); 396 | 397 | executor.start_all_currently_runnable(scope, &world, &wrapped); 398 | assert_eq!(executor.systems_running.len(), 1); 399 | executor.wait_for_and_process_finished(); 400 | assert!(executor.systems_running.is_empty()); 401 | assert!(executor.systems_to_run_now.is_empty()); 402 | }); 403 | assert_eq!(a.0, 2); 404 | } 405 | 406 | #[test] 407 | fn queries_incompatible_mutable_immutable() { 408 | let mut world = World::new(); 409 | world.spawn_batch((0..10).map(|_| (B(0),))); 410 | let mut executor = ExecutorParallel::<(A,)>::build( 411 | Executor::builder() 412 | .system(|ctx, _: (), q: QueryMarker<&B>| for (_, _) in ctx.query(q).iter() {}) 413 | .system(|ctx, a: &A, q: QueryMarker<&mut B>| { 414 | for (_, b) in ctx.query(q).iter() { 415 | b.0 += a.0; 416 | } 417 | }), 418 | ) 419 | .unwrap_to_scheduler(); 420 | let mut a = A(1); 421 | let mut a = &mut a; 422 | let mut borrows = (AtomicBorrow::new(),); 423 | let wrapped = a.wrap(&mut borrows); 424 | local_pool_scope_fifo(|scope| { 425 | executor.prepare(&world); 426 | executor.start_all_currently_runnable(scope, &world, &wrapped); 427 | assert_eq!(executor.systems_running.len(), 1); 428 | executor.wait_for_and_process_finished(); 429 | assert!(executor.systems_running.is_empty()); 430 | 431 | executor.start_all_currently_runnable(scope, &world, &wrapped); 432 | assert_eq!(executor.systems_running.len(), 1); 433 | executor.wait_for_and_process_finished(); 434 | assert!(executor.systems_running.is_empty()); 435 | assert!(executor.systems_to_run_now.is_empty()); 436 | }); 437 | for (_, b) in world.query::<&B>().iter() { 438 | assert_eq!(b.0, 1); 439 | } 440 | } 441 | 442 | #[test] 443 | fn queries_incompatible_mutable_mutable() { 444 | let mut world = World::new(); 445 | world.spawn_batch((0..10).map(|_| (B(0),))); 446 | let mut executor = ExecutorParallel::<(A,)>::build( 447 | Executor::builder() 448 | .system(|ctx, a: &A, q: QueryMarker<&mut B>| { 449 | for (_, b) in ctx.query(q).iter() { 450 | b.0 += a.0; 451 | } 452 | }) 453 | .system(|ctx, a: &A, q: QueryMarker<&mut B>| { 454 | for (_, b) in ctx.query(q).iter() { 455 | b.0 += a.0; 456 | } 457 | }), 458 | ) 459 | .unwrap_to_scheduler(); 460 | let mut a = A(1); 461 | let mut a = &mut a; 462 | let mut borrows = (AtomicBorrow::new(),); 463 | let wrapped = a.wrap(&mut borrows); 464 | local_pool_scope_fifo(|scope| { 465 | executor.prepare(&world); 466 | executor.start_all_currently_runnable(scope, &world, &wrapped); 467 | assert_eq!(executor.systems_running.len(), 1); 468 | executor.wait_for_and_process_finished(); 469 | assert!(executor.systems_running.is_empty()); 470 | 471 | executor.start_all_currently_runnable(scope, &world, &wrapped); 472 | assert_eq!(executor.systems_running.len(), 1); 473 | executor.wait_for_and_process_finished(); 474 | assert!(executor.systems_running.is_empty()); 475 | assert!(executor.systems_to_run_now.is_empty()); 476 | }); 477 | for (_, b) in world.query::<&B>().iter() { 478 | assert_eq!(b.0, 2); 479 | } 480 | } 481 | 482 | #[test] 483 | fn queries_disjoint_by_archetypes() { 484 | let mut world = World::new(); 485 | world.spawn_batch((0..10).map(|_| (A(0), B(0)))); 486 | world.spawn_batch((0..10).map(|_| (B(0), C(0)))); 487 | let mut executor = ExecutorParallel::<(A,)>::build( 488 | Executor::builder() 489 | .system(|ctx, a: &A, q: QueryMarker<(&A, &mut B)>| { 490 | for (_, (_, b)) in ctx.query(q).iter() { 491 | b.0 += a.0; 492 | } 493 | }) 494 | .system(|ctx, a: &A, q: QueryMarker<(&mut B, &C)>| { 495 | for (_, (b, _)) in ctx.query(q).iter() { 496 | b.0 += a.0; 497 | } 498 | }), 499 | ) 500 | .unwrap_to_scheduler(); 501 | let mut a = A(2); 502 | let mut a = &mut a; 503 | let mut borrows = (AtomicBorrow::new(),); 504 | let wrapped = a.wrap(&mut borrows); 505 | local_pool_scope_fifo(|scope| { 506 | executor.prepare(&world); 507 | executor.start_all_currently_runnable(scope, &world, &wrapped); 508 | assert_eq!(executor.systems_running.len(), 2); 509 | executor.wait_for_one_finished(); 510 | executor.wait_for_and_process_finished(); 511 | assert!(executor.systems_running.is_empty()); 512 | assert!(executor.systems_to_run_now.is_empty()); 513 | }); 514 | for (_, b) in world.query::<&B>().iter() { 515 | assert_eq!(b.0, 2); 516 | } 517 | 518 | /*let mut entities: Vec<_> = world 519 | .spawn_batch((0..10).map(|_| (A(0), B(1), C(0)))) 520 | .collect();*/ 521 | world.spawn_batch((0..10).map(|_| (A(0), B(1), C(0)))); 522 | let mut a = A(1); 523 | let mut a = &mut a; 524 | let wrapped = a.wrap(&mut borrows); 525 | local_pool_scope_fifo(|scope| { 526 | executor.prepare(&world); 527 | executor.start_all_currently_runnable(scope, &world, &wrapped); 528 | assert_eq!(executor.systems_running.len(), 1); 529 | executor.wait_for_and_process_finished(); 530 | assert!(executor.systems_running.is_empty()); 531 | 532 | executor.start_all_currently_runnable(scope, &world, &wrapped); 533 | assert_eq!(executor.systems_running.len(), 1); 534 | executor.wait_for_and_process_finished(); 535 | assert!(executor.systems_running.is_empty()); 536 | assert!(executor.systems_to_run_now.is_empty()); 537 | }); 538 | for (_, b) in world.query::<&B>().iter() { 539 | assert_eq!(b.0, 3); 540 | } 541 | 542 | /*entities 543 | .drain(..) 544 | .for_each(|entity| world.despawn(entity).unwrap()); 545 | rayon::scope(|scope| { 546 | executor.prepare(&world); 547 | executor.start_all_currently_runnable(scope, &world, &wrapped); 548 | // TODO this fails. Suggest upstream changes? 549 | assert_eq!(executor.systems_running.len(), 2); 550 | executor.wait_for_one_finished(); 551 | executor.wait_for_and_process_finished(); 552 | assert!(executor.systems_running.is_empty()); 553 | assert!(executor.systems_to_run_now.is_empty()); 554 | }); 555 | for (_, b) in world.query::<&B>().iter() { 556 | assert_eq!(b.0, 4); 557 | }*/ 558 | } 559 | } 560 | --------------------------------------------------------------------------------