From 302f9612bcd7fc2311a0ca5b59d876b87f1b8a39 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 1 Feb 2024 12:15:55 -0800 Subject: [PATCH 1/5] Relax auto trait impls, add rayon iterators, fix archetype refresh bugs --- CHANGELOG.md | 3 + Cargo.toml | 5 +- benches/iter.rs | 134 ++++++++++++++++++++++++------ src/archetype.rs | 13 ++- src/bit_set.rs | 1 + src/event.rs | 10 ++- src/fetch.rs | 208 +++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 3 + src/system.rs | 22 +++-- src/world.rs | 15 ++-- 10 files changed, 365 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8442a35..4591ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## Unreleased +- Fixed archetype refresh bugs and changed semantics of `refresh_archetype`. - Fixed potential "cannot shadow tuple struct with let binding" errors in derived `Query` and `SystemParam` impls. +- Added `#[must_use]` attribute to iterators. +- Relaxed auto trait impls for various types. - Minor performance improvements. ## 0.1.1 - 2024-01-25 diff --git a/Cargo.toml b/Cargo.toml index 5c818a6..7032016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,17 +11,20 @@ categories = ["data-structures", "no-std", "game-development"] [features] default = ["std"] std = ["dep:ahash"] +rayon = ["dep:rayon"] [dependencies] ahash = { version = "0.8.7", optional = true } bumpalo = "3.14.0" evenio_macros = { path = "evenio_macros", version = "0.1.1" } memoffset = "0.9.0" +rayon = { version = "1.8.1", optional = true } slab = "0.4.9" [dev-dependencies] ahash = "0.8.7" -bevy_ecs = "0.12.1" +bevy_ecs = { version = "0.12.1", features = ["multi-threaded"] } +bevy_tasks = "0.12.1" divan = "0.1.11" [lints] diff --git a/benches/iter.rs b/benches/iter.rs index 966056a..3231d4e 100644 --- a/benches/iter.rs +++ b/benches/iter.rs @@ -6,57 +6,141 @@ fn main() { divan::main() } -const ITERS: usize = 1_000_000; - const DATA: f64 = 123456789.0; +const ARGS: [usize; 7] = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000]; + +#[derive(evenio::component::Component, bevy_ecs::component::Component)] +struct C1(f64); + +#[derive(evenio::component::Component, bevy_ecs::component::Component)] +struct C2(#[allow(dead_code)] f64); + +#[derive(evenio::component::Component, bevy_ecs::component::Component)] +struct C3(#[allow(dead_code)] f64); -#[derive(Copy, Clone, evenio::component::Component, bevy_ecs::component::Component)] -struct Data(f64); +#[derive(evenio::event::Event)] +struct E; -#[divan::bench] -fn iter_evenio(bencher: Bencher) { +#[divan::bench(args = ARGS)] +fn iter_evenio(bencher: Bencher, len: usize) { use evenio::prelude::*; let mut world = World::new(); - for _ in 0..ITERS { + for i in 0..len { let e = world.spawn(); - world.insert(e, Data(DATA)); + + world.insert(e, C1(42.0)); + + if i % 2 == 0 { + world.insert(e, C2(42.0)); + } + + if i % 3 == 0 { + world.insert(e, C3(42.0)); + } + } + + world.add_system(move |_: Receiver, f: Fetcher<&mut C1>| { + for c in f { + c.0 = c.0.sqrt(); + } + }); + + bencher.bench_local(|| world.send(E)); +} + +#[divan::bench(args = ARGS)] +fn iter_bevy(bencher: Bencher, len: usize) { + use bevy_ecs::prelude::*; + + let mut world = World::new(); + + for i in 0..len { + let mut e = world.spawn(C1(DATA)); + + if i % 2 == 0 { + e.insert(C2(DATA)); + } + + if i % 3 == 0 { + e.insert(C3(DATA)); + } } + let mut state = world.query::<&mut C1>(); + + bencher.bench_local(|| { + for mut c in state.iter_mut(&mut world) { + c.bypass_change_detection().0 = c.0.sqrt(); + } + }); +} + +// TODO: iteration with high fragmentation. + +#[cfg(feature = "rayon")] +#[divan::bench(args = ARGS)] +fn par_iter_evenio(bencher: Bencher, len: usize) { + use evenio::prelude::*; + use rayon::prelude::*; + #[derive(Event)] struct E; - world.add_system(|_: Receiver, f: Fetcher<&mut Data>| { - for data in f { - data.0 = data.0.sqrt(); + let mut world = World::new(); + + for i in 0..len { + let e = world.spawn(); + + world.insert(e, C1(42.0)); + + if i % 2 == 0 { + world.insert(e, C2(42.0)); + } + + if i % 3 == 0 { + world.insert(e, C3(42.0)); } + } + + world.add_system(move |_: Receiver, f: Fetcher<&mut C1>| { + f.into_par_iter().for_each(|c| { + c.0 = c.0.sqrt(); + }); }); bencher.bench_local(|| world.send(E)); } -#[divan::bench] -fn iter_bevy(bencher: Bencher) { +#[divan::bench(args = ARGS)] +fn par_iter_bevy(bencher: Bencher, len: usize) { use bevy_ecs::prelude::*; - use bevy_ecs::schedule::ScheduleLabel; + use bevy_tasks::{ComputeTaskPool, TaskPool}; - let mut world = World::new(); + ComputeTaskPool::get_or_init(TaskPool::new); - world.spawn_batch((0..ITERS).map(|_| Data(DATA))); + let mut world = World::new(); - #[derive(ScheduleLabel, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Copy, Clone)] - struct Whatever; + for i in 0..len { + let mut e = world.spawn(C1(DATA)); - let mut sched = Schedule::new(Whatever); + if i % 2 == 0 { + e.insert(C2(DATA)); + } - sched.add_systems(|mut q: Query<&mut Data>| { - for mut data in &mut q { - data.bypass_change_detection().0 = data.0.sqrt(); + if i % 3 == 0 { + e.insert(C3(DATA)); } - }); + } + + let mut state = world.query::<&mut C1>(); - bencher.bench_local(|| sched.run(&mut world)); + bencher.bench_local(|| { + state.par_iter_mut(&mut world).for_each(|mut c| { + c.bypass_change_detection().0 = c.0.sqrt(); + }); + }); } -// TODO: iteration with many fragmented archetypes. +// TODO: parallel iteration with varying amounts of work per iteration. diff --git a/src/archetype.rs b/src/archetype.rs index d03198b..a003c68 100644 --- a/src/archetype.rs +++ b/src/archetype.rs @@ -176,6 +176,9 @@ impl Archetypes { /// Traverses one edge of the archetype graph in the insertion direction. /// Returns the destination archetype. + /// + /// If the archetype with the added component does not exist, then it is + /// created. Otherwise, the existing archetype is returned. pub(crate) unsafe fn traverse_insert( &mut self, src_arch_idx: ArchetypeIdx, @@ -243,6 +246,9 @@ impl Archetypes { /// Traverses one edge of the archetype graph in the remove direction. /// Returns the destination archetype. + /// + /// If the archetype with the removed component does not exist, then it is + /// created. Otherwise, the existing archetype is returned. pub(crate) unsafe fn traverse_remove( &mut self, src_arch_idx: ArchetypeIdx, @@ -598,7 +604,9 @@ impl Archetype { .expr .eval(|idx| self.column_of(idx).is_some()) { - info.system_mut().refresh_archetype(self); + if self.entity_count() > 0 { + info.system_mut().refresh_archetype(self); + } self.refresh_listeners.insert(info.ptr()); } @@ -680,6 +688,9 @@ impl Archetype { } } +unsafe impl Send for Archetype {} +unsafe impl Sync for Archetype {} + /// All of the component data for a single component type in an [`Archetype`]. #[derive(Debug)] pub struct Column { diff --git a/src/bit_set.rs b/src/bit_set.rs index c6278d9..d5631dd 100644 --- a/src/bit_set.rs +++ b/src/bit_set.rs @@ -229,6 +229,7 @@ where } /// An iterator over the items in a [`BitSet`]. +#[must_use = "iterators are lazy and do nothing unless consumed"] pub struct Iter<'a, T = usize> { bits: Block, block_idx: usize, diff --git a/src/event.rs b/src/event.rs index 42693e9..b3982a8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -8,6 +8,7 @@ use core::any::TypeId; use core::marker::PhantomData; use core::num::NonZeroU32; use core::ops::{Deref, DerefMut, Index}; +use core::panic::{RefUnwindSafe, UnwindSafe}; use core::ptr::NonNull; use core::{any, fmt}; @@ -665,6 +666,9 @@ impl EventQueue { // the event queue. unsafe impl Sync for EventQueue {} +impl UnwindSafe for EventQueue {} +impl RefUnwindSafe for EventQueue {} + #[derive(Clone, Copy, Debug)] pub(crate) struct EventQueueItem { pub(crate) meta: EventMeta, @@ -673,6 +677,10 @@ pub(crate) struct EventQueueItem { pub(crate) event: *mut u8, } +// SAFETY: Events are always Send + Sync. +unsafe impl Send for EventQueueItem {} +unsafe impl Sync for EventQueueItem {} + /// Metadata for an event in the event queue. #[derive(Clone, Copy, Debug)] pub(crate) enum EventMeta { @@ -1057,7 +1065,7 @@ unsafe impl SystemParam for ReceiverMut<'_, E, Q> } fn remove_archetype(state: &mut Self::State, arch: &Archetype) { - state.refresh_archetype(arch) + state.remove_archetype(arch) } } diff --git a/src/fetch.rs b/src/fetch.rs index 0ad0f9d..afc8032 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -98,6 +98,8 @@ impl FetcherState { Ok(Q::get(state, loc.row)) } + // TODO: get_many_mut + #[inline] pub(crate) unsafe fn iter<'a>(&'a self, archetypes: &'a Archetypes) -> Iter<'a, Q> where @@ -141,7 +143,38 @@ impl FetcherState { } } + #[cfg(feature = "rayon")] + pub(crate) unsafe fn par_iter<'a>(&'a self, archetypes: &'a Archetypes) -> ParIter<'a, Q> + where + Q: ReadOnlyQuery, + { + ParIter { + arch_states: self.map.values(), + arch_indices: self.map.keys(), + archetypes, + _marker: PhantomData, + } + } + + #[cfg(feature = "rayon")] + pub(crate) unsafe fn par_iter_mut<'a>( + &'a mut self, + archetypes: &'a Archetypes, + ) -> ParIter<'a, Q> { + ParIter { + arch_states: self.map.values(), + arch_indices: self.map.keys(), + archetypes, + _marker: PhantomData, + } + } + pub(crate) fn refresh_archetype(&mut self, arch: &Archetype) { + debug_assert!( + arch.entity_count() != 0, + "`refresh_archetype` called with empty archetype" + ); + if let Some(fetch) = Q::new_arch_state(arch, &mut self.state) { self.map.insert(arch.index(), fetch); } @@ -150,8 +183,6 @@ impl FetcherState { pub(crate) fn remove_archetype(&mut self, arch: &Archetype) { self.map.remove(arch.index()); } - - // TODO: get_many_mut } impl fmt::Debug for FetcherState { @@ -409,7 +440,7 @@ unsafe impl SystemParam for TrySingle<'_, Q> { } fn remove_archetype(state: &mut Self::State, arch: &Archetype) { - state.refresh_archetype(arch) + state.remove_archetype(arch) } } @@ -439,6 +470,7 @@ impl std::error::Error for SingleError {} /// Iterator over entities matching the query `Q`. /// /// Entities are visited in a deterministic but otherwise unspecified order. +#[must_use = "iterators are lazy and do nothing unless consumed"] pub struct Iter<'a, Q: Query> { /// Pointer into the array of archetype states. This pointer moves forward /// until it reaches `state_last`. @@ -454,7 +486,7 @@ pub struct Iter<'a, Q: Query> { /// Number of entities in the current archetype. len: u32, archetypes: &'a Archetypes, - // Iterator should inherit the variance of the query item. + // Variance and auto traits should be inherited from the query item. _marker: PhantomData>, } @@ -534,7 +566,7 @@ impl<'a, Q: ReadOnlyQuery> Clone for Iter<'a, Q> { } } -impl<'a, Q: Query> fmt::Debug for Iter<'a, Q> { +impl fmt::Debug for Iter<'_, Q> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Iter") .field("state", &self.state) @@ -547,7 +579,133 @@ impl<'a, Q: Query> fmt::Debug for Iter<'a, Q> { } } -// TODO `Send` and `Sync` impls for `Iter`. +unsafe impl<'a, Q> Send for Iter<'a, Q> +where + Q: Query, + Q::Item<'a>: Send, +{ +} + +unsafe impl<'a, Q> Sync for Iter<'a, Q> +where + Q: Query, + Q::Item<'a>: Sync, +{ +} + +#[cfg(feature = "rayon")] +pub use rayon_impl::*; + +#[cfg(feature = "rayon")] +mod rayon_impl { + use rayon::iter::plumbing::UnindexedConsumer; + use rayon::prelude::*; + + use super::*; + + /// A [`ParallelIterator`] over entities matching the query `Q`. + /// + /// This is the parallel version of [`Iter`]. + #[must_use = "iterators are lazy and do nothing unless consumed"] + pub struct ParIter<'a, Q: Query> { + pub(super) arch_states: &'a [Q::ArchState], + pub(super) arch_indices: &'a [ArchetypeIdx], + pub(super) archetypes: &'a Archetypes, + pub(super) _marker: PhantomData>, + } + + impl Clone for ParIter<'_, Q> { + fn clone(&self) -> Self { + Self { + arch_states: self.arch_states, + arch_indices: self.arch_indices, + archetypes: self.archetypes, + _marker: PhantomData, + } + } + } + + impl fmt::Debug for ParIter<'_, Q> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ParIter") + .field("arch_states", &self.arch_states) + .field("arch_indices", &self.arch_indices) + .field("archetypes", &self.archetypes) + .finish() + } + } + + impl<'a, Q> ParallelIterator for ParIter<'a, Q> + where + Q: Query, + Q::Item<'a>: Send, + { + type Item = Q::Item<'a>; + + fn drive_unindexed(self, consumer: C) -> C::Result + where + C: UnindexedConsumer, + { + unsafe { assume_debug_checked(self.arch_states.len() == self.arch_indices.len()) }; + + self.arch_states + .par_iter() + .zip_eq(self.arch_indices) + .flat_map(|(state, &index)| { + let entity_count = + unsafe { self.archetypes.get(index).unwrap_debug_checked() }.entity_count(); + + (0..entity_count).into_par_iter().map(|row| { + let item: Q::Item<'a> = unsafe { Q::get(state, ArchetypeRow(row)) }; + item + }) + }) + .drive_unindexed(consumer) + } + } + + impl<'a, Q> IntoParallelIterator for Fetcher<'a, Q> + where + Q: Query, + Q::Item<'a>: Send, + { + type Iter = ParIter<'a, Q>; + + type Item = Q::Item<'a>; + + fn into_par_iter(self) -> Self::Iter { + unsafe { self.state.par_iter_mut(self.world.archetypes()) } + } + } + + impl<'a, Q> IntoParallelIterator for &'a Fetcher<'_, Q> + where + Q: ReadOnlyQuery, + Q::Item<'a>: Send, + { + type Iter = ParIter<'a, Q>; + + type Item = Q::Item<'a>; + + fn into_par_iter(self) -> Self::Iter { + unsafe { self.state.par_iter(self.world.archetypes()) } + } + } + + impl<'a, Q: Query> IntoParallelIterator for &'a mut Fetcher<'_, Q> + where + Q: Query, + Q::Item<'a>: Send, + { + type Iter = ParIter<'a, Q>; + + type Item = Q::Item<'a>; + + fn into_par_iter(self) -> Self::Iter { + unsafe { self.state.par_iter_mut(self.world.archetypes()) } + } + } +} #[cfg(test)] mod tests { @@ -610,13 +768,14 @@ mod tests { } #[test] - fn iter_cached() { + fn iter() { let mut world = World::new(); let mut set = BTreeSet::new(); for i in 0..100_u32 { let e = world.spawn(); + world.insert(e, C1(i.pow(2))); if i % 2 == 0 { @@ -641,8 +800,39 @@ mod tests { world.send(E1); } + #[cfg(feature = "rayon")] + #[test] + fn par_iter() { + use rayon::prelude::*; + + let mut world = World::new(); + + const N: u32 = 100; + + for i in 0..N { + let e = world.spawn(); + + world.insert(e, C1(i)); + + if i % 2 == 0 { + world.insert(e, C2(i)); + } + + if i % 3 == 0 { + world.insert(e, C3(i)); + } + } + + world.add_system(move |_: Receiver, f: Fetcher<&C1>| { + let sum = f.par_iter().map(|c| c.0).sum::(); + assert_eq!(sum, N * (N - 1) / 2); + }); + + world.send(E1); + } + #[test] - fn iter_cached_empty() { + fn iter_empty() { let mut world = World::new(); world.add_system(move |_: Receiver, f: Fetcher<&C1>| { @@ -655,7 +845,7 @@ mod tests { } #[test] - fn iter_cached_len() { + fn iter_len() { let mut world = World::new(); let count = 100; diff --git a/src/lib.rs b/src/lib.rs index 73655d6..8d83da9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,9 @@ pub mod system; pub mod tutorial; pub mod world; +#[cfg(feature = "rayon")] +pub use rayon; + /// For macros only. #[doc(hidden)] pub mod __private { diff --git a/src/system.rs b/src/system.rs index 0101a8d..8d23ef7 100644 --- a/src/system.rs +++ b/src/system.rs @@ -7,6 +7,7 @@ use alloc::vec::Vec; use core::any::TypeId; use core::marker::PhantomData; use core::ops::{Deref, DerefMut, Index}; +use core::panic::{RefUnwindSafe, UnwindSafe}; use core::ptr::NonNull; use core::{any, fmt}; @@ -158,6 +159,9 @@ impl Systems { } } +unsafe impl Send for Systems {} +unsafe impl Sync for Systems {} + impl Index for Systems { type Output = SystemInfo; @@ -349,6 +353,9 @@ impl SystemInfo { } } +impl UnwindSafe for SystemInfoInner {} +impl RefUnwindSafe for SystemInfoInner {} + unsafe impl Send for SystemInfo {} unsafe impl Sync for SystemInfo {} @@ -691,11 +698,12 @@ pub trait System: Send + Sync + 'static { /// internal state updated. /// /// This is invoked in the following scenarios: - /// - The archetype is brand new. - /// - The archetype's columns were reallocated, and any pointers into the - /// archetype's columns need to be reacquired. /// - The archetype was previously empty, but has now gained at least one /// entity. + /// - The archetype's columns were reallocated, and any pointers into the + /// archetype's columns need to be reacquired. + /// + /// This method must not be called with empty archetypes. fn refresh_archetype(&mut self, arch: &Archetype); /// Notifies the system that an archetype it might care about is no longer @@ -704,6 +712,10 @@ pub trait System: Send + Sync + 'static { /// This is invoked in the following scenarios: /// - The archetype was removed from the world. /// - The archetype previously had entities in it, but is now empty. + /// + /// In either case, the system must assume that the archetype is no longer + /// available. Attempting to read the component data from a removed + /// archetype is illegal. fn remove_archetype(&mut self, arch: &Archetype); } @@ -1207,7 +1219,7 @@ unsafe impl SystemParam for std::sync::Mutex

{ } fn remove_archetype(state: &mut Self::State, arch: &Archetype) { - P::refresh_archetype(state, arch) + P::remove_archetype(state, arch) } } @@ -1235,7 +1247,7 @@ unsafe impl SystemParam for std::sync::RwLock

{ } fn remove_archetype(state: &mut Self::State, arch: &Archetype) { - P::refresh_archetype(state, arch) + P::remove_archetype(state, arch) } } diff --git a/src/world.rs b/src/world.rs index 8b420a7..6554015 100644 --- a/src/world.rs +++ b/src/world.rs @@ -7,7 +7,6 @@ use core::any::{self, TypeId}; use core::cell::UnsafeCell; use core::marker::PhantomData; use core::mem; -use core::panic::{RefUnwindSafe, UnwindSafe}; use core::ptr::{self, NonNull}; use crate::archetype::Archetypes; @@ -993,12 +992,6 @@ impl Drop for World { } } -unsafe impl Send for World {} -unsafe impl Sync for World {} - -impl UnwindSafe for World {} -impl RefUnwindSafe for World {} - /// Used for queueing events. Passed to the closure given in [`send_many`]. /// /// [`send_many`]: World::send_many @@ -1121,6 +1114,7 @@ impl<'a> UnsafeWorldCell<'a> { #[cfg(test)] mod tests { use alloc::sync::Arc; + use core::panic::{RefUnwindSafe, UnwindSafe}; use std::panic; use crate::prelude::*; @@ -1230,4 +1224,11 @@ mod tests { assert_eq!(Arc::strong_count(&arc), 1); } + + /// Asserts that `World` has the expected auto trait implementations. + fn _assert_auto_trait_impls() + where + World: Send + Sync + UnwindSafe + RefUnwindSafe, + { + } } From f9ee108e67f7c8cced447674113439545946e190 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 1 Feb 2024 21:39:34 -0800 Subject: [PATCH 2/5] Skip test in Miri --- src/fetch.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fetch.rs b/src/fetch.rs index afc8032..79665a7 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -800,7 +800,10 @@ mod tests { world.send(E1); } - #[cfg(feature = "rayon")] + #[cfg(all( + feature = "rayon", + not(miri) // Rayon isn't working with Miri. + ))] #[test] fn par_iter() { use rayon::prelude::*; From 00159d10c1458aaa7710fbb180bf7016576c037c Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 1 Feb 2024 21:43:15 -0800 Subject: [PATCH 3/5] Document `rayon` feature flag --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5d1ab8e..c3c83ca 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,6 @@ fn update_positions_system(_: Receiver, entities: Fetcher<(&mut Position, ## Feature Flags - `std` (_enabled by default_): Enables support for the standard library. Without this, `evenio` depends only on `core` and `alloc`. +- `rayon`: Adds parallel iterator support for `Fetcher`. Uses the [Rayon] library. + +[Rayon]: https://github.com/rayon-rs/rayon From 27f3d2c4fda7f18dfc61a8924fd07f403dcf999e Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 2 Feb 2024 00:07:06 -0800 Subject: [PATCH 4/5] Fix Send, Sync bounds on iterators --- Cargo.toml | 2 +- README.md | 2 +- src/fetch.rs | 27 +++++---------------------- src/query.rs | 5 ++--- 4 files changed, 9 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7032016..e4dfd5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["game", "ecs"] categories = ["data-structures", "no-std", "game-development"] [features] -default = ["std"] +default = ["std", "rayon"] std = ["dep:ahash"] rayon = ["dep:rayon"] diff --git a/README.md b/README.md index c3c83ca..5168cc3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The control flow of the entire program is then defined by the flow of events bet - The interface to the library does not rely on Rust's type system. `evenio` should also be usable in dynamic contexts such as scripting languages or plugins. - A small set of dependencies and `no_std` support. -Features such as intra-system parallelism and event batching are planned but not yet implemented. +Features such as inter-system parallelism and event batching are planned but not yet implemented. > **For a full step-by-step introduction, please read the [tutorial book 📚](tutorial).** diff --git a/src/fetch.rs b/src/fetch.rs index 79665a7..fa9c947 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -139,7 +139,6 @@ impl FetcherState { row: ArchetypeRow(0), len: first_arch_len, archetypes, - _marker: PhantomData, } } @@ -152,7 +151,6 @@ impl FetcherState { arch_states: self.map.values(), arch_indices: self.map.keys(), archetypes, - _marker: PhantomData, } } @@ -165,7 +163,6 @@ impl FetcherState { arch_states: self.map.values(), arch_indices: self.map.keys(), archetypes, - _marker: PhantomData, } } @@ -486,8 +483,6 @@ pub struct Iter<'a, Q: Query> { /// Number of entities in the current archetype. len: u32, archetypes: &'a Archetypes, - // Variance and auto traits should be inherited from the query item. - _marker: PhantomData>, } impl<'a, Q: Query> Iterator for Iter<'a, Q> { @@ -561,7 +556,6 @@ impl<'a, Q: ReadOnlyQuery> Clone for Iter<'a, Q> { row: self.row, len: self.len, archetypes: self.archetypes, - _marker: self._marker, } } } @@ -579,19 +573,10 @@ impl fmt::Debug for Iter<'_, Q> { } } -unsafe impl<'a, Q> Send for Iter<'a, Q> -where - Q: Query, - Q::Item<'a>: Send, -{ -} - -unsafe impl<'a, Q> Sync for Iter<'a, Q> -where - Q: Query, - Q::Item<'a>: Sync, -{ -} +// SAFETY: `Iter` iterates over component data only, which is always `Send` and +// `Sync`. +unsafe impl Send for Iter<'_, Q> {} +unsafe impl Sync for Iter<'_, Q> {} #[cfg(feature = "rayon")] pub use rayon_impl::*; @@ -611,7 +596,6 @@ mod rayon_impl { pub(super) arch_states: &'a [Q::ArchState], pub(super) arch_indices: &'a [ArchetypeIdx], pub(super) archetypes: &'a Archetypes, - pub(super) _marker: PhantomData>, } impl Clone for ParIter<'_, Q> { @@ -620,7 +604,6 @@ mod rayon_impl { arch_states: self.arch_states, arch_indices: self.arch_indices, archetypes: self.archetypes, - _marker: PhantomData, } } } @@ -732,7 +715,7 @@ mod tests { struct C3(u32); #[test] - fn random_access_cached() { + fn random_access() { let mut world = World::new(); let e = world.spawn(); diff --git a/src/query.rs b/src/query.rs index 2f4e42c..49da101 100644 --- a/src/query.rs +++ b/src/query.rs @@ -741,11 +741,10 @@ unsafe impl Query for PhantomData { unsafe impl ReadOnlyQuery for PhantomData {} -/// Wrapper for `NonNull` which implements [`Send`] and [`Sync`] -/// unconditionally. +/// Transparent wrapper around a [`NonNull`]. This implements [`Send`] and [`Sync`] unconditionally. #[doc(hidden)] #[repr(transparent)] -pub struct ColumnPtr(NonNull); +pub struct ColumnPtr(pub NonNull); impl Clone for ColumnPtr { fn clone(&self) -> Self { From 6a3a05e64b0346c75e9d3bdbb589392151ab5d18 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 2 Feb 2024 00:08:12 -0800 Subject: [PATCH 5/5] Lints --- src/fetch.rs | 1 - src/query.rs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fetch.rs b/src/fetch.rs index fa9c947..93478b4 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -2,7 +2,6 @@ use alloc::format; use core::iter::FusedIterator; -use core::marker::PhantomData; use core::ptr::NonNull; use core::{any, fmt}; diff --git a/src/query.rs b/src/query.rs index 49da101..27146d9 100644 --- a/src/query.rs +++ b/src/query.rs @@ -741,7 +741,8 @@ unsafe impl Query for PhantomData { unsafe impl ReadOnlyQuery for PhantomData {} -/// Transparent wrapper around a [`NonNull`]. This implements [`Send`] and [`Sync`] unconditionally. +/// Transparent wrapper around a [`NonNull`]. This implements [`Send`] and +/// [`Sync`] unconditionally. #[doc(hidden)] #[repr(transparent)] pub struct ColumnPtr(pub NonNull);