Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relax auto trait impls, add rayon iterators, fix archetype refresh bugs #10

Merged
merged 5 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ keywords = ["game", "ecs"]
categories = ["data-structures", "no-std", "game-development"]

[features]
default = ["std"]
default = ["std", "rayon"]
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]
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).**

Expand Down Expand Up @@ -81,3 +81,6 @@ fn update_positions_system(_: Receiver<Tick>, 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
134 changes: 109 additions & 25 deletions benches/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<E>, 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<E>, 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<E>, 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.
13 changes: 12 additions & 1 deletion src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions src/bit_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -1057,7 +1065,7 @@ unsafe impl<E: Event, Q: Query + 'static> SystemParam for ReceiverMut<'_, E, Q>
}

fn remove_archetype(state: &mut Self::State, arch: &Archetype) {
state.refresh_archetype(arch)
state.remove_archetype(arch)
}
}

Expand Down
Loading
Loading