| @@ -1,8 +1,10 @@ | |||||
| [workspace] | [workspace] | ||||
| members = [ | members = [ | ||||
| "async-ecs", | "async-ecs", | ||||
| "async-ecs-derive" | |||||
| "async-ecs-derive", | |||||
| "parallel-iterator" | |||||
| ] | ] | ||||
| [patch.crates-io] | [patch.crates-io] | ||||
| async-ecs-derive = { path = "async-ecs-derive" } | async-ecs-derive = { path = "async-ecs-derive" } | ||||
| parallel-iterator = { path = "parallel-iterator" } | |||||
| @@ -12,6 +12,8 @@ hashbrown = "0.9" | |||||
| hibitset = { version = "0.6", default-features = false } | hibitset = { version = "0.6", default-features = false } | ||||
| log = "0.4" | log = "0.4" | ||||
| mopa = "0.2" | mopa = "0.2" | ||||
| num_cpus = "1.13" | |||||
| parallel-iterator = "0.1" | |||||
| rand = "0.7" | rand = "0.7" | ||||
| thiserror = "1.0" | thiserror = "1.0" | ||||
| tokio = { version = "0.3", features = [ "full", "net", "time", "rt-multi-thread" ] } | tokio = { version = "0.3", features = [ "full", "net", "time", "rt-multi-thread" ] } | ||||
| @@ -9,7 +9,7 @@ use crate::{ | |||||
| resource::{Ref, RefMut, Resource}, | resource::{Ref, RefMut, Resource}, | ||||
| }; | }; | ||||
| use super::{read::Read, write::Write}; | |||||
| use super::{read::Read, write::Write, ParJoin}; | |||||
| pub trait Join { | pub trait Join { | ||||
| type Type; | type Type; | ||||
| @@ -69,6 +69,8 @@ where | |||||
| } | } | ||||
| } | } | ||||
| impl<T> ParJoin for MaybeJoin<T> where T: ParJoin {} | |||||
| pub struct JoinIter<J: Join> { | pub struct JoinIter<J: Join> { | ||||
| keys: BitIter<J::Mask>, | keys: BitIter<J::Mask>, | ||||
| values: J::Value, | values: J::Value, | ||||
| @@ -157,6 +159,11 @@ macro_rules! define_tuple_join { | |||||
| unconstrained | unconstrained | ||||
| } | } | ||||
| } | } | ||||
| impl<$($from,)*> ParJoin for ($($from),*,) | |||||
| where $($from: ParJoin),*, | |||||
| ($(<$from as Join>::Mask,)*): BitAnd, | |||||
| {} | |||||
| } | } | ||||
| } | } | ||||
| @@ -201,6 +208,13 @@ macro_rules! define_mutable_join { | |||||
| <&'a mut T as Join>::is_unconstrained() | <&'a mut T as Join>::is_unconstrained() | ||||
| } | } | ||||
| } | } | ||||
| impl<'a, 'b, T> ParJoin for &'a mut $ty | |||||
| where | |||||
| &'a mut T: ParJoin, | |||||
| T: Resource, | |||||
| { | |||||
| } | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -231,6 +245,13 @@ macro_rules! define_immutable_join { | |||||
| <&'a T as Join>::is_unconstrained() | <&'a T as Join>::is_unconstrained() | ||||
| } | } | ||||
| } | } | ||||
| impl<'a, 'b, T> ParJoin for &'a $ty | |||||
| where | |||||
| &'a T: ParJoin, | |||||
| T: Resource, | |||||
| { | |||||
| } | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| pub mod accessor; | pub mod accessor; | ||||
| pub mod join; | pub mod join; | ||||
| pub mod par_join; | |||||
| pub mod read; | pub mod read; | ||||
| pub mod read_storage; | pub mod read_storage; | ||||
| pub mod write; | pub mod write; | ||||
| @@ -7,6 +8,7 @@ pub mod write_storage; | |||||
| pub use accessor::{Accessor, AccessorCow, AccessorType, StaticAccessor}; | pub use accessor::{Accessor, AccessorCow, AccessorType, StaticAccessor}; | ||||
| pub use join::Join; | pub use join::Join; | ||||
| pub use par_join::{JoinParIter, ParJoin}; | |||||
| pub use read::Read; | pub use read::Read; | ||||
| pub use read_storage::ReadStorage; | pub use read_storage::ReadStorage; | ||||
| pub use write::Write; | pub use write::Write; | ||||
| @@ -0,0 +1,113 @@ | |||||
| use std::cell::UnsafeCell; | |||||
| use hibitset::BitSetLike; | |||||
| use parallel_iterator::{Consumer, Executor, Folder, ParallelIterator, Producer}; | |||||
| use crate::misc::{BitIter, BitProducer, TokioExecutor}; | |||||
| use super::Join; | |||||
| pub trait ParJoin: Join { | |||||
| fn par_join(self) -> JoinParIter<Self> | |||||
| where | |||||
| Self: Sized, | |||||
| { | |||||
| if <Self as Join>::is_unconstrained() { | |||||
| log::warn!( | |||||
| "`ParJoin` possibly iterating through all indices, you might've made a join with all `MaybeJoin`s, which is unbounded in length." | |||||
| ); | |||||
| } | |||||
| JoinParIter(self) | |||||
| } | |||||
| } | |||||
| pub struct JoinParIter<J>(J); | |||||
| impl<J> ParallelIterator for JoinParIter<J> | |||||
| where | |||||
| J: Join + Send, | |||||
| J::Type: Send, | |||||
| J::Mask: Send + Sync, | |||||
| J::Value: Send, | |||||
| { | |||||
| type Item = J::Type; | |||||
| fn drive<C>(self, consumer: C) -> C::Result | |||||
| where | |||||
| C: Consumer<Self::Item>, | |||||
| { | |||||
| let (keys, values) = self.0.open(); | |||||
| let values = UnsafeCell::new(values); | |||||
| let keys = BitIter::new(&keys, [0, 0, 0, keys.layer3()], [0; 3]); | |||||
| let producer = BitProducer::new(keys, 3); | |||||
| let producer = JoinProducer::<J>::new(producer, &values); | |||||
| TokioExecutor.exec(producer, consumer) | |||||
| } | |||||
| } | |||||
| struct JoinProducer<'a, J> | |||||
| where | |||||
| J: Join + Send, | |||||
| J::Type: Send, | |||||
| J::Mask: Send, | |||||
| J::Value: Send, | |||||
| { | |||||
| pub(crate) keys: BitProducer<'a, J::Mask>, | |||||
| pub(crate) values: &'a UnsafeCell<J::Value>, | |||||
| } | |||||
| impl<'a, J> JoinProducer<'a, J> | |||||
| where | |||||
| J: Join + Send, | |||||
| J::Type: Send, | |||||
| J::Mask: Send, | |||||
| J::Value: Send, | |||||
| { | |||||
| fn new(keys: BitProducer<'a, J::Mask>, values: &'a UnsafeCell<J::Value>) -> Self { | |||||
| JoinProducer { keys, values } | |||||
| } | |||||
| } | |||||
| unsafe impl<'a, J> Send for JoinProducer<'a, J> | |||||
| where | |||||
| J: Join + Send, | |||||
| J::Type: Send, | |||||
| J::Value: Send + 'a, | |||||
| J::Mask: Send + Sync + 'a, | |||||
| { | |||||
| } | |||||
| impl<'a, J> Producer for JoinProducer<'a, J> | |||||
| where | |||||
| J: Join + Send, | |||||
| J::Type: Send, | |||||
| J::Value: Send + 'a, | |||||
| J::Mask: Send + Sync + 'a, | |||||
| { | |||||
| type Item = J::Type; | |||||
| fn split(self) -> (Self, Option<Self>) { | |||||
| let (cur, other) = self.keys.split(); | |||||
| let values = self.values; | |||||
| let first = JoinProducer::new(cur, values); | |||||
| let second = other.map(|o| JoinProducer::new(o, values)); | |||||
| (first, second) | |||||
| } | |||||
| fn fold_with<F>(self, folder: F) -> F | |||||
| where | |||||
| F: Folder<Self::Item>, | |||||
| { | |||||
| let JoinProducer { values, keys, .. } = self; | |||||
| let iter = keys | |||||
| .iter | |||||
| .map(|idx| unsafe { J::get(&mut *values.get(), idx) }); | |||||
| folder.consume_iter(iter) | |||||
| } | |||||
| } | |||||
| @@ -11,7 +11,9 @@ pub mod storage; | |||||
| pub mod system; | pub mod system; | ||||
| pub mod world; | pub mod world; | ||||
| pub use access::{Join, ReadStorage, WriteStorage}; | |||||
| pub use parallel_iterator::ParallelIterator; | |||||
| pub use access::{Join, ParJoin, ReadStorage, WriteStorage}; | |||||
| pub use dispatcher::Dispatcher; | pub use dispatcher::Dispatcher; | ||||
| pub use resource::Resources; | pub use resource::Resources; | ||||
| pub use storage::VecStorage; | pub use storage::VecStorage; | ||||
| @@ -2,8 +2,8 @@ use std::io::Error as IoError; | |||||
| use std::time::{Duration, Instant}; | use std::time::{Duration, Instant}; | ||||
| use async_ecs::{ | use async_ecs::{ | ||||
| dispatcher::Error as DispatcherError, AsyncSystem, Dispatcher, Join, ReadStorage, System, | |||||
| VecStorage, World, WriteStorage, | |||||
| dispatcher::Error as DispatcherError, AsyncSystem, Dispatcher, Join, ParJoin, ParallelIterator, | |||||
| ReadStorage, System, VecStorage, World, WriteStorage, | |||||
| }; | }; | ||||
| use async_ecs_derive::Component; | use async_ecs_derive::Component; | ||||
| use log::info; | use log::info; | ||||
| @@ -17,14 +17,17 @@ fn main() -> Result<(), Error> { | |||||
| .format_timestamp_nanos() | .format_timestamp_nanos() | ||||
| .init(); | .init(); | ||||
| Builder::new_multi_thread() | |||||
| let rt = Builder::new_multi_thread() | |||||
| .worker_threads(num_cpus::get() + 4) | |||||
| .enable_all() | .enable_all() | ||||
| .build()? | |||||
| .block_on(async move { LocalSet::new().run_until(run()).await }) | |||||
| .build()?; | |||||
| rt.block_on(async move { LocalSet::new().run_until(run()).await }) | |||||
| } | } | ||||
| async fn run() -> Result<(), Error> { | async fn run() -> Result<(), Error> { | ||||
| info!("Application started!"); | info!("Application started!"); | ||||
| info!(" num_cpus = {}", num_cpus::get()); | |||||
| let mut world = World::default(); | let mut world = World::default(); | ||||
| world.register_component::<Position>(); | world.register_component::<Position>(); | ||||
| @@ -43,8 +46,8 @@ async fn run() -> Result<(), Error> { | |||||
| info!("World initialized!"); | info!("World initialized!"); | ||||
| let mut dispatcher = Dispatcher::builder() | let mut dispatcher = Dispatcher::builder() | ||||
| .with(Move, "move", &[])? | |||||
| .with(Accelerate, "accelerate", &["move"])? | |||||
| .with(ParMove, "move", &[])? | |||||
| .with(ParAccelerate, "accelerate", &["move"])? | |||||
| .build(); | .build(); | ||||
| info!("Setup done!"); | info!("Setup done!"); | ||||
| @@ -72,7 +75,7 @@ async fn run() -> Result<(), Error> { | |||||
| } | } | ||||
| const ENTITY_COUNT: usize = 1_000_000; | const ENTITY_COUNT: usize = 1_000_000; | ||||
| const REPEAT_COUNT: u32 = 10; | |||||
| const REPEAT_COUNT: u32 = 100; | |||||
| #[derive(Debug, Component)] | #[derive(Debug, Component)] | ||||
| #[storage(VecStorage)] | #[storage(VecStorage)] | ||||
| @@ -95,8 +98,11 @@ struct Acceleration { | |||||
| y: f64, | y: f64, | ||||
| } | } | ||||
| struct Move; | |||||
| struct Accelerate; | |||||
| struct SeqMove; | |||||
| struct SeqAccelerate; | |||||
| struct ParMove; | |||||
| struct ParAccelerate; | |||||
| #[derive(Debug, Error)] | #[derive(Debug, Error)] | ||||
| enum Error { | enum Error { | ||||
| @@ -134,7 +140,7 @@ impl Acceleration { | |||||
| } | } | ||||
| } | } | ||||
| impl<'a> System<'a> for Move { | |||||
| impl<'a> System<'a> for SeqMove { | |||||
| type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>); | type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>); | ||||
| fn run(&mut self, (mut position, velocity): Self::SystemData) { | fn run(&mut self, (mut position, velocity): Self::SystemData) { | ||||
| @@ -142,21 +148,10 @@ impl<'a> System<'a> for Move { | |||||
| position.x += velocity.x; | position.x += velocity.x; | ||||
| position.y += velocity.y; | position.y += velocity.y; | ||||
| } | } | ||||
| /* | |||||
| use specs::{prelude::ParallelIterator, ParJoin}; | |||||
| (&mut position, &velocity) | |||||
| .par_join() | |||||
| .for_each(|(position, velocity)| { | |||||
| position.x += velocity.x; | |||||
| position.y += velocity.y; | |||||
| }); | |||||
| */ | |||||
| } | } | ||||
| } | } | ||||
| impl<'a> AsyncSystem<'a> for Accelerate { | |||||
| impl<'a> AsyncSystem<'a> for SeqAccelerate { | |||||
| type SystemData = (WriteStorage<'a, Velocity>, ReadStorage<'a, Acceleration>); | type SystemData = (WriteStorage<'a, Velocity>, ReadStorage<'a, Acceleration>); | ||||
| type Future = futures::future::Ready<()>; | type Future = futures::future::Ready<()>; | ||||
| @@ -167,17 +162,35 @@ impl<'a> AsyncSystem<'a> for Accelerate { | |||||
| } | } | ||||
| futures::future::ready(()) | futures::future::ready(()) | ||||
| } | |||||
| } | |||||
| /* | |||||
| use specs::{prelude::ParallelIterator, ParJoin}; | |||||
| impl<'a> System<'a> for ParMove { | |||||
| type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>); | |||||
| fn run(&mut self, (mut position, velocity): Self::SystemData) { | |||||
| (&mut position, &velocity) | |||||
| .par_join() | |||||
| .for_each(|(position, velocity)| { | |||||
| position.x += velocity.x; | |||||
| position.y += velocity.y; | |||||
| }); | |||||
| } | |||||
| } | |||||
| impl<'a> AsyncSystem<'a> for ParAccelerate { | |||||
| type SystemData = (WriteStorage<'a, Velocity>, ReadStorage<'a, Acceleration>); | |||||
| type Future = futures::future::Ready<()>; | |||||
| fn run(&mut self, (mut velocity, acceleration): Self::SystemData) -> Self::Future { | |||||
| (&mut velocity, &acceleration) | (&mut velocity, &acceleration) | ||||
| .par_join() | .par_join() | ||||
| .for_each(|(velocity, acceleration)| { | .for_each(|(velocity, acceleration)| { | ||||
| velocity.x += acceleration.x; | velocity.x += acceleration.x; | ||||
| velocity.y += acceleration.y; | velocity.y += acceleration.y; | ||||
| }); | }); | ||||
| */ | |||||
| futures::future::ready(()) | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,302 @@ | |||||
| pub fn bit_average(n: usize) -> Option<usize> { | |||||
| #[cfg(target_pointer_width = "64")] | |||||
| let average = bit_average_u64(n as u64).map(|n| n as usize); | |||||
| #[cfg(target_pointer_width = "32")] | |||||
| let average = bit_average_u32(n as u32).map(|n| n as usize); | |||||
| average | |||||
| } | |||||
| #[allow(clippy::many_single_char_names)] | |||||
| #[cfg(any(test, target_pointer_width = "32"))] | |||||
| fn bit_average_u32(n: u32) -> Option<u32> { | |||||
| const PAR: [u32; 5] = [!0 / 0x3, !0 / 0x5, !0 / 0x11, !0 / 0x101, !0 / 0x10001]; | |||||
| let a = n - ((n >> 1) & PAR[0]); | |||||
| let b = (a & PAR[1]) + ((a >> 2) & PAR[1]); | |||||
| let c = (b + (b >> 4)) & PAR[2]; | |||||
| let d = (c + (c >> 8)) & PAR[3]; | |||||
| let mut cur = d >> 16; | |||||
| let count = (d + cur) & PAR[4]; | |||||
| if count <= 1 { | |||||
| return None; | |||||
| } | |||||
| let mut target = count / 2; | |||||
| let mut result = 32; | |||||
| { | |||||
| let mut descend = |child, child_stride, child_mask| { | |||||
| if cur < target { | |||||
| result -= 2 * child_stride; | |||||
| target -= cur; | |||||
| } | |||||
| cur = (child >> (result - child_stride)) & child_mask; | |||||
| }; | |||||
| descend(c, 8, 16 - 1); // PAR[3] | |||||
| descend(b, 4, 8 - 1); // PAR[2] | |||||
| descend(a, 2, 4 - 1); // PAR[1] | |||||
| descend(n, 1, 2 - 1); // PAR[0] | |||||
| } | |||||
| if cur < target { | |||||
| result -= 1; | |||||
| } | |||||
| Some(result - 1) | |||||
| } | |||||
| #[allow(clippy::many_single_char_names)] | |||||
| #[cfg(any(test, target_pointer_width = "64"))] | |||||
| fn bit_average_u64(n: u64) -> Option<u64> { | |||||
| const PAR: [u64; 6] = [ | |||||
| !0 / 0x3, | |||||
| !0 / 0x5, | |||||
| !0 / 0x11, | |||||
| !0 / 0x101, | |||||
| !0 / 0x10001, | |||||
| !0 / 0x100000001, | |||||
| ]; | |||||
| let a = n - ((n >> 1) & PAR[0]); | |||||
| let b = (a & PAR[1]) + ((a >> 2) & PAR[1]); | |||||
| let c = (b + (b >> 4)) & PAR[2]; | |||||
| let d = (c + (c >> 8)) & PAR[3]; | |||||
| let e = (d + (d >> 16)) & PAR[4]; | |||||
| let mut cur = e >> 32; | |||||
| let count = (e + cur) & PAR[5]; | |||||
| if count <= 1 { | |||||
| return None; | |||||
| } | |||||
| let mut target = count / 2; | |||||
| let mut result = 64; | |||||
| { | |||||
| let mut descend = |child, child_stride, child_mask| { | |||||
| if cur < target { | |||||
| result -= 2 * child_stride; | |||||
| target -= cur; | |||||
| } | |||||
| cur = (child >> (result - child_stride)) & child_mask; | |||||
| }; | |||||
| descend(d, 16, 256 - 1); // PAR[4] | |||||
| descend(c, 8, 16 - 1); // PAR[3] | |||||
| descend(b, 4, 8 - 1); // PAR[2] | |||||
| descend(a, 2, 4 - 1); // PAR[1] | |||||
| descend(n, 1, 2 - 1); // PAR[0] | |||||
| } | |||||
| if cur < target { | |||||
| result -= 1; | |||||
| } | |||||
| Some(result - 1) | |||||
| } | |||||
| #[cfg(test)] | |||||
| mod test_bit_average { | |||||
| use super::*; | |||||
| #[test] | |||||
| fn parity_0_bit_average_u32() { | |||||
| struct EvenParity(u32); | |||||
| impl Iterator for EvenParity { | |||||
| type Item = u32; | |||||
| fn next(&mut self) -> Option<Self::Item> { | |||||
| if self.0 == u32::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| while self.0.count_ones() & 1 != 0 { | |||||
| if self.0 == u32::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| } | |||||
| Some(self.0) | |||||
| } | |||||
| } | |||||
| let steps = 1000; | |||||
| for i in 0..steps { | |||||
| let pos = i * (u32::max_value() / steps); | |||||
| for i in EvenParity(pos).take(steps as usize) { | |||||
| let mask = (1 << bit_average_u32(i).unwrap_or(31)) - 1; | |||||
| assert_eq!((i & mask).count_ones(), (i & !mask).count_ones(), "{:x}", i); | |||||
| } | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn parity_1_bit_average_u32() { | |||||
| struct OddParity(u32); | |||||
| impl Iterator for OddParity { | |||||
| type Item = u32; | |||||
| fn next(&mut self) -> Option<Self::Item> { | |||||
| if self.0 == u32::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| while self.0.count_ones() & 1 == 0 { | |||||
| if self.0 == u32::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| } | |||||
| Some(self.0) | |||||
| } | |||||
| } | |||||
| let steps = 1000; | |||||
| for i in 0..steps { | |||||
| let pos = i * (u32::max_value() / steps); | |||||
| for i in OddParity(pos).take(steps as usize) { | |||||
| let mask = (1 << bit_average_u32(i).unwrap_or(31)) - 1; | |||||
| let a = (i & mask).count_ones(); | |||||
| let b = (i & !mask).count_ones(); | |||||
| if a < b { | |||||
| assert_eq!(a + 1, b, "{:x}", i); | |||||
| } else if b < a { | |||||
| assert_eq!(a, b + 1, "{:x}", i); | |||||
| } else { | |||||
| panic!("Odd parity shouldn't split in exactly half"); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn empty_bit_average_u32() { | |||||
| assert_eq!(None, bit_average_u32(0)); | |||||
| } | |||||
| #[test] | |||||
| fn singleton_bit_average_u32() { | |||||
| for i in 0..32 { | |||||
| assert_eq!(None, bit_average_u32(1 << i), "{:x}", i); | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn parity_0_bit_average_u64() { | |||||
| struct EvenParity(u64); | |||||
| impl Iterator for EvenParity { | |||||
| type Item = u64; | |||||
| fn next(&mut self) -> Option<Self::Item> { | |||||
| if self.0 == u64::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| while self.0.count_ones() & 1 != 0 { | |||||
| if self.0 == u64::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| } | |||||
| Some(self.0) | |||||
| } | |||||
| } | |||||
| let steps = 1000; | |||||
| for i in 0..steps { | |||||
| let pos = i * (u64::max_value() / steps); | |||||
| for i in EvenParity(pos).take(steps as usize) { | |||||
| let mask = (1 << bit_average_u64(i).unwrap_or(63)) - 1; | |||||
| assert_eq!((i & mask).count_ones(), (i & !mask).count_ones(), "{:x}", i); | |||||
| } | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn parity_1_bit_average_u64() { | |||||
| struct OddParity(u64); | |||||
| impl Iterator for OddParity { | |||||
| type Item = u64; | |||||
| fn next(&mut self) -> Option<Self::Item> { | |||||
| if self.0 == u64::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| while self.0.count_ones() & 1 == 0 { | |||||
| if self.0 == u64::max_value() { | |||||
| return None; | |||||
| } | |||||
| self.0 += 1; | |||||
| } | |||||
| Some(self.0) | |||||
| } | |||||
| } | |||||
| let steps = 1000; | |||||
| for i in 0..steps { | |||||
| let pos = i * (u64::max_value() / steps); | |||||
| for i in OddParity(pos).take(steps as usize) { | |||||
| let mask = (1 << bit_average_u64(i).unwrap_or(63)) - 1; | |||||
| let a = (i & mask).count_ones(); | |||||
| let b = (i & !mask).count_ones(); | |||||
| if a < b { | |||||
| assert_eq!(a + 1, b, "{:x}", i); | |||||
| } else if b < a { | |||||
| assert_eq!(a, b + 1, "{:x}", i); | |||||
| } else { | |||||
| panic!("Odd parity shouldn't split in exactly half"); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn empty_bit_average_u64() { | |||||
| assert_eq!(None, bit_average_u64(0)); | |||||
| } | |||||
| #[test] | |||||
| fn singleton_bit_average_u64() { | |||||
| for i in 0..64 { | |||||
| assert_eq!(None, bit_average_u64(1 << i), "{:x}", i); | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn bit_average_agree_u32_u64() { | |||||
| let steps = 1000; | |||||
| for i in 0..steps { | |||||
| let pos = i * (u32::max_value() / steps); | |||||
| for i in pos..steps { | |||||
| assert_eq!( | |||||
| bit_average_u32(i), | |||||
| bit_average_u64(i as u64).map(|n| n as u32), | |||||
| "{:x}", | |||||
| i | |||||
| ); | |||||
| } | |||||
| } | |||||
| } | |||||
| #[test] | |||||
| fn specific_values() { | |||||
| assert_eq!(Some(4), bit_average_u32(0b10110)); | |||||
| assert_eq!(Some(5), bit_average_u32(0b100010)); | |||||
| assert_eq!(None, bit_average_u32(0)); | |||||
| assert_eq!(None, bit_average_u32(1)); | |||||
| assert_eq!(Some(4), bit_average_u64(0b10110)); | |||||
| assert_eq!(Some(5), bit_average_u64(0b100010)); | |||||
| assert_eq!(None, bit_average_u64(0)); | |||||
| assert_eq!(None, bit_average_u64(1)); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| use hibitset::BitSetLike; | |||||
| #[derive(Debug, Clone)] | |||||
| pub struct BitIter<T> { | |||||
| pub(crate) set: T, | |||||
| pub(crate) masks: [usize; LAYERS], | |||||
| pub(crate) prefix: [u32; LAYERS - 1], | |||||
| } | |||||
| impl<T> BitIter<T> { | |||||
| pub fn new(set: T, masks: [usize; LAYERS], prefix: [u32; LAYERS - 1]) -> Self { | |||||
| Self { set, masks, prefix } | |||||
| } | |||||
| } | |||||
| impl<T: BitSetLike> BitIter<T> { | |||||
| pub fn contains(&self, i: u32) -> bool { | |||||
| self.set.contains(i) | |||||
| } | |||||
| } | |||||
| #[derive(PartialEq)] | |||||
| pub(crate) enum State { | |||||
| Empty, | |||||
| Continue, | |||||
| Value(u32), | |||||
| } | |||||
| impl<T> Iterator for BitIter<T> | |||||
| where | |||||
| T: BitSetLike, | |||||
| { | |||||
| type Item = u32; | |||||
| fn next(&mut self) -> Option<Self::Item> { | |||||
| use self::State::*; | |||||
| 'find: loop { | |||||
| for level in 0..LAYERS { | |||||
| match self.handle_level(level) { | |||||
| Value(v) => return Some(v), | |||||
| Continue => continue 'find, | |||||
| Empty => {} | |||||
| } | |||||
| } | |||||
| return None; | |||||
| } | |||||
| } | |||||
| } | |||||
| impl<T: BitSetLike> BitIter<T> { | |||||
| pub(crate) fn handle_level(&mut self, level: usize) -> State { | |||||
| use self::State::*; | |||||
| if self.masks[level] == 0 { | |||||
| Empty | |||||
| } else { | |||||
| let first_bit = self.masks[level].trailing_zeros(); | |||||
| self.masks[level] &= !(1 << first_bit); | |||||
| let idx = self.prefix.get(level).cloned().unwrap_or(0) | first_bit; | |||||
| if level == 0 { | |||||
| Value(idx) | |||||
| } else { | |||||
| self.masks[level - 1] = self.set.get_from_layer(level - 1, idx as usize); | |||||
| self.prefix[level - 1] = idx << BITS; | |||||
| Continue | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| const LAYERS: usize = 4; | |||||
| #[cfg(target_pointer_width = "64")] | |||||
| pub const BITS: usize = 6; | |||||
| #[cfg(target_pointer_width = "32")] | |||||
| pub const BITS: usize = 5; | |||||
| #[cfg(test)] | |||||
| mod tests { | |||||
| use hibitset::{BitSet, BitSetLike}; | |||||
| #[test] | |||||
| fn iterator_clone() { | |||||
| let mut set = BitSet::new(); | |||||
| set.add(1); | |||||
| set.add(3); | |||||
| let iter = set.iter().skip(1); | |||||
| for (a, b) in iter.clone().zip(iter) { | |||||
| assert_eq!(a, b); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,93 @@ | |||||
| use hibitset::BitSetLike; | |||||
| use parallel_iterator::{Folder, Producer}; | |||||
| use crate::misc::bit_average; | |||||
| use super::BitIter; | |||||
| pub struct BitProducer<'a, T> { | |||||
| pub(crate) iter: BitIter<&'a T>, | |||||
| pub(crate) splits: u8, | |||||
| } | |||||
| impl<'a, T> BitProducer<'a, T> | |||||
| where | |||||
| T: BitSetLike, | |||||
| { | |||||
| pub fn new(iter: BitIter<&'a T>, splits: u8) -> Self { | |||||
| Self { iter, splits } | |||||
| } | |||||
| fn handle_level(&mut self, level: usize) -> Option<Self> { | |||||
| if self.iter.masks[level] == 0 { | |||||
| None | |||||
| } else { | |||||
| let level_prefix = self.iter.prefix.get(level).cloned().unwrap_or(0); | |||||
| let first_bit = self.iter.masks[level].trailing_zeros(); | |||||
| bit_average(self.iter.masks[level]) | |||||
| .map(|average_bit| { | |||||
| let mask = (1 << average_bit) - 1; | |||||
| let mut other = BitProducer::new( | |||||
| BitIter::new(self.iter.set, [0; LAYERS], [0; LAYERS - 1]), | |||||
| self.splits, | |||||
| ); | |||||
| other.iter.masks[level] = self.iter.masks[level] & !mask; | |||||
| other.iter.prefix[level - 1] = (level_prefix | average_bit as u32) << BITS; | |||||
| other.iter.prefix[level..].copy_from_slice(&self.iter.prefix[level..]); | |||||
| self.iter.masks[level] &= mask; | |||||
| self.iter.prefix[level - 1] = (level_prefix | first_bit) << BITS; | |||||
| other | |||||
| }) | |||||
| .or_else(|| { | |||||
| let idx = level_prefix as usize | first_bit as usize; | |||||
| self.iter.prefix[level - 1] = (idx as u32) << BITS; | |||||
| self.iter.masks[level] = 0; | |||||
| self.iter.masks[level - 1] = self.iter.set.get_from_layer(level - 1, idx); | |||||
| None | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| impl<'a, T> Producer for BitProducer<'a, T> | |||||
| where | |||||
| T: BitSetLike + Send + Sync, | |||||
| { | |||||
| type Item = u32; | |||||
| fn split(mut self) -> (Self, Option<Self>) { | |||||
| let other = { | |||||
| let top_layer = LAYERS - 1; | |||||
| let mut h = self.handle_level(top_layer); | |||||
| for i in 1..self.splits { | |||||
| h = h.or_else(|| self.handle_level(top_layer - i as usize)); | |||||
| } | |||||
| h | |||||
| }; | |||||
| (self, other) | |||||
| } | |||||
| fn fold_with<F>(self, folder: F) -> F | |||||
| where | |||||
| F: Folder<Self::Item>, | |||||
| { | |||||
| folder.consume_iter(self.iter) | |||||
| } | |||||
| } | |||||
| const LAYERS: usize = 4; | |||||
| #[cfg(target_pointer_width = "64")] | |||||
| pub const BITS: usize = 6; | |||||
| #[cfg(target_pointer_width = "32")] | |||||
| pub const BITS: usize = 5; | |||||
| @@ -1,7 +1,15 @@ | |||||
| pub mod bit_and; | pub mod bit_and; | ||||
| pub mod bit_average; | |||||
| pub mod bit_iter; | |||||
| pub mod bit_producer; | |||||
| pub mod split; | pub mod split; | ||||
| pub mod tokio_executor; | |||||
| pub mod try_default; | pub mod try_default; | ||||
| pub use bit_and::BitAnd; | pub use bit_and::BitAnd; | ||||
| pub use bit_average::bit_average; | |||||
| pub use bit_iter::BitIter; | |||||
| pub use bit_producer::BitProducer; | |||||
| pub use split::Split; | pub use split::Split; | ||||
| pub use tokio_executor::TokioExecutor; | |||||
| pub use try_default::TryDefault; | pub use try_default::TryDefault; | ||||
| @@ -0,0 +1,90 @@ | |||||
| use std::mem::transmute; | |||||
| use futures::{ | |||||
| executor::block_on, | |||||
| future::{BoxFuture, Future, FutureExt}, | |||||
| join, | |||||
| }; | |||||
| use tokio::task::{block_in_place, spawn}; | |||||
| use parallel_iterator::{ | |||||
| Consumer, Executor, Folder, IndexedConsumer, IndexedProducer, Producer, Reducer, | |||||
| }; | |||||
| pub struct TokioExecutor; | |||||
| impl Executor for TokioExecutor { | |||||
| fn exec<P, C>(self, producer: P, consumer: C) -> C::Result | |||||
| where | |||||
| P: Producer, | |||||
| C: Consumer<P::Item>, | |||||
| { | |||||
| block_in_place(|| block_on(exec(2 * num_cpus::get(), producer, consumer))) | |||||
| } | |||||
| fn exec_indexed<P, C>(self, producer: P, consumer: C) -> C::Result | |||||
| where | |||||
| P: IndexedProducer, | |||||
| C: IndexedConsumer<P::Item>, | |||||
| { | |||||
| let _producer = producer; | |||||
| let _consumer = consumer; | |||||
| unimplemented!() | |||||
| } | |||||
| } | |||||
| fn exec<'a, P, C>(splits: usize, producer: P, consumer: C) -> BoxFuture<'a, C::Result> | |||||
| where | |||||
| P: Producer + 'a, | |||||
| C: Consumer<P::Item> + 'a, | |||||
| { | |||||
| async move { | |||||
| if consumer.is_full() { | |||||
| consumer.into_folder().complete() | |||||
| } else if splits > 0 { | |||||
| match producer.split() { | |||||
| (left_producer, Some(right_producer)) => { | |||||
| let ((left_consumer, reducer), right_consumer) = | |||||
| (consumer.split_off_left(), consumer); | |||||
| let left = run_as_task(exec(splits / 2, left_producer, left_consumer)); | |||||
| let right = run_as_task(exec(splits / 2, right_producer, right_consumer)); | |||||
| let (left_result, right_result) = join!(left, right); | |||||
| reducer.reduce(left_result, right_result) | |||||
| } | |||||
| (producer, None) => producer.fold_with(consumer.into_folder()).complete(), | |||||
| } | |||||
| } else { | |||||
| producer.fold_with(consumer.into_folder()).complete() | |||||
| } | |||||
| } | |||||
| .boxed() | |||||
| } | |||||
| struct Pointer<T>(*mut T); | |||||
| unsafe impl<T> Send for Pointer<T> {} | |||||
| async fn run_as_task<'a, T, F>(f: F) -> T | |||||
| where | |||||
| T: Send + 'a, | |||||
| F: Future<Output = T> + Send + 'a, | |||||
| { | |||||
| let mut result = None; | |||||
| let r = Pointer(&mut result as *mut _); | |||||
| let task: BoxFuture<'a, ()> = async move { | |||||
| unsafe { | |||||
| *r.0 = Some(f.await); | |||||
| } | |||||
| } | |||||
| .boxed(); | |||||
| let task: BoxFuture<'static, ()> = unsafe { transmute(task) }; | |||||
| spawn(task).await.expect("Error in iterator executor"); | |||||
| result.unwrap() | |||||
| } | |||||
| @@ -0,0 +1,26 @@ | |||||
| use hibitset::{BitSet, BitSetNot}; | |||||
| use crate::{ | |||||
| access::{Join, ParJoin}, | |||||
| entity::Index, | |||||
| }; | |||||
| use super::DistinctStorage; | |||||
| pub struct AntiStorage<'a>(pub &'a BitSet); | |||||
| impl<'a> DistinctStorage for AntiStorage<'a> {} | |||||
| impl<'a> Join for AntiStorage<'a> { | |||||
| type Mask = BitSetNot<&'a BitSet>; | |||||
| type Type = (); | |||||
| type Value = (); | |||||
| fn open(self) -> (Self::Mask, ()) { | |||||
| (BitSetNot(self.0), ()) | |||||
| } | |||||
| fn get(_: &mut (), _: Index) {} | |||||
| } | |||||
| impl<'a> ParJoin for AntiStorage<'a> {} | |||||
| @@ -1,7 +1,9 @@ | |||||
| mod anti_storage; | |||||
| mod masked_storage; | mod masked_storage; | ||||
| mod storage_wrapper; | mod storage_wrapper; | ||||
| mod vec_storage; | mod vec_storage; | ||||
| pub use anti_storage::AntiStorage; | |||||
| pub use masked_storage::MaskedStorage; | pub use masked_storage::MaskedStorage; | ||||
| pub use storage_wrapper::StorageWrapper; | pub use storage_wrapper::StorageWrapper; | ||||
| pub use vec_storage::VecStorage; | pub use vec_storage::VecStorage; | ||||
| @@ -13,3 +15,5 @@ pub trait Storage<T>: TryDefault { | |||||
| fn get_mut(&mut self, index: Index) -> &mut T; | fn get_mut(&mut self, index: Index) -> &mut T; | ||||
| fn insert(&mut self, index: Index, value: T); | fn insert(&mut self, index: Index, value: T); | ||||
| } | } | ||||
| pub trait DistinctStorage {} | |||||
| @@ -1,10 +1,10 @@ | |||||
| use std::marker::PhantomData; | use std::marker::PhantomData; | ||||
| use std::ops::{Deref, DerefMut, Not}; | use std::ops::{Deref, DerefMut, Not}; | ||||
| use hibitset::{BitSet, BitSetNot}; | |||||
| use hibitset::BitSet; | |||||
| use crate::{ | use crate::{ | ||||
| access::Join, | |||||
| access::{Join, ParJoin}, | |||||
| component::Component, | component::Component, | ||||
| entity::{Entities, Entity, Index}, | entity::{Entities, Entity, Index}, | ||||
| error::Error, | error::Error, | ||||
| @@ -12,7 +12,7 @@ use crate::{ | |||||
| storage::MaskedStorage, | storage::MaskedStorage, | ||||
| }; | }; | ||||
| use super::Storage; | |||||
| use super::{AntiStorage, DistinctStorage, Storage}; | |||||
| pub struct StorageWrapper<'a, T, D> { | pub struct StorageWrapper<'a, T, D> { | ||||
| data: D, | data: D, | ||||
| @@ -20,8 +20,6 @@ pub struct StorageWrapper<'a, T, D> { | |||||
| phantom: PhantomData<T>, | phantom: PhantomData<T>, | ||||
| } | } | ||||
| pub struct AntiStorage<'a>(pub &'a BitSet); | |||||
| impl<'a, T, D> StorageWrapper<'a, T, D> { | impl<'a, T, D> StorageWrapper<'a, T, D> { | ||||
| pub fn new(data: D, entities: Ref<'a, Entities>) -> Self { | pub fn new(data: D, entities: Ref<'a, Entities>) -> Self { | ||||
| Self { | Self { | ||||
| @@ -50,6 +48,11 @@ where | |||||
| } | } | ||||
| } | } | ||||
| impl<'a, T: Component, D> DistinctStorage for StorageWrapper<'a, T, D> where | |||||
| T::Storage: DistinctStorage | |||||
| { | |||||
| } | |||||
| impl<'a, 'e, T, D> Not for &'a StorageWrapper<'e, T, D> | impl<'a, 'e, T, D> Not for &'a StorageWrapper<'e, T, D> | ||||
| where | where | ||||
| T: Component, | T: Component, | ||||
| @@ -101,14 +104,18 @@ where | |||||
| } | } | ||||
| } | } | ||||
| impl<'a> Join for AntiStorage<'a> { | |||||
| type Mask = BitSetNot<&'a BitSet>; | |||||
| type Type = (); | |||||
| type Value = (); | |||||
| fn open(self) -> (Self::Mask, ()) { | |||||
| (BitSetNot(self.0), ()) | |||||
| } | |||||
| impl<'a, 'e, T, D> ParJoin for &'a StorageWrapper<'e, T, D> | |||||
| where | |||||
| T: Component, | |||||
| D: Deref<Target = MaskedStorage<T>>, | |||||
| T::Storage: Sync, | |||||
| { | |||||
| } | |||||
| fn get(_: &mut (), _: Index) {} | |||||
| impl<'a, 'e, T, D> ParJoin for &'a mut StorageWrapper<'e, T, D> | |||||
| where | |||||
| T: Component, | |||||
| D: DerefMut<Target = MaskedStorage<T>>, | |||||
| T::Storage: Sync + DistinctStorage, | |||||
| { | |||||
| } | } | ||||
| @@ -2,7 +2,7 @@ use std::mem::MaybeUninit; | |||||
| use crate::entity::Index; | use crate::entity::Index; | ||||
| use super::Storage; | |||||
| use super::{DistinctStorage, Storage}; | |||||
| pub struct VecStorage<T>(Vec<MaybeUninit<T>>); | pub struct VecStorage<T>(Vec<MaybeUninit<T>>); | ||||
| @@ -34,6 +34,8 @@ impl<T> Storage<T> for VecStorage<T> { | |||||
| } | } | ||||
| } | } | ||||
| impl<T> DistinctStorage for VecStorage<T> {} | |||||
| impl<T> Default for VecStorage<T> { | impl<T> Default for VecStorage<T> { | ||||
| fn default() -> Self { | fn default() -> Self { | ||||
| Self(Vec::new()) | Self(Vec::new()) | ||||
| @@ -0,0 +1,7 @@ | |||||
| [package] | |||||
| name = "parallel-iterator" | |||||
| version = "0.1.0" | |||||
| authors = ["Bergmann89 <info@bergmann89.de>"] | |||||
| edition = "2018" | |||||
| [dependencies] | |||||
| @@ -0,0 +1,61 @@ | |||||
| use super::{no_op::NoOpReducer, Consumer, Folder, ParallelIterator}; | |||||
| pub fn for_each<I, F, T>(iter: I, f: &F) | |||||
| where | |||||
| I: ParallelIterator<Item = T>, | |||||
| F: Fn(T) + Sync, | |||||
| T: Send, | |||||
| { | |||||
| let consumer = ForEachConsumer { f }; | |||||
| iter.drive(consumer) | |||||
| } | |||||
| struct ForEachConsumer<'f, F> { | |||||
| f: &'f F, | |||||
| } | |||||
| impl<'f, F, T> Consumer<T> for ForEachConsumer<'f, F> | |||||
| where | |||||
| F: Fn(T) + Sync, | |||||
| { | |||||
| type Folder = ForEachConsumer<'f, F>; | |||||
| type Reducer = NoOpReducer; | |||||
| type Result = (); | |||||
| fn split_off_left(&self) -> (Self, Self::Reducer) { | |||||
| (Self { f: self.f }, NoOpReducer) | |||||
| } | |||||
| fn into_folder(self) -> Self::Folder { | |||||
| self | |||||
| } | |||||
| } | |||||
| impl<'f, F, T> Folder<T> for ForEachConsumer<'f, F> | |||||
| where | |||||
| F: Fn(T) + Sync, | |||||
| { | |||||
| type Result = (); | |||||
| fn is_full(&self) -> bool { | |||||
| false | |||||
| } | |||||
| fn complete(self) -> Self::Result {} | |||||
| fn consume(self, item: T) -> Self { | |||||
| (self.f)(item); | |||||
| self | |||||
| } | |||||
| fn consume_iter<I>(self, iter: I) -> Self | |||||
| where | |||||
| I: IntoIterator<Item = T>, | |||||
| { | |||||
| iter.into_iter().for_each(self.f); | |||||
| self | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,121 @@ | |||||
| mod for_each; | |||||
| mod no_op; | |||||
| pub trait ParallelIterator: Sized + Send { | |||||
| type Item: Send; | |||||
| fn for_each<F>(self, f: F) | |||||
| where | |||||
| F: Fn(Self::Item) + Sync + Send, | |||||
| { | |||||
| for_each::for_each(self, &f) | |||||
| } | |||||
| fn drive<C>(self, consumer: C) -> C::Result | |||||
| where | |||||
| C: Consumer<Self::Item>; | |||||
| fn len_hint_opt(&self) -> Option<usize> { | |||||
| None | |||||
| } | |||||
| } | |||||
| pub trait IndexedParallelIterator: ParallelIterator { | |||||
| fn drive_indexed<C>(self, consumer: C) -> C::Result | |||||
| where | |||||
| C: IndexedConsumer<Self::Item>; | |||||
| fn len_hint(&self) -> usize; | |||||
| } | |||||
| pub trait Producer: Send + Sized { | |||||
| type Item; | |||||
| fn split(self) -> (Self, Option<Self>); | |||||
| fn fold_with<F>(self, folder: F) -> F | |||||
| where | |||||
| F: Folder<Self::Item>; | |||||
| } | |||||
| pub trait IndexedProducer: Send + Sized { | |||||
| type Item; | |||||
| type IntoIter: Iterator<Item = Self::Item> + DoubleEndedIterator + ExactSizeIterator; | |||||
| fn into_iter(self) -> Self::IntoIter; | |||||
| fn min_len(&self) -> usize { | |||||
| 1 | |||||
| } | |||||
| fn max_len(&self) -> usize { | |||||
| usize::MAX | |||||
| } | |||||
| fn split_at(self, index: usize) -> (Self, Self); | |||||
| fn fold_with<F>(self, folder: F) -> F | |||||
| where | |||||
| F: Folder<Self::Item>, | |||||
| { | |||||
| folder.consume_iter(self.into_iter()) | |||||
| } | |||||
| } | |||||
| pub trait Consumer<Item>: Send + Sized { | |||||
| type Folder: Folder<Item, Result = Self::Result>; | |||||
| type Reducer: Reducer<Self::Result> + Send; | |||||
| type Result: Send; | |||||
| fn split_off_left(&self) -> (Self, Self::Reducer); | |||||
| fn into_folder(self) -> Self::Folder; | |||||
| fn is_full(&self) -> bool { | |||||
| false | |||||
| } | |||||
| } | |||||
| pub trait IndexedConsumer<Item>: Consumer<Item> { | |||||
| fn split_at(self, index: usize) -> (Self, Self, Self::Reducer); | |||||
| } | |||||
| pub trait Folder<Item>: Sized { | |||||
| type Result; | |||||
| fn is_full(&self) -> bool; | |||||
| fn complete(self) -> Self::Result; | |||||
| fn consume(self, item: Item) -> Self; | |||||
| fn consume_iter<I>(mut self, iter: I) -> Self | |||||
| where | |||||
| I: IntoIterator<Item = Item>, | |||||
| { | |||||
| for item in iter { | |||||
| self = self.consume(item); | |||||
| if self.is_full() { | |||||
| break; | |||||
| } | |||||
| } | |||||
| self | |||||
| } | |||||
| } | |||||
| pub trait Reducer<Result> { | |||||
| fn reduce(self, left: Result, right: Result) -> Result; | |||||
| } | |||||
| pub trait Executor { | |||||
| fn exec<P, C>(self, producer: P, consumer: C) -> C::Result | |||||
| where | |||||
| P: Producer, | |||||
| C: Consumer<P::Item>; | |||||
| fn exec_indexed<P, C>(self, producer: P, consumer: C) -> C::Result | |||||
| where | |||||
| P: IndexedProducer, | |||||
| C: IndexedConsumer<P::Item>; | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| use super::Reducer; | |||||
| pub struct NoOpReducer; | |||||
| impl Reducer<()> for NoOpReducer { | |||||
| fn reduce(self, _left: (), _right: ()) {} | |||||
| } | |||||