upgrade bevy_legion / re-apply fork

This commit is contained in:
Carter Anderson 2020-03-09 00:46:38 -07:00
parent 9355a53980
commit 6ef1c099ff
47 changed files with 2180 additions and 1257 deletions

View file

@ -6,8 +6,7 @@ edition = "2018"
[dependencies] [dependencies]
# Modified to use std::any::type_name instead of std::any::TypeId # Modified to use std::any::type_name instead of std::any::TypeId
# legion = { path = "bevy_legion", features = ["serde-1"] } legion = { path = "bevy_legion", features = ["serialize"] }
legion = { git = "https://github.com/TomGillen/legion", rev = "c5b9628630d4f9fc54b6843b5ce02d0669434a61", features = ["serialize"] }
wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "a7b0d5ae5bc0934439ef559ed145e93f0117c39a"} wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "a7b0d5ae5bc0934439ef559ed145e93f0117c39a"}
glam = "0.8.4" glam = "0.8.4"
winit = "0.21.0" winit = "0.21.0"

101
bevy_legion/.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,101 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
check:
name: Compile
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- uses: actions/cache@v1.0.1
with:
path: target
key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.OS }}-build-
- uses: actions-rs/cargo@v1
with:
command: test
args: --all
check-features:
name: Features
runs-on: ubuntu-latest
needs: [check]
strategy:
matrix:
features:
- --manifest-path=example/Cargo.toml
- --all-features --release
- --no-default-features --release
- --all-features
- --no-default-features
- --no-default-features --features par-iter
- --no-default-features --features par-schedule
- --no-default-features --features metrics
- --no-default-features --features ffi
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- uses: actions/cache@v1.0.1
with:
path: target
key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.OS }}-build-
- uses: actions-rs/cargo@v1
with:
command: test
args: --all ${{ matrix.features }}
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all -- -D warnings

3
bevy_legion/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
target
**/*.rs.bk
Cargo.lock

2
bevy_legion/.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/legion_systems/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/legion_systems/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/legion_systems/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/legion_systems/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/legion_core/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/legion_core/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/legion_core/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/legion_core/benches" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/legion_core/target" />
<excludeFolder url="file://$MODULE_DIR$/legion_systems/target" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/legion.iml" filepath="$PROJECT_DIR$/.idea/legion.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1,21 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true
cache: cargo
script:
- cargo test --all-features
- cargo test --no-default-features
- cargo test --no-default-features --features events
- cargo test --no-default-features --features par-iter
- cargo test --no-default-features --features ffi
- cargo test --release --all-features
- cargo test --release --no-default-features
- cargo test --release --no-default-features --features events
- cargo test --release --no-default-features --features par-iter
- cargo test --release --no-default-features --features ffi

View file

@ -14,40 +14,35 @@ edition = "2018"
travis-ci = { repository = "TomGillen/legion", branch = "master" } travis-ci = { repository = "TomGillen/legion", branch = "master" }
[features] [features]
default = ["par-iter", "par-schedule", "events", "ffi"] default = ["par-iter", "par-schedule", "ffi"]
par-iter = ["rayon"] par-iter = ["legion-core/par-iter", "legion-systems/par-iter"]
par-schedule = ["rayon", "crossbeam-queue"] par-schedule = ["legion-systems/par-schedule"]
log = ["tracing/log", "tracing/log-always"] log = ["tracing/log", "tracing/log-always"]
ffi = [] ffi = ["legion-core/ffi"]
serde-1 = ["serde"] serialize = ["legion-core/serialize"]
events = ["rayon"] metrics = ["legion-core/metrics"]
[workspace]
members = [
"legion_core",
"legion_systems",
]
[dependencies] [dependencies]
parking_lot = "0.9" legion-core = { path = "legion_core", version = "0.2.1", default-features = false }
downcast-rs = "1.0" legion-systems = { path = "legion_systems", version = "0.2.1", default-features = false }
itertools = "0.8"
rayon = { version = "1.2", optional = true }
crossbeam-queue = { version = "0.2.0", optional = true }
crossbeam-channel = "0.4.0"
derivative = "1"
smallvec = "0.6"
bit-set = "0.5"
paste = "0.1"
tracing = "0.1"
metrics = { version = "0.12", optional = true }
serde = { version = "1", optional = true }
fxhash = "0.2"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"
cgmath = "0.17" cgmath = "0.17"
tracing-subscriber = "0.1.6" tracing-subscriber = "0.2"
legion = { features = ["serde-1"], path = "." }
serde_json = "1.0"
type-uuid = "0.1"
erased-serde = "0.3" erased-serde = "0.3"
serde = { version = "1", features = ["derive"]} serde = { version = "1", features = ["derive"]}
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
tracing = "0.1"
itertools = "0.8"
rayon = "1.2"
crossbeam-channel = "0.4.0"
[[bench]] [[bench]]
name = "benchmarks" name = "benchmarks"
@ -60,3 +55,7 @@ harness = false
[[bench]] [[bench]]
name = "transform" name = "transform"
harness = false harness = false
[[bench]]
name = "insertion"
harness = false

View file

@ -54,16 +54,16 @@ fn add_background_entities(world: &mut World, count: usize) {
create_entities( create_entities(
world, world,
&mut [ &mut [
Box::new(|e, w| w.add_component(e, A(0.0))), Box::new(|e, w| w.add_component(e, A(0.0)).unwrap()),
Box::new(|e, w| w.add_component(e, B(0.0))), Box::new(|e, w| w.add_component(e, B(0.0)).unwrap()),
Box::new(|e, w| w.add_component(e, C(0.0))), Box::new(|e, w| w.add_component(e, C(0.0)).unwrap()),
Box::new(|e, w| w.add_tag(e, Tag(0.0))), Box::new(|e, w| w.add_tag(e, Tag(0.0)).unwrap()),
Box::new(|e, w| w.add_component(e, D(0.0))), Box::new(|e, w| w.add_component(e, D(0.0)).unwrap()),
Box::new(|e, w| w.add_tag(e, Tag(1.0))), Box::new(|e, w| w.add_tag(e, Tag(1.0)).unwrap()),
Box::new(|e, w| w.add_component(e, E(0.0))), Box::new(|e, w| w.add_component(e, E(0.0)).unwrap()),
Box::new(|e, w| w.add_tag(e, Tag(2.0))), Box::new(|e, w| w.add_tag(e, Tag(2.0)).unwrap()),
Box::new(|e, w| w.add_component(e, F(0.0))), Box::new(|e, w| w.add_component(e, F(0.0)).unwrap()),
Box::new(|e, w| w.add_tag(e, Tag(3.0))), Box::new(|e, w| w.add_tag(e, Tag(3.0)).unwrap()),
], ],
5, 5,
count, count,

View file

@ -0,0 +1,69 @@
use criterion::*;
use legion::prelude::*;
fn bench_insert_zero_baseline(c: &mut Criterion) {
c.bench_function("insert_zero_baseline", |b| {
b.iter(|| {
//let universe = Universe::new();
//let mut world = universe.create_world();
let components: Vec<isize> = (0..10000).map(|i| i).collect();
criterion::black_box(components);
});
});
}
fn bench_insert_one_baseline(c: &mut Criterion) {
c.bench_function("insert_one_baseline", |b| {
b.iter(|| {
let universe = Universe::new();
let mut world = universe.create_world();
let components: Vec<isize> = (0..10000).map(|i| i).collect();
criterion::black_box(components);
world.insert((), vec![(1usize,)]);
});
});
}
fn bench_insert_unbatched(c: &mut Criterion) {
c.bench_function("insert_unbatched", |b| {
b.iter(|| {
let universe = Universe::new();
let mut world = universe.create_world();
let components: Vec<isize> = (0..10000).map(|i| i).collect();
for component in components {
world.insert((), vec![(component,)]);
}
});
});
}
fn bench_insert_batched(c: &mut Criterion) {
c.bench(
"insert_batched",
ParameterizedBenchmark::new(
"counts",
|b, n| {
b.iter(|| {
let universe = Universe::new();
let mut world = universe.create_world();
let components: Vec<(isize,)> = (0..*n).map(|i| (i,)).collect();
world.insert((), components);
});
},
(1..11).map(|i| i * 1000),
),
);
}
criterion_group!(
basic,
bench_insert_zero_baseline,
bench_insert_one_baseline,
bench_insert_unbatched,
bench_insert_batched,
);
criterion_main!(basic);

View file

@ -55,23 +55,27 @@ fn setup(data: &[Variants]) -> World {
match i { match i {
0 => world.insert( 0 => world.insert(
(), (),
group.map(|x| { group
if let Variants::AB(a, b) = x { .map(|x| {
(*a, *b) if let Variants::AB(a, b) = x {
} else { (*a, *b)
panic!(); } else {
} panic!();
}), }
})
.collect::<Vec<_>>(),
), ),
_ => world.insert( _ => world.insert(
(), (),
group.map(|x| { group
if let Variants::AC(a, c) = x { .map(|x| {
(*a, *c) if let Variants::AC(a, c) = x {
} else { (*a, *c)
panic!(); } else {
} panic!();
}), }
})
.collect::<Vec<_>>(),
), ),
}; };
} }

View file

@ -0,0 +1,6 @@
[workspace]
members = [
"hello_world",
"serialization",
]

View file

@ -0,0 +1,9 @@
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Thomas Gillen <thomas.gillen@googlemail.com>"]
edition = "2018"
[dependencies]
legion = { path = "../.." }
tracing-subscriber = "0.2"

View file

@ -17,16 +17,13 @@ fn main() {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
// Insert resources into the world // Create resources
// Resources are also dynamically scheduled just like components, so the accessed // Resources are also dynamically scheduled just like components, so the accesses
// declared within a SystemBuilder is correct. // declared within a SystemBuilder are correct.
// Any resource accessed by systems *must be* manually inserted beforehand, otherwise it will panic. // Any resource accessed by systems *must be* manually inserted beforehand, otherwise it will panic.
world let mut resources = Resources::default();
.resources resources.insert(ExampleResource1("ExampleResource1".to_string()));
.insert(ExampleResource1("ExampleResource1".to_string())); resources.insert(ExampleResource2("ExampleResource2".to_string()));
world
.resources
.insert(ExampleResource2("ExampleResource2".to_string()));
// create entities // create entities
// An insert call is used to insert matching entities into the world. // An insert call is used to insert matching entities into the world.
@ -89,7 +86,7 @@ fn main() {
); );
}); });
let thread_local_example = Box::new(|world: &mut World| { let thread_local_example = Box::new(|world: &mut World, _resources: &mut Resources| {
// This is an example of a thread local system which has full, exclusive mutable access to the world. // This is an example of a thread local system which has full, exclusive mutable access to the world.
let query = <(Write<Pos>, Read<Vel>)>::query(); let query = <(Write<Pos>, Read<Vel>)>::query();
for (mut pos, vel) in query.iter_mut(world) { for (mut pos, vel) in query.iter_mut(world) {
@ -110,5 +107,5 @@ fn main() {
.build(); .build();
// Execute a frame of the schedule. // Execute a frame of the schedule.
schedule.execute(&mut world); schedule.execute(&mut world, &mut resources);
} }

View file

@ -0,0 +1,14 @@
[package]
name = "serialization"
version = "0.1.0"
authors = ["Thomas Gillen <thomas.gillen@googlemail.com>"]
edition = "2018"
[dependencies]
legion = { path = "../..", features = ["serialize"] }
tracing-subscriber = "0.2"
serde_json = "1.0"
type-uuid = "0.1"
erased-serde = "0.3"
serde = { version = "1", features = ["derive"]}
uuid = { version = "0.8", features = ["v4"] }

View file

@ -75,12 +75,15 @@ impl<'de, 'a, T: for<'b> Deserialize<'b> + 'static> Visitor<'de>
Some((storage_ptr, storage_len)) => { Some((storage_ptr, storage_len)) => {
let storage_ptr = storage_ptr.as_ptr() as *mut T; let storage_ptr = storage_ptr.as_ptr() as *mut T;
for idx in 0..storage_len { for idx in 0..storage_len {
let element_ptr = unsafe { storage_ptr.offset(idx as isize) }; let element_ptr = unsafe { storage_ptr.add(idx) };
if let None = seq.next_element_seed(ComponentDeserializer { if seq
ptr: element_ptr, .next_element_seed(ComponentDeserializer {
_marker: PhantomData, ptr: element_ptr,
})? { _marker: PhantomData,
})?
.is_none()
{
panic!( panic!(
"expected {} elements in chunk but only {} found", "expected {} elements in chunk but only {} found",
storage_len, idx storage_len, idx
@ -89,7 +92,7 @@ impl<'de, 'a, T: for<'b> Deserialize<'b> + 'static> Visitor<'de>
} }
} }
None => { None => {
if let Some(_) = seq.next_element::<IgnoredAny>()? { if seq.next_element::<IgnoredAny>()?.is_some() {
panic!("unexpected element when there was no storage space available"); panic!("unexpected element when there was no storage space available");
} else { } else {
// No more elements and no more storage - that's what we want! // No more elements and no more storage - that's what we want!
@ -199,7 +202,7 @@ struct SerializeImpl {
comp_types: HashMap<TypeId, ComponentRegistration>, comp_types: HashMap<TypeId, ComponentRegistration>,
entity_map: RefCell<HashMap<Entity, uuid::Bytes>>, entity_map: RefCell<HashMap<Entity, uuid::Bytes>>,
} }
impl legion::ser::WorldSerializer for SerializeImpl { impl legion::serialize::ser::WorldSerializer for SerializeImpl {
fn can_serialize_tag(&self, ty: &TagTypeId, _meta: &TagMeta) -> bool { fn can_serialize_tag(&self, ty: &TagTypeId, _meta: &TagMeta) -> bool {
self.tag_types.get(&ty.0).is_some() self.tag_types.get(&ty.0).is_some()
} }
@ -302,7 +305,7 @@ struct DeserializeImpl {
comp_types_by_uuid: HashMap<type_uuid::Bytes, ComponentRegistration>, comp_types_by_uuid: HashMap<type_uuid::Bytes, ComponentRegistration>,
entity_map: RefCell<HashMap<uuid::Bytes, Entity>>, entity_map: RefCell<HashMap<uuid::Bytes, Entity>>,
} }
impl legion::de::WorldDeserializer for DeserializeImpl { impl legion::serialize::de::WorldDeserializer for DeserializeImpl {
fn deserialize_archetype_description<'de, D: Deserializer<'de>>( fn deserialize_archetype_description<'de, D: Deserializer<'de>>(
&self, &self,
deserializer: D, deserializer: D,
@ -417,7 +420,7 @@ fn main() {
entity_map: RefCell::new(HashMap::new()), entity_map: RefCell::new(HashMap::new()),
}; };
let serializable = legion::ser::serializable_world(&world, &ser_helper); let serializable = legion::serialize::ser::serializable_world(&world, &ser_helper);
let serialized_data = serde_json::to_string(&serializable).unwrap(); let serialized_data = serde_json::to_string(&serializable).unwrap();
let de_helper = DeserializeImpl { let de_helper = DeserializeImpl {
tag_types_by_uuid: HashMap::from_iter( tag_types_by_uuid: HashMap::from_iter(
@ -445,7 +448,8 @@ fn main() {
}; };
let mut deserialized_world = universe.create_world(); let mut deserialized_world = universe.create_world();
let mut deserializer = serde_json::Deserializer::from_str(&serialized_data); let mut deserializer = serde_json::Deserializer::from_str(&serialized_data);
legion::de::deserialize(&mut deserialized_world, &de_helper, &mut deserializer).unwrap(); legion::serialize::de::deserialize(&mut deserialized_world, &de_helper, &mut deserializer)
.unwrap();
let ser_helper = SerializeImpl { let ser_helper = SerializeImpl {
tag_types: de_helper.tag_types, tag_types: de_helper.tag_types,
comp_types: de_helper.comp_types, comp_types: de_helper.comp_types,
@ -458,7 +462,7 @@ fn main() {
.map(|(uuid, e)| (e, uuid)), .map(|(uuid, e)| (e, uuid)),
)), )),
}; };
let serializable = legion::ser::serializable_world(&deserialized_world, &ser_helper); let serializable = legion::serialize::ser::serializable_world(&deserialized_world, &ser_helper);
let roundtrip_data = serde_json::to_string(&serializable).unwrap(); let roundtrip_data = serde_json::to_string(&serializable).unwrap();
assert_eq!(roundtrip_data, serialized_data); assert_eq!(roundtrip_data, serialized_data);
} }

View file

@ -0,0 +1,45 @@
[package]
name = "legion-core"
version = "0.2.1"
description = "High performance entity component system (ECS) library"
authors = ["Thomas Gillen <thomas.gillen@googlemail.com>"]
repository = "https://github.com/TomGillen/legion"
keywords = ["ecs", "game"]
categories = ["game-engines", "data-structures"]
readme = "readme.md"
license = "MIT"
edition = "2018"
[badges]
travis-ci = { repository = "TomGillen/legion", branch = "master" }
[features]
par-iter = ["rayon"]
ffi = []
serialize = ["serde"]
[dependencies]
parking_lot = "0.10"
downcast-rs = "1.0"
itertools = "0.8"
rayon = { version = "1.2", optional = true }
crossbeam-queue = { version = "0.2.0", optional = true }
crossbeam-channel = "0.4.0"
derivative = "1"
smallvec = "1.2"
tracing = "0.1"
metrics = { version = "0.12", optional = true }
serde = { version = "1", optional = true }
fxhash = "0.2"
thiserror = "1.0"
[dev-dependencies]
tracing-subscriber = "0.2"
serde_json = "1.0"
type-uuid = "0.1"
erased-serde = "0.3"
serde = { version = "1", features = ["derive"]}
uuid = { version = "0.8", features = ["v4"] }
tracing = "0.1"
itertools = "0.8"
rayon = "1.2"

View file

@ -7,62 +7,16 @@ use std::any::{Any, type_name};
use std::ops::Deref; use std::ops::Deref;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::atomic::AtomicIsize; use std::sync::atomic::AtomicIsize;
use crate::resource::Resource;
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
use std::marker::PhantomData; use std::marker::PhantomData;
// #[inline(always)]
// pub fn downcast_typename_mut<U: Any>(value: &mut dyn Any) -> &mut U {
// unsafe { &mut *(value as *mut dyn Any as *mut U) }
// }
// #[inline(always)]
// pub fn downcast_typename_ref<U: Any>(value: &dyn Any) -> &U {
// unsafe { &*(value as *const dyn Any as *const U) }
// // if type_name::<T>() == type_name::<U>() {
// // unsafe { Some(&*(value as *const dyn Any as *const U)) }
// // } else {
// // None
// // }
// }
pub trait DowncastTypename { pub trait DowncastTypename {
fn downcast_typename_mut<T: Any>(&mut self) -> Option<&mut T>; fn downcast_typename_mut<T: Any>(&mut self) -> Option<&mut T>;
fn downcast_typename_ref<T: Any>(&self) -> Option<&T>; fn downcast_typename_ref<T: Any>(&self) -> Option<&T>;
fn is_typename<T: Any>(&self) -> bool; fn is_typename<T: Any>(&self) -> bool;
} }
impl DowncastTypename for dyn Resource {
#[inline(always)]
fn downcast_typename_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is_typename::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe { Some(&mut *(self.as_any_mut() as *mut dyn Any as *mut T)) }
} else {
None
}
}
#[inline(always)]
fn downcast_typename_ref<T: Any>(&self) -> Option<&T> {
if self.is_typename::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe { Some(&*(self.as_any() as *const dyn Any as *const T)) }
} else {
None
}
}
#[inline(always)]
fn is_typename<T: Any>(&self) -> bool {
true
// TODO: it would be nice to add type safety here, but the type names don't match
// println!("{} {}", type_name_of_val(self), type_name::<T>());
// type_name_of_val(self) == type_name::<T>()
}
}
pub fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str { pub fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
type_name::<T>() type_name::<T>()
} }

View file

@ -4,11 +4,16 @@ use crate::{
entity::{Entity, EntityAllocator}, entity::{Entity, EntityAllocator},
filter::{ChunksetFilterData, Filter}, filter::{ChunksetFilterData, Filter},
storage::{Component, ComponentTypeId, Tag, TagTypeId}, storage::{Component, ComponentTypeId, Tag, TagTypeId},
world::{ComponentSource, ComponentTupleSet, IntoComponentSource, TagLayout, TagSet, World}, world::{
ComponentSource, ComponentTupleSet, IntoComponentSource, PreallocComponentSource,
TagLayout, TagSet, World, WorldId,
},
}; };
use derivative::Derivative; use derivative::Derivative;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::ops::Range;
use std::{collections::VecDeque, iter::FromIterator, marker::PhantomData, sync::Arc}; use std::{collections::VecDeque, iter::FromIterator, marker::PhantomData, sync::Arc};
use tracing::{span, Level};
/// This trait can be used to implement custom world writer types that can be directly /// This trait can be used to implement custom world writer types that can be directly
/// inserted into the command buffer, for more custom and complex world operations. This is analogous /// inserted into the command buffer, for more custom and complex world operations. This is analogous
@ -16,7 +21,7 @@ use std::{collections::VecDeque, iter::FromIterator, marker::PhantomData, sync::
/// access. /// access.
pub trait WorldWritable { pub trait WorldWritable {
/// Destructs the writer and performs the write operations on the world. /// Destructs the writer and performs the write operations on the world.
fn write(self: Arc<Self>, world: &mut World); fn write(self: Arc<Self>, world: &mut World, cmd: &CommandBuffer);
/// Returns the list of `ComponentTypeId` which are written by this command buffer. This is leveraged /// Returns the list of `ComponentTypeId` which are written by this command buffer. This is leveraged
/// to allow parralel command buffer flushing. /// to allow parralel command buffer flushing.
@ -38,17 +43,23 @@ struct InsertBufferedCommand<T, C> {
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
components: C, components: C,
entities: Vec<Entity>, entities: Range<usize>,
} }
impl<T, C> WorldWritable for InsertBufferedCommand<T, C> impl<T, C> WorldWritable for InsertBufferedCommand<T, C>
where where
T: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>, T: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: ComponentSource, C: ComponentSource,
{ {
fn write(self: Arc<Self>, world: &mut World) { fn write(self: Arc<Self>, world: &mut World, cmd: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap(); let consumed = Arc::try_unwrap(self).unwrap();
world.insert_buffered(&consumed.entities, consumed.tags, consumed.components); world.insert(
consumed.tags,
PreallocComponentSource::new(
cmd.pending_insertion[consumed.entities].iter().copied(),
consumed.components,
),
);
} }
fn write_components(&self) -> Vec<ComponentTypeId> { self.write_components.clone() } fn write_components(&self) -> Vec<ComponentTypeId> { self.write_components.clone() }
@ -71,7 +82,7 @@ where
T: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>, T: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: IntoComponentSource, C: IntoComponentSource,
{ {
fn write(self: Arc<Self>, world: &mut World) { fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap(); let consumed = Arc::try_unwrap(self).unwrap();
world.insert(consumed.tags, consumed.components); world.insert(consumed.tags, consumed.components);
} }
@ -84,7 +95,7 @@ where
#[derivative(Debug(bound = ""))] #[derivative(Debug(bound = ""))]
struct DeleteEntityCommand(Entity); struct DeleteEntityCommand(Entity);
impl WorldWritable for DeleteEntityCommand { impl WorldWritable for DeleteEntityCommand {
fn write(self: Arc<Self>, world: &mut World) { world.delete(self.0); } fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) { world.delete(self.0); }
fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) } fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) } fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) }
@ -101,9 +112,11 @@ impl<T> WorldWritable for AddTagCommand<T>
where where
T: Tag, T: Tag,
{ {
fn write(self: Arc<Self>, world: &mut World) { fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap(); let consumed = Arc::try_unwrap(self).unwrap();
world.add_tag(consumed.entity, consumed.tag) if let Err(err) = world.add_tag(consumed.entity, consumed.tag) {
tracing::error!(error = %err, "error adding tag");
}
} }
fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) } fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
@ -120,7 +133,11 @@ impl<T> WorldWritable for RemoveTagCommand<T>
where where
T: Tag, T: Tag,
{ {
fn write(self: Arc<Self>, world: &mut World) { world.remove_tag::<T>(self.entity) } fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
if let Err(err) = world.remove_tag::<T>(self.entity) {
tracing::error!(error = %err, "error removing tag");
}
}
fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) } fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn write_tags(&self) -> Vec<TagTypeId> { vec![TagTypeId::of::<T>()] } fn write_tags(&self) -> Vec<TagTypeId> { vec![TagTypeId::of::<T>()] }
@ -138,11 +155,11 @@ impl<C> WorldWritable for AddComponentCommand<C>
where where
C: Component, C: Component,
{ {
fn write(self: Arc<Self>, world: &mut World) { fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap(); let consumed = Arc::try_unwrap(self).unwrap();
world if let Err(err) = world.add_component::<C>(consumed.entity, consumed.component) {
.add_component::<C>(consumed.entity, consumed.component) tracing::error!(error = %err, "error adding component");
.unwrap(); }
} }
fn write_components(&self) -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<C>()] } fn write_components(&self) -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<C>()] }
@ -159,7 +176,11 @@ impl<C> WorldWritable for RemoveComponentCommand<C>
where where
C: Component, C: Component,
{ {
fn write(self: Arc<Self>, world: &mut World) { world.remove_component::<C>(self.entity) } fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
if let Err(err) = world.remove_component::<C>(self.entity) {
tracing::error!(error = %err, "error removing component");
}
}
fn write_components(&self) -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<C>()] } fn write_components(&self) -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<C>()] }
fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) } fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) }
@ -183,25 +204,27 @@ enum EntityCommand {
/// Inserting an entity using the `EntityBuilder`: /// Inserting an entity using the `EntityBuilder`:
/// ///
/// ``` /// ```
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position(f32); /// # struct Position(f32);
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Rotation(f32); /// # struct Rotation(f32);
/// # let universe = Universe::new(); /// # let universe = Universe::new();
/// # let mut world = universe.create_world(); /// # let mut world = universe.create_world();
/// let mut command_buffer = CommandBuffer::from_world(&mut world); /// let mut command_buffer = CommandBuffer::new(&world);
/// command_buffer.build_entity().unwrap() /// command_buffer.start_entity()
/// .with_component(Position(123.0)) /// .with_component(Position(123.0))
/// .with_component(Rotation(456.0)).build(&mut command_buffer); /// .with_component(Rotation(456.0))
/// .build();
/// command_buffer.write(&mut world); /// command_buffer.write(&mut world);
/// ``` /// ```
pub struct EntityBuilder<TS = (), CS = ()> { pub struct EntityBuilder<'a, TS = (), CS = ()> {
entity: Entity, cmd: &'a mut CommandBuffer,
tags: TS, tags: TS,
components: CS, components: CS,
} }
impl<TS, CS> EntityBuilder<TS, CS>
impl<'a, TS, CS> EntityBuilder<'a, TS, CS>
where where
TS: 'static + Send + ConsFlatten, TS: 'static + Send + ConsFlatten,
CS: 'static + Send + ConsFlatten, CS: 'static + Send + ConsFlatten,
@ -211,71 +234,48 @@ where
pub fn with_component<C: Component>( pub fn with_component<C: Component>(
self, self,
component: C, component: C,
) -> EntityBuilder<TS, <CS as ConsAppend<C>>::Output> ) -> EntityBuilder<'a, TS, <CS as ConsAppend<C>>::Output>
where where
CS: ConsAppend<C>, CS: ConsAppend<C>,
<CS as ConsAppend<C>>::Output: ConsFlatten, <CS as ConsAppend<C>>::Output: ConsFlatten,
{ {
EntityBuilder { EntityBuilder {
cmd: self.cmd,
components: ConsAppend::append(self.components, component), components: ConsAppend::append(self.components, component),
entity: self.entity,
tags: self.tags, tags: self.tags,
} }
} }
/// Adds a tag to this builder, returning a new builder type containing that component type /// Adds a tag to this builder, returning a new builder type containing that component type
/// and its data. /// and its data.
pub fn with_tag<T: Tag>(self, tag: T) -> EntityBuilder<<TS as ConsAppend<T>>::Output, CS> pub fn with_tag<T: Tag>(self, tag: T) -> EntityBuilder<'a, <TS as ConsAppend<T>>::Output, CS>
where where
TS: ConsAppend<T>, TS: ConsAppend<T>,
<TS as ConsAppend<T>>::Output: ConsFlatten, <TS as ConsAppend<T>>::Output: ConsFlatten,
{ {
EntityBuilder { EntityBuilder {
cmd: self.cmd,
tags: ConsAppend::append(self.tags, tag), tags: ConsAppend::append(self.tags, tag),
entity: self.entity,
components: self.components, components: self.components,
} }
} }
/// Finalizes this builder type and submits it to the `CommandBuffer` as a `WorldWritable` trait /// Finalizes this builder type and submits it to the `CommandBuffer`.
/// object. pub fn build(self) -> Entity
pub fn build(self, buffer: &mut CommandBuffer)
where where
<TS as ConsFlatten>::Output: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>, <TS as ConsFlatten>::Output: TagSet + TagLayout + for<'b> Filter<ChunksetFilterData<'b>>,
ComponentTupleSet< ComponentTupleSet<
<CS as ConsFlatten>::Output, <CS as ConsFlatten>::Output,
std::iter::Once<<CS as ConsFlatten>::Output>, std::iter::Once<<CS as ConsFlatten>::Output>,
>: ComponentSource, >: ComponentSource,
{ {
buffer self.cmd.insert(
.commands self.tags.flatten(),
.get_mut() std::iter::once(self.components.flatten()),
.push_front(EntityCommand::WriteWorld(Arc::new(InsertBufferedCommand { )[0]
write_components: Vec::default(),
write_tags: Vec::default(),
tags: self.tags.flatten(),
components: IntoComponentSource::into(std::iter::once(self.components.flatten())),
entities: vec![self.entity],
})));
} }
} }
/// Errors returned by the `CommandBuffer`
#[derive(Debug)]
pub enum CommandError {
/// The command buffers entity cache has been exhausted. This is defaulted to 64 at `World::DEFAULT_COMMAND_BUFFER_SIZE`.
/// This upper limit can be changed via `SystemBuilder::with_command_buffer_size` for specific systems,
/// or globally via `World::set_command_buffer_size`.
EntityBlockFull,
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "CommandError") }
}
impl std::error::Error for CommandError {
fn cause(&self) -> Option<&dyn std::error::Error> { None }
}
/// A command buffer used to queue mutable changes to the world from a system. This buffer is automatically /// A command buffer used to queue mutable changes to the world from a system. This buffer is automatically
/// flushed and refreshed at the beginning of every frame by `Schedule`. If `Schedule` is not used, /// flushed and refreshed at the beginning of every frame by `Schedule`. If `Schedule` is not used,
/// then the user needs to manually flush it by performing `CommandBuffer::write`. /// then the user needs to manually flush it by performing `CommandBuffer::write`.
@ -295,71 +295,54 @@ impl std::error::Error for CommandError {
/// Inserting an entity using the `CommandBuffer`: /// Inserting an entity using the `CommandBuffer`:
/// ///
/// ``` /// ```
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position(f32); /// # struct Position(f32);
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Rotation(f32); /// # struct Rotation(f32);
/// # let universe = Universe::new(); /// # let universe = Universe::new();
/// # let mut world = universe.create_world(); /// # let mut world = universe.create_world();
/// let mut command_buffer = CommandBuffer::from_world(&mut world); /// let mut command_buffer = CommandBuffer::new(&world);
/// let entity = command_buffer.create_entity().unwrap(); /// let entity = command_buffer.start_entity().build();
/// ///
/// command_buffer.add_component(entity, Position(123.0)); /// command_buffer.add_component(entity, Position(123.0));
/// command_buffer.delete(entity); /// command_buffer.delete(entity);
/// ///
/// command_buffer.write(&mut world); /// command_buffer.write(&mut world);
/// ``` /// ```
#[derive(Default)]
pub struct CommandBuffer { pub struct CommandBuffer {
world_id: WorldId,
commands: AtomicRefCell<VecDeque<EntityCommand>>, commands: AtomicRefCell<VecDeque<EntityCommand>>,
entity_allocator: Option<Arc<EntityAllocator>>, entity_allocator: Arc<EntityAllocator>,
pub(crate) custom_capacity: Option<usize>, preallocated_capacity: usize,
pub(crate) free_list: SmallVec<[Entity; 64]>, free_list: SmallVec<[Entity; 64]>,
pub(crate) used_list: SmallVec<[Entity; 64]>, pending_insertion: SmallVec<[Entity; 64]>,
} }
// This is safe because only 1 system in 1 execution is only ever accessing a command buffer // This is safe because only 1 system in 1 execution is only ever accessing a command buffer
// and we garuntee the write operations of a command buffer occur in a safe manner // and we gaurantee the write operations of a command buffer occur in a safe manner
unsafe impl Send for CommandBuffer {} unsafe impl Send for CommandBuffer {}
unsafe impl Sync for CommandBuffer {} unsafe impl Sync for CommandBuffer {}
impl CommandBuffer { impl CommandBuffer {
/// Creates a `CommandBuffer` with a custom capacity of cached Entity's to be collected every frame. /// Creates a `CommandBuffer` with a custom capacity of cached Entity's to be collected every frame.
/// Allocating a command buffer in this manner will overwrite `World::set_command_buffer_size` and /// Allocating a command buffer in this manner will override `World::set_command_buffer_size` and
/// this system will always allocate the custom provide capacity of entities every frame.
///
/// # Notes
/// This function does not perform any actual entity preallocation. `ComamandBuffer:resize` or `CommandBuffer:write`
/// must be called before using the command buffer for the first time to make entities available.
pub fn with_capacity(capacity: usize) -> Self {
// Pull free entities from the world.
Self {
custom_capacity: Some(capacity),
free_list: SmallVec::with_capacity(capacity),
commands: Default::default(),
used_list: SmallVec::with_capacity(capacity),
entity_allocator: None,
}
}
/// Creates a `CommandBuffer` with a custom capacity of cached Entity's to be collected every frame.
/// Allocating a command buffer in this manner will overwrite `World::set_command_buffer_size` and
/// this system will always allocate the custom provide capacity of entities every frame. /// this system will always allocate the custom provide capacity of entities every frame.
/// ///
/// This constructor will preallocate the first round of entities needed from the world. /// This constructor will preallocate the first round of entities needed from the world.
pub fn from_world_with_capacity(world: &mut World, capacity: usize) -> Self { pub fn new_with_capacity(world: &World, capacity: usize) -> Self {
// Pull free entities from the world. // Pull free entities from the world.
let free_list = let free_list =
SmallVec::from_iter((0..capacity).map(|_| world.entity_allocator.create_entity())); SmallVec::from_iter((0..capacity).map(|_| world.entity_allocator.create_entity()));
Self { Self {
world_id: world.id(),
free_list, free_list,
custom_capacity: Some(capacity), preallocated_capacity: capacity,
commands: Default::default(), commands: Default::default(),
used_list: SmallVec::with_capacity(capacity), pending_insertion: SmallVec::new(),
entity_allocator: Some(world.entity_allocator.clone()), entity_allocator: world.entity_allocator.clone(),
} }
} }
@ -368,43 +351,44 @@ impl CommandBuffer {
/// value. /// value.
/// ///
/// This constructor will preallocate the first round of entities needed from the world. /// This constructor will preallocate the first round of entities needed from the world.
pub fn from_world(world: &mut World) -> Self { pub fn new(world: &World) -> Self {
// Pull free entities from the world.
let free_list = SmallVec::from_iter( let free_list = SmallVec::from_iter(
(0..world.command_buffer_size()).map(|_| world.entity_allocator.create_entity()), (0..world.command_buffer_size()).map(|_| world.entity_allocator.create_entity()),
); );
Self { Self {
world_id: world.id(),
free_list, free_list,
custom_capacity: None, preallocated_capacity: world.command_buffer_size(),
commands: Default::default(), commands: Default::default(),
used_list: SmallVec::with_capacity(world.command_buffer_size()), pending_insertion: SmallVec::new(),
entity_allocator: Some(world.entity_allocator.clone()), entity_allocator: world.entity_allocator.clone(),
} }
} }
/// Gets the ID of the world this command buffer belongs to.
pub fn world(&self) -> WorldId { self.world_id }
/// Changes the cached capacity of this `CommandBuffer` to the specified capacity. This includes shrinking /// Changes the cached capacity of this `CommandBuffer` to the specified capacity. This includes shrinking
/// and growing the allocated entities, and possibly returning them to the entity allocator in the /// and growing the allocated entities, and possibly returning them to the entity allocator in the
/// case of a shrink. /// case of a shrink.
/// ///
/// This function does *NOT* set the `CommandBuffer::custom_capacity` override. /// This function does *NOT* set the `CommandBuffer::custom_capacity` override.
#[allow(clippy::comparison_chain)] #[allow(clippy::comparison_chain)]
pub fn resize(&mut self, capacity: usize) { fn resize(&mut self) {
let allocator = &self.entity_allocator; let allocator = &self.entity_allocator;
let free_list = &mut self.free_list; let free_list = &mut self.free_list;
let capacity = self.preallocated_capacity;
if let Some(allocator) = allocator.as_ref() { if free_list.len() < capacity {
if free_list.len() < capacity { for entity in allocator.create_entities().take(capacity - free_list.len()) {
(free_list.len()..capacity).for_each(|_| free_list.push(allocator.create_entity())); free_list.push(entity);
} else if free_list.len() > capacity {
// Free the entities
(free_list.len() - capacity..capacity).for_each(|_| {
allocator.delete_entity(free_list.pop().unwrap());
});
} }
} else { } else if free_list.len() > capacity {
panic!("Entity allocator not assigned to command buffer") // Free the entities
(free_list.len() - capacity..capacity).for_each(|_| {
allocator.delete_entity(free_list.pop().unwrap());
});
} }
} }
@ -412,63 +396,47 @@ impl CommandBuffer {
/// ///
/// Command flushes are performed in a FIFO manner, allowing for reliable, linear commands being /// Command flushes are performed in a FIFO manner, allowing for reliable, linear commands being
/// executed in the order they were provided. /// executed in the order they were provided.
///
/// This function also calls `CommandBuffer:resize`, performing any appropriate entity preallocation,
/// refilling the entity cache of any consumed entities.
pub fn write(&mut self, world: &mut World) { pub fn write(&mut self, world: &mut World) {
tracing::trace!("Draining command buffer"); let span = span!(Level::TRACE, "Draining command buffer");
let _guard = span.enter();
if self.entity_allocator.is_none() { if self.world_id != world.id() {
self.entity_allocator = Some(world.entity_allocator.clone()); panic!("command buffers may only write into their parent world");
} }
let empty = Vec::from_iter((0..self.used_list.len()).map(|_| ()));
world.insert_buffered(
self.used_list.as_slice(),
(),
IntoComponentSource::into(empty),
);
self.used_list.clear();
while let Some(command) = self.commands.get_mut().pop_back() { while let Some(command) = self.commands.get_mut().pop_back() {
match command { match command {
EntityCommand::WriteWorld(ptr) => ptr.write(world), EntityCommand::WriteWorld(ptr) => ptr.write(world, self),
EntityCommand::ExecMutWorld(closure) => closure(world), EntityCommand::ExecMutWorld(closure) => closure(world),
EntityCommand::ExecWorld(closure) => closure(world), EntityCommand::ExecWorld(closure) => closure(world),
} }
} }
self.pending_insertion.clear();
// Refill our entity buffer from the world // Refill our entity buffer from the world
if let Some(custom_capacity) = self.custom_capacity { self.resize();
self.resize(custom_capacity);
} else {
self.resize(world.command_buffer_size());
}
} }
/// Consumed an internally cached entity, returning an `EntityBuilder` using that entity. /// Creates an entity builder for constructing a new entity.
pub fn build_entity(&mut self) -> Result<EntityBuilder<(), ()>, CommandError> { pub fn start_entity(&mut self) -> EntityBuilder<(), ()> {
let entity = self.create_entity()?; EntityBuilder {
cmd: self,
Ok(EntityBuilder {
entity,
tags: (), tags: (),
components: (), components: (),
}) }
} }
/// Consumed an internally cached entity, or returns `CommandError` /// Allocates a new entity.
pub fn create_entity(&mut self) -> Result<Entity, CommandError> { fn allocate_entity(&mut self) -> Entity {
if self.free_list.is_empty() { if self.free_list.is_empty() {
self.resize( self.resize();
self.custom_capacity
.unwrap_or(World::DEFAULT_COMMAND_BUFFER_SIZE),
);
} }
let entity = self.free_list.pop().ok_or(CommandError::EntityBlockFull)?; let entity = self
self.used_list.push(entity); .free_list
.pop()
Ok(entity) .unwrap_or_else(|| self.entity_allocator.create_entity());
self.pending_insertion.push(entity);
entity
} }
/// Executes an arbitrary closure against the mutable world, allowing for queued exclusive /// Executes an arbitrary closure against the mutable world, allowing for queued exclusive
@ -485,7 +453,7 @@ impl CommandBuffer {
/// Inserts an arbitrary implementor of the `WorldWritable` trait into the command queue. /// Inserts an arbitrary implementor of the `WorldWritable` trait into the command queue.
/// This can be leveraged for creating custom `WorldWritable` trait implementors, and is used /// This can be leveraged for creating custom `WorldWritable` trait implementors, and is used
/// internally for the default writers. /// internally for the default writers.
pub fn insert_writer<W>(&self, writer: W) fn insert_writer<W>(&self, writer: W)
where where
W: 'static + WorldWritable, W: 'static + WorldWritable,
{ {
@ -494,46 +462,23 @@ impl CommandBuffer {
.push_front(EntityCommand::WriteWorld(Arc::new(writer))); .push_front(EntityCommand::WriteWorld(Arc::new(writer)));
} }
/// Queues an *unbuffered* insertion into the world. This command follows the same syntax as
/// the normal `World::insert`, except for one caviate - entities are NOT returned by this
/// function, meaning that the internal entity cache and limits of this `CommandBuffer` are not
/// applicable to this function.
///
/// This function can be considered a "fire and forget" entity creation method which is not bound
/// by the standard command buffer size limits of the other entity insertion functions. This allows
/// for mass insertion of entities, exceeding the command buffer sizes, to occur in scenarios that
/// the entities do not need to be retrieved.
pub fn insert_unbuffered<T, C>(&mut self, tags: T, components: C)
where
T: 'static + TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: 'static + IntoComponentSource,
{
self.commands
.get_mut()
.push_front(EntityCommand::WriteWorld(Arc::new(InsertCommand {
write_components: Vec::default(),
write_tags: Vec::default(),
tags,
components,
})));
}
/// Queues an insertion into the world. This command follows the same syntax as /// Queues an insertion into the world. This command follows the same syntax as
/// the normal `World::insert`, returning the entities created for this command. /// the normal `World::insert`, returning the entities created for this command.
pub fn insert<T, C>(&mut self, tags: T, components: C) -> Result<Vec<Entity>, CommandError> pub fn insert<T, C>(&mut self, tags: T, components: C) -> &[Entity]
where where
T: 'static + TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>, T: 'static + TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: 'static + IntoComponentSource, C: 'static + IntoComponentSource,
{ {
let components = components.into(); let components = components.into();
if components.len() > self.free_list.len() { let start = self.pending_insertion.len();
return Err(CommandError::EntityBlockFull); let count = components.len();
self.pending_insertion.reserve(count);
for _ in 0..count {
self.allocate_entity();
} }
let mut entities = Vec::with_capacity(components.len()); let range = start..self.pending_insertion.len();
for _ in 0..components.len() {
entities.push(self.free_list.pop().ok_or(CommandError::EntityBlockFull)?);
}
self.commands self.commands
.get_mut() .get_mut()
@ -542,65 +487,43 @@ impl CommandBuffer {
write_tags: Vec::default(), write_tags: Vec::default(),
tags, tags,
components, components,
entities: entities.clone(), entities: range.clone(),
}))); })));
Ok(entities) &self.pending_insertion[range]
} }
/// Queues the deletion of an entity in the command buffer. This writer calls `World::delete` /// Queues the deletion of an entity in the command buffer. This writer calls `World::delete`
pub fn delete(&self, entity: Entity) { pub fn delete(&self, entity: Entity) { self.insert_writer(DeleteEntityCommand(entity)); }
self.commands
.get_mut()
.push_front(EntityCommand::WriteWorld(Arc::new(DeleteEntityCommand(
entity,
))));
}
/// Queues the addition of a component from an entity in the command buffer. /// Queues the addition of a component from an entity in the command buffer.
/// This writer calls `World::add_component` /// This writer calls `World::add_component`
pub fn add_component<C: Component>(&self, entity: Entity, component: C) { pub fn add_component<C: Component>(&self, entity: Entity, component: C) {
self.commands self.insert_writer(AddComponentCommand { entity, component });
.get_mut()
.push_front(EntityCommand::WriteWorld(Arc::new(AddComponentCommand {
entity,
component,
})));
} }
/// Queues the removal of a component from an entity in the command buffer. /// Queues the removal of a component from an entity in the command buffer.
/// This writer calls `World::remove_component` /// This writer calls `World::remove_component`
pub fn remove_component<C: Component>(&self, entity: Entity) { pub fn remove_component<C: Component>(&self, entity: Entity) {
self.commands self.insert_writer(RemoveComponentCommand {
.get_mut() entity,
.push_front(EntityCommand::WriteWorld(Arc::new( _marker: PhantomData::<C>::default(),
RemoveComponentCommand { });
entity,
_marker: PhantomData::<C>::default(),
},
)));
} }
/// Queues the addition of a tag from an entity in the command buffer. /// Queues the addition of a tag from an entity in the command buffer.
/// This writer calls `World::add_tag` /// This writer calls `World::add_tag`
pub fn add_tag<T: Tag>(&self, entity: Entity, tag: T) { pub fn add_tag<T: Tag>(&self, entity: Entity, tag: T) {
self.commands self.insert_writer(AddTagCommand { entity, tag });
.get_mut()
.push_front(EntityCommand::WriteWorld(Arc::new(AddTagCommand {
entity,
tag,
})));
} }
/// Queues the removal of a tag from an entity in the command buffer. /// Queues the removal of a tag from an entity in the command buffer.
/// This writer calls `World::remove_tag` /// This writer calls `World::remove_tag`
pub fn remove_tag<T: Tag>(&self, entity: Entity) { pub fn remove_tag<T: Tag>(&self, entity: Entity) {
self.commands self.insert_writer(RemoveTagCommand {
.get_mut() entity,
.push_front(EntityCommand::WriteWorld(Arc::new(RemoveTagCommand { _marker: PhantomData::<T>::default(),
entity, });
_marker: PhantomData::<T>::default(),
})));
} }
/// Returns the current number of commands already queued in this `CommandBuffer` instance. /// Returns the current number of commands already queued in this `CommandBuffer` instance.
@ -612,6 +535,18 @@ impl CommandBuffer {
pub fn is_empty(&self) -> bool { self.commands.get().len() == 0 } pub fn is_empty(&self) -> bool { self.commands.get().len() == 0 }
} }
impl Drop for CommandBuffer {
fn drop(&mut self) {
while let Some(entity) = self.free_list.pop() {
self.entity_allocator.delete_entity(entity);
}
while let Some(entity) = self.pending_insertion.pop() {
self.entity_allocator.delete_entity(entity);
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -625,7 +560,7 @@ mod tests {
struct TestResource(pub i32); struct TestResource(pub i32);
#[test] #[test]
fn create_entity_test() -> Result<(), CommandError> { fn create_entity_test() {
let _ = tracing_subscriber::fmt::try_init(); let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new(); let universe = Universe::new();
@ -638,9 +573,9 @@ mod tests {
let components_len = components.len(); let components_len = components.len();
//world.entity_allocator.get_block() //world.entity_allocator.get_block()
let mut command = CommandBuffer::from_world(&mut world); let mut command = CommandBuffer::new(&world);
let entity1 = command.create_entity()?; let entity1 = command.start_entity().build();
let entity2 = command.create_entity()?; let entity2 = command.start_entity().build();
command.add_component(entity1, Pos(1., 2., 3.)); command.add_component(entity1, Pos(1., 2., 3.));
command.add_component(entity2, Pos(4., 5., 6.)); command.add_component(entity2, Pos(4., 5., 6.));
@ -650,17 +585,15 @@ mod tests {
let query = Read::<Pos>::query(); let query = Read::<Pos>::query();
let mut count = 0; let mut count = 0;
for _ in query.iter_entities(&mut world) { for _ in query.iter_entities(&world) {
count += 1; count += 1;
} }
assert_eq!(components_len, count); assert_eq!(components_len, count);
Ok(())
} }
#[test] #[test]
fn simple_write_test() -> Result<(), CommandError> { fn simple_write_test() {
let _ = tracing_subscriber::fmt::try_init(); let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new(); let universe = Universe::new();
@ -673,8 +606,8 @@ mod tests {
let components_len = components.len(); let components_len = components.len();
//world.entity_allocator.get_block() //world.entity_allocator.get_block()
let mut command = CommandBuffer::from_world(&mut world); let mut command = CommandBuffer::new(&world);
let _ = command.insert((), components)?; let _ = command.insert((), components);
// Assert writing checks // Assert writing checks
// TODO: // TODO:
@ -693,7 +626,5 @@ mod tests {
} }
assert_eq!(components_len, count); assert_eq!(components_len, count);
Ok(())
} }
} }

View file

@ -1,153 +1,155 @@
// Things happen here, and they work. // Things happen here, and they work.
// ,---. // ,---.
// / | // / |
// / | // / |
// / | // / |
// / | // / |
// ___,' | // ___,' |
// < -' : // < -' :
// `-.__..--'``-,_\_ // `-.__..--'``-,_\_
// |o/ ` :,.)_`> // |o/ ` :,.)_`>
// :/ ` ||/) // :/ ` ||/)
// (_.).__,-` |\ // (_.).__,-` |\
// /( `.`` `| : // /( `.`` `| :
// \'`-.) ` ; ; // \'`-.) ` ; ;
// | ` /-< // | ` /-<
// | ` / `. // | ` / `.
// ,-_-..____ /| ` :__..-'\ // ,-_-..____ /| ` :__..-'\
// ,'-.__\\ ``-./ :` ; \ // ,'-.__\\ ``-./ :` ; \
//`\ `\ `\\ \ : ( ` / , `. \ //`\ `\ `\\ \ : ( ` / , `. \
// \` \ \\ | | ` : : .\ \ // \` \ \\ | | ` : : .\ \
// \ `\_ )) : ; | | ): : // \ `\_ )) : ; | | ): :
// (`-.-'\ || |\ \ ` ; ; | | // (`-.-'\ || |\ \ ` ; ; | |
// \-_ `;;._ ( ` / /_ | | // \-_ `;;._ ( ` / /_ | |
// `-.-.// ,'`-._\__/_,' ; | // `-.-.// ,'`-._\__/_,' ; |
// \:: : / ` , / | // \:: : / ` , / |
// || | ( ,' / / | // || | ( ,' / / |
// || ,' / | // || ,' / |
/// Prepend a new type into a cons list /// Prepend a new type into a cons list
pub trait ConsPrepend<T> { pub trait ConsPrepend<T> {
/// Result of prepend /// Result of prepend
type Output; type Output;
/// Prepend to runtime cons value /// Prepend to runtime cons value
fn prepend(self, t: T) -> Self::Output; fn prepend(self, t: T) -> Self::Output;
} }
impl<T> ConsPrepend<T> for () { impl<T> ConsPrepend<T> for () {
type Output = (T, Self); type Output = (T, Self);
fn prepend(self, t: T) -> Self::Output { (t, self) } fn prepend(self, t: T) -> Self::Output { (t, self) }
} }
impl<T, A, B> ConsPrepend<T> for (A, B) { impl<T, A, B> ConsPrepend<T> for (A, B) {
type Output = (T, Self); type Output = (T, Self);
fn prepend(self, t: T) -> Self::Output { (t, self) } fn prepend(self, t: T) -> Self::Output { (t, self) }
} }
/// Prepend a new type into a cons list /// Prepend a new type into a cons list
pub trait ConsAppend<T> { pub trait ConsAppend<T> {
/// Result of append /// Result of append
type Output; type Output;
/// Prepend to runtime cons value /// Prepend to runtime cons value
fn append(self, t: T) -> Self::Output; fn append(self, t: T) -> Self::Output;
} }
impl<T> ConsAppend<T> for () { impl<T> ConsAppend<T> for () {
type Output = (T, Self); type Output = (T, Self);
fn append(self, t: T) -> Self::Output { (t, ()) } fn append(self, t: T) -> Self::Output { (t, ()) }
} }
impl<T, A, B: ConsAppend<T>> ConsAppend<T> for (A, B) { impl<T, A, B: ConsAppend<T>> ConsAppend<T> for (A, B) {
type Output = (A, <B as ConsAppend<T>>::Output); type Output = (A, <B as ConsAppend<T>>::Output);
fn append(self, t: T) -> Self::Output { fn append(self, t: T) -> Self::Output {
let (a, b) = self; let (a, b) = self;
(a, b.append(t)) (a, b.append(t))
} }
} }
/// transform cons list into a flat tuple /// transform cons list into a flat tuple
pub trait ConsFlatten { pub trait ConsFlatten {
/// Flattened tuple /// Flattened tuple
type Output; type Output;
/// Flatten runtime cons value /// Flatten runtime cons value
fn flatten(self) -> Self::Output; fn flatten(self) -> Self::Output;
} }
impl ConsFlatten for () { impl ConsFlatten for () {
type Output = (); type Output = ();
fn flatten(self) -> Self::Output { self } fn flatten(self) -> Self::Output { self }
} }
macro_rules! cons { macro_rules! cons {
() => ( () => (
() ()
); );
($head:tt) => ( ($head:tt) => (
($head, ()) ($head, ())
); );
($head:tt, $($tail:tt),*) => ( ($head:tt, $($tail:tt),*) => (
($head, cons!($($tail),*)) ($head, cons!($($tail),*))
); );
} }
macro_rules! impl_flatten { macro_rules! impl_flatten {
($($items:ident),*) => { ($($items:ident),*) => {
#[allow(unused_parens)] // This is added because the nightly compiler complains #[allow(unused_parens)] // This is added because the nightly compiler complains
impl<$($items),*> ConsFlatten for cons!($($items),*) impl<$($items),*> ConsFlatten for cons!($($items),*)
{ {
type Output = ($($items),*); type Output = ($($items),*);
fn flatten(self) -> Self::Output { fn flatten(self) -> Self::Output {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let cons!($($items),*) = self; let cons!($($items),*) = self;
($($items),*) ($($items),*)
} }
} }
impl_flatten!(@ $($items),*); impl_flatten!(@ $($items),*);
}; };
(@ $head:ident, $($tail:ident),*) => { (@ $head:ident, $($tail:ident),*) => {
impl_flatten!($($tail),*); impl_flatten!($($tail),*);
}; };
(@ $head:ident) => {}; (@ $head:ident) => {};
} }
impl_flatten!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); impl_flatten!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
fn test_api() {} fn test_api() {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn cons_macro() { fn cons_macro() {
assert_eq!(cons!(), ()); #![allow(clippy::unit_cmp)]
assert_eq!(cons!(1), (1, ())); assert_eq!(cons!(), ());
assert_eq!(cons!(1, 2, 3, 4), (1, (2, (3, (4, ()))))); assert_eq!(cons!(1), (1, ()));
} assert_eq!(cons!(1, 2, 3, 4), (1, (2, (3, (4, ())))));
}
#[test]
fn cons_prepend() { #[test]
assert_eq!(().prepend(123), (123, ())); fn cons_prepend() {
assert_eq!( assert_eq!(().prepend(123), (123, ()));
cons!(1, 2, 3, 4, 5).prepend(123).prepend(15), assert_eq!(
cons!(15, 123, 1, 2, 3, 4, 5) cons!(1, 2, 3, 4, 5).prepend(123).prepend(15),
); cons!(15, 123, 1, 2, 3, 4, 5)
} );
}
#[test]
fn cons_append() { #[test]
assert_eq!(().append(123), (123, ())); fn cons_append() {
assert_eq!( assert_eq!(().append(123), (123, ()));
cons!(1, 2, 3, 4, 5).append(123).append(15), assert_eq!(
cons!(1, 2, 3, 4, 5, 123, 15) cons!(1, 2, 3, 4, 5).append(123).append(15),
); cons!(1, 2, 3, 4, 5, 123, 15)
} );
}
#[test]
fn cons_flatten() { #[test]
assert_eq!(().flatten(), ()); fn cons_flatten() {
assert_eq!((1, ()).flatten(), 1); #![allow(clippy::unit_cmp)]
assert_eq!(cons!(1, 2, 3, 4, 5).flatten(), (1, 2, 3, 4, 5)); assert_eq!(().flatten(), ());
} assert_eq!((1, ()).flatten(), 1);
} assert_eq!(cons!(1, 2, 3, 4, 5).flatten(), (1, 2, 3, 4, 5));
}
}

View file

@ -1,9 +1,15 @@
use parking_lot::{Mutex, RwLock}; use crate::index::ArchetypeIndex;
use crate::index::ChunkIndex;
use crate::index::ComponentIndex;
use crate::index::SetIndex;
use parking_lot::{Mutex, RwLock, RwLockWriteGuard};
use std::fmt::Display; use std::fmt::Display;
use std::num::Wrapping; use std::num::Wrapping;
use std::ops::Deref;
use std::ops::DerefMut;
use std::sync::Arc; use std::sync::Arc;
pub(crate) type EntityIndex = u32; pub type EntityIndex = u32;
pub(crate) type EntityVersion = Wrapping<u32>; pub(crate) type EntityVersion = Wrapping<u32>;
/// A handle to an entity. /// A handle to an entity.
@ -18,7 +24,7 @@ impl Entity {
Entity { index, version } Entity { index, version }
} }
pub(crate) fn index(self) -> EntityIndex { self.index } pub fn index(self) -> EntityIndex { self.index }
} }
impl Display for Entity { impl Display for Entity {
@ -28,19 +34,19 @@ impl Display for Entity {
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct EntityLocation { pub struct EntityLocation {
archetype_index: usize, archetype_index: ArchetypeIndex,
set_index: usize, set_index: SetIndex,
chunk_index: usize, chunk_index: ChunkIndex,
component_index: usize, component_index: ComponentIndex,
} }
impl EntityLocation { impl EntityLocation {
pub(crate) fn new( pub(crate) fn new(
archetype_index: usize, archetype_index: ArchetypeIndex,
set_index: usize, set_index: SetIndex,
chunk_index: usize, chunk_index: ChunkIndex,
component_index: usize, component_index: ComponentIndex,
) -> Self { ) -> Self {
EntityLocation { EntityLocation {
archetype_index, archetype_index,
@ -50,13 +56,58 @@ impl EntityLocation {
} }
} }
pub(crate) fn archetype(&self) -> usize { self.archetype_index } pub fn archetype(&self) -> ArchetypeIndex { self.archetype_index }
pub(crate) fn set(&self) -> usize { self.set_index } pub fn set(&self) -> SetIndex { self.set_index }
pub(crate) fn chunk(&self) -> usize { self.chunk_index } pub fn chunk(&self) -> ChunkIndex { self.chunk_index }
pub(crate) fn component(&self) -> usize { self.component_index } pub fn component(&self) -> ComponentIndex { self.component_index }
}
pub(crate) struct Locations {
blocks: Vec<Option<Vec<EntityLocation>>>,
}
impl Locations {
pub fn new() -> Self { Locations { blocks: Vec::new() } }
fn index(entity: EntityIndex) -> (usize, usize) {
let block = entity as usize / BlockAllocator::BLOCK_SIZE;
let index = entity as usize - block * BlockAllocator::BLOCK_SIZE;
(block, index)
}
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
let (block, index) = Locations::index(entity.index());
self.blocks
.get(block)
.map(|b| b.as_ref())
.flatten()
.map(|b| b[index])
}
pub fn set(&mut self, entity: Entity, location: EntityLocation) {
let (block_index, index) = Locations::index(entity.index());
if self.blocks.len() <= block_index {
let fill = block_index - self.blocks.len() + 1;
self.blocks.extend((0..fill).map(|_| None));
}
let block_opt = &mut self.blocks[block_index];
let block = block_opt.get_or_insert_with(|| {
std::iter::repeat(EntityLocation::new(
ArchetypeIndex(0),
SetIndex(0),
ChunkIndex(0),
ComponentIndex(0),
))
.take(BlockAllocator::BLOCK_SIZE)
.collect()
});
block[index] = location;
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -89,12 +140,11 @@ impl BlockAllocator {
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct EntityBlock { pub struct EntityBlock {
start: EntityIndex, start: EntityIndex,
len: usize, len: usize,
versions: Vec<EntityVersion>, versions: Vec<EntityVersion>,
free: Vec<EntityIndex>, free: Vec<EntityIndex>,
locations: Vec<EntityLocation>,
} }
impl EntityBlock { impl EntityBlock {
@ -104,9 +154,6 @@ impl EntityBlock {
len, len,
versions: Vec::with_capacity(len), versions: Vec::with_capacity(len),
free: Vec::new(), free: Vec::new(),
locations: std::iter::repeat(EntityLocation::new(0, 0, 0, 0))
.take(len)
.collect(),
} }
} }
@ -138,45 +185,80 @@ impl EntityBlock {
} }
} }
pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> { pub fn free(&mut self, entity: Entity) -> bool {
if let Some(true) = self.is_alive(entity) { if let Some(true) = self.is_alive(entity) {
let i = self.index(entity.index); let i = self.index(entity.index);
self.versions[i] += Wrapping(1); self.versions[i] += Wrapping(1);
self.free.push(entity.index); self.free.push(entity.index);
self.get_location(entity.index) true
} else { } else {
None false
} }
} }
}
pub fn set_location(&mut self, entity: EntityIndex, location: EntityLocation) {
assert!(entity >= self.start); #[derive(Debug)]
let index = (entity - self.start) as usize; struct Blocks {
*self.locations.get_mut(index).unwrap() = location; blocks: Vec<Option<EntityBlock>>,
} }
pub fn get_location(&self, entity: EntityIndex) -> Option<EntityLocation> { impl Blocks {
if entity < self.start { fn new() -> Self { Self { blocks: Vec::new() } }
return None;
} pub fn index(entity: EntityIndex) -> usize { entity as usize / BlockAllocator::BLOCK_SIZE }
let index = (entity - self.start) as usize; fn find(&self, entity: EntityIndex) -> Option<&EntityBlock> {
self.locations.get(index).copied() let i = Blocks::index(entity);
} self.blocks.get(i).map(|b| b.as_ref()).flatten()
}
fn find_mut(&mut self, entity: EntityIndex) -> Option<&mut EntityBlock> {
let i = Blocks::index(entity);
self.blocks.get_mut(i).map(|b| b.as_mut()).flatten()
}
fn push(&mut self, block: EntityBlock) -> usize {
let i = Blocks::index(block.start);
if self.blocks.len() > i {
self.blocks[i] = Some(block);
} else {
let fill = i - self.blocks.len();
self.blocks.extend((0..fill).map(|_| None));
self.blocks.push(Some(block));
}
i
}
fn append(&mut self, other: &mut Blocks) {
for block in other.blocks.drain(..) {
if let Some(block) = block {
self.push(block);
}
}
}
}
impl Deref for Blocks {
type Target = [Option<EntityBlock>];
fn deref(&self) -> &Self::Target { self.blocks.deref() }
}
impl DerefMut for Blocks {
fn deref_mut(&mut self) -> &mut Self::Target { self.blocks.deref_mut() }
} }
/// Manages the allocation and deletion of `Entity` IDs within a world. /// Manages the allocation and deletion of `Entity` IDs within a world.
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct EntityAllocator { pub struct EntityAllocator {
allocator: Arc<Mutex<BlockAllocator>>, allocator: Arc<Mutex<BlockAllocator>>,
blocks: Arc<RwLock<Vec<EntityBlock>>>, blocks: RwLock<Blocks>,
} }
impl EntityAllocator { impl EntityAllocator {
pub(crate) fn new(allocator: Arc<Mutex<BlockAllocator>>) -> Self { pub(crate) fn new(allocator: Arc<Mutex<BlockAllocator>>) -> Self {
EntityAllocator { EntityAllocator {
allocator, allocator,
blocks: Arc::new(RwLock::new(Vec::new())), blocks: RwLock::new(Blocks::new()),
} }
} }
@ -184,59 +266,99 @@ impl EntityAllocator {
pub fn is_alive(&self, entity: Entity) -> bool { pub fn is_alive(&self, entity: Entity) -> bool {
self.blocks self.blocks
.read() .read()
.iter() .find(entity.index())
.filter_map(|b| b.is_alive(entity)) .map(|b| b.is_alive(entity))
.nth(0) .flatten()
.unwrap_or(false) .unwrap_or(false)
} }
/// Allocates a new unused `Entity` ID. /// Allocates a new unused `Entity` ID.
pub fn create_entity(&self) -> Entity { pub fn create_entity(&self) -> Entity { self.create_entities().next().unwrap() }
let mut blocks = self.blocks.write();
if let Some(entity) = blocks.iter_mut().rev().filter_map(|b| b.allocate()).nth(0) { /// Creates an iterator which allocates new `Entity` IDs.
entity pub fn create_entities(&self) -> CreateEntityIter {
} else { CreateEntityIter {
let mut block = self.allocator.lock().allocate(); blocks: self.blocks.write(),
let entity = block.allocate().unwrap(); allocator: &self.allocator,
blocks.push(block); current_block: None,
entity
} }
} }
pub(crate) fn delete_entity(&self, entity: Entity) -> Option<EntityLocation> { pub(crate) fn delete_entity(&self, entity: Entity) -> bool {
self.blocks.write().iter_mut().find_map(|b| b.free(entity))
}
pub(crate) fn set_location(&self, entity: EntityIndex, location: EntityLocation) {
self.blocks self.blocks
.write() .write()
.iter_mut() .find_mut(entity.index())
.rev() .map(|b| b.free(entity))
.find(|b| b.in_range(entity)) .unwrap_or(false)
.unwrap()
.set_location(entity, location);
} }
pub(crate) fn get_location(&self, entity: EntityIndex) -> Option<EntityLocation> { pub(crate) fn delete_all_entities(&self) {
self.blocks for block in self.blocks.write().blocks.drain(..) {
.read() if let Some(mut block) = block {
.iter() // If any entity in the block is in an allocated state, clear
.find(|b| b.in_range(entity)) // and repopulate the free list. This forces all entities into an
.and_then(|b| b.get_location(entity)) // unallocated state. Bump versions of all entity indexes to
// ensure that we don't reuse the same entity.
if block.free.len() < block.versions.len() {
block.free.clear();
for (i, version) in block.versions.iter_mut().enumerate() {
*version += Wrapping(1);
block.free.push(i as u32 + block.start);
}
}
self.allocator.lock().free(block);
}
}
} }
pub(crate) fn merge(&self, other: EntityAllocator) { pub(crate) fn merge(&self, other: EntityAllocator) {
assert!(Arc::ptr_eq(&self.allocator, &other.allocator)); assert!(Arc::ptr_eq(&self.allocator, &other.allocator));
self.blocks.write().append(&mut other.blocks.write()); self.blocks.write().append(&mut *other.blocks.write());
} }
} }
impl Drop for EntityAllocator { impl Drop for EntityAllocator {
fn drop(&mut self) { fn drop(&mut self) { self.delete_all_entities(); }
for block in self.blocks.write().drain(..) { }
self.allocator.lock().free(block);
pub struct CreateEntityIter<'a> {
current_block: Option<usize>,
blocks: RwLockWriteGuard<'a, Blocks>,
allocator: &'a Mutex<BlockAllocator>,
}
impl<'a> Iterator for CreateEntityIter<'a> {
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
// try and allocate from the block we last used
if let Some(block) = self.current_block {
if let Some(entity) = self.blocks[block].as_mut().unwrap().allocate() {
return Some(entity);
}
} }
// search for a block with spare entities
for (i, allocated) in self
.blocks
.iter_mut()
.enumerate()
.rev()
.filter(|(_, b)| b.is_some())
.map(|(i, b)| (i, b.as_mut().unwrap().allocate()))
{
if let Some(entity) = allocated {
self.current_block = Some(i);
return Some(entity);
}
}
// allocate a new block
let mut block = self.allocator.lock().allocate();
let entity = block.allocate().unwrap();
self.current_block = Some(self.blocks.push(block));
Some(entity)
} }
} }
@ -311,7 +433,7 @@ mod tests {
let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new()))); let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
let entity = allocator.create_entity(); let entity = allocator.create_entity();
assert_eq!(true, allocator.delete_entity(entity).is_some()); assert_eq!(true, allocator.delete_entity(entity));
} }
#[test] #[test]
@ -320,7 +442,7 @@ mod tests {
let entity = allocator.create_entity(); let entity = allocator.create_entity();
allocator.delete_entity(entity); allocator.delete_entity(entity);
assert_eq!(None, allocator.delete_entity(entity)); assert_eq!(false, allocator.delete_entity(entity));
} }
#[test] #[test]
@ -328,14 +450,14 @@ mod tests {
let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new()))); let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
let entity = Entity::new(10 as EntityIndex, Wrapping(10)); let entity = Entity::new(10 as EntityIndex, Wrapping(10));
assert_eq!(None, allocator.delete_entity(entity)); assert_eq!(false, allocator.delete_entity(entity));
} }
#[test] #[test]
fn multiple_allocators_unique_ids() { fn multiple_allocators_unique_ids() {
let blocks = Arc::from(Mutex::new(BlockAllocator::new())); let blocks = Arc::from(Mutex::new(BlockAllocator::new()));
let allocator_a = EntityAllocator::new(blocks.clone()); let allocator_a = EntityAllocator::new(blocks.clone());
let allocator_b = EntityAllocator::new(blocks.clone()); let allocator_b = EntityAllocator::new(blocks);
let mut entities_a = HashSet::<Entity>::default(); let mut entities_a = HashSet::<Entity>::default();
let mut entities_b = HashSet::<Entity>::default(); let mut entities_b = HashSet::<Entity>::default();

View file

@ -2,6 +2,9 @@ use crate::entity::Entity;
use crate::filter::{ use crate::filter::{
ArchetypeFilterData, ChunkFilterData, ChunksetFilterData, EntityFilter, Filter, FilterResult, ArchetypeFilterData, ChunkFilterData, ChunksetFilterData, EntityFilter, Filter, FilterResult,
}; };
use crate::index::ArchetypeIndex;
use crate::index::ChunkIndex;
use crate::index::SetIndex;
use crate::storage::ArchetypeId; use crate::storage::ArchetypeId;
use crate::storage::ChunkId; use crate::storage::ChunkId;
use crossbeam_channel::{Sender, TrySendError}; use crossbeam_channel::{Sender, TrySendError};
@ -21,15 +24,19 @@ pub enum Event {
} }
pub(crate) trait EventFilter: Send + Sync + 'static { pub(crate) trait EventFilter: Send + Sync + 'static {
fn matches_archetype(&self, data: ArchetypeFilterData, index: usize) -> bool; fn matches_archetype(&self, data: ArchetypeFilterData, index: ArchetypeIndex) -> bool;
fn matches_chunkset(&self, data: ChunksetFilterData, index: usize) -> bool; fn matches_chunkset(&self, data: ChunksetFilterData, index: SetIndex) -> bool;
fn matches_chunk(&self, data: ChunkFilterData, index: usize) -> bool; fn matches_chunk(&self, data: ChunkFilterData, index: ChunkIndex) -> bool;
} }
pub(crate) struct EventFilterWrapper<T: EntityFilter + Sync + 'static>(pub T); pub(crate) struct EventFilterWrapper<T: EntityFilter + Sync + 'static>(pub T);
impl<T: EntityFilter + Sync + 'static> EventFilter for EventFilterWrapper<T> { impl<T: EntityFilter + Sync + 'static> EventFilter for EventFilterWrapper<T> {
fn matches_archetype(&self, data: ArchetypeFilterData, index: usize) -> bool { fn matches_archetype(
&self,
data: ArchetypeFilterData,
ArchetypeIndex(index): ArchetypeIndex,
) -> bool {
let (filter, _, _) = self.0.filters(); let (filter, _, _) = self.0.filters();
if let Some(element) = filter.collect(data).nth(index) { if let Some(element) = filter.collect(data).nth(index) {
return filter.is_match(&element).is_pass(); return filter.is_match(&element).is_pass();
@ -38,7 +45,7 @@ impl<T: EntityFilter + Sync + 'static> EventFilter for EventFilterWrapper<T> {
false false
} }
fn matches_chunkset(&self, data: ChunksetFilterData, index: usize) -> bool { fn matches_chunkset(&self, data: ChunksetFilterData, SetIndex(index): SetIndex) -> bool {
let (_, filter, _) = self.0.filters(); let (_, filter, _) = self.0.filters();
if let Some(element) = filter.collect(data).nth(index) { if let Some(element) = filter.collect(data).nth(index) {
return filter.is_match(&element).is_pass(); return filter.is_match(&element).is_pass();
@ -47,7 +54,7 @@ impl<T: EntityFilter + Sync + 'static> EventFilter for EventFilterWrapper<T> {
false false
} }
fn matches_chunk(&self, data: ChunkFilterData, index: usize) -> bool { fn matches_chunk(&self, data: ChunkFilterData, ChunkIndex(index): ChunkIndex) -> bool {
let (_, _, filter) = self.0.filters(); let (_, _, filter) = self.0.filters();
if let Some(element) = filter.collect(data).nth(index) { if let Some(element) = filter.collect(data).nth(index) {
return filter.is_match(&element).is_pass(); return filter.is_match(&element).is_pass();
@ -93,7 +100,7 @@ impl Subscribers {
} }
} }
pub fn matches_archetype(&self, data: ArchetypeFilterData, index: usize) -> Self { pub fn matches_archetype(&self, data: ArchetypeFilterData, index: ArchetypeIndex) -> Self {
let subscribers = self let subscribers = self
.subscribers .subscribers
.iter() .iter()
@ -103,7 +110,7 @@ impl Subscribers {
Self { subscribers } Self { subscribers }
} }
pub fn matches_chunkset(&self, data: ChunksetFilterData, index: usize) -> Self { pub fn matches_chunkset(&self, data: ChunksetFilterData, index: SetIndex) -> Self {
let subscribers = self let subscribers = self
.subscribers .subscribers
.iter() .iter()
@ -113,7 +120,7 @@ impl Subscribers {
Self { subscribers } Self { subscribers }
} }
pub fn matches_chunk(&self, data: ChunkFilterData, index: usize) -> Self { pub fn matches_chunk(&self, data: ChunkFilterData, index: ChunkIndex) -> Self {
let subscribers = self let subscribers = self
.subscribers .subscribers
.iter() .iter()

View file

@ -1,3 +1,6 @@
use crate::index::ArchetypeIndex;
use crate::index::ChunkIndex;
use crate::index::SetIndex;
use crate::iterator::FissileZip; use crate::iterator::FissileZip;
use crate::iterator::SliceVecIter; use crate::iterator::SliceVecIter;
use crate::storage::ArchetypeData; use crate::storage::ArchetypeData;
@ -364,12 +367,12 @@ pub struct FilterArchIter<'a, 'b, F: Filter<ArchetypeFilterData<'a>>> {
} }
impl<'a, 'b, F: Filter<ArchetypeFilterData<'a>>> Iterator for FilterArchIter<'a, 'b, F> { impl<'a, 'b, F: Filter<ArchetypeFilterData<'a>>> Iterator for FilterArchIter<'a, 'b, F> {
type Item = usize; type Item = ArchetypeIndex;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some((i, data)) = self.archetypes.next() { if let Some((i, data)) = self.archetypes.next() {
if self.filter.is_match(&data).is_pass() { if self.filter.is_match(&data).is_pass() {
return Some(i); return Some(ArchetypeIndex(i));
} }
} }
@ -384,12 +387,12 @@ pub struct FilterChunkIter<'a, 'b, F: Filter<ChunksetFilterData<'a>>> {
} }
impl<'a, 'b, F: Filter<ChunksetFilterData<'a>>> Iterator for FilterChunkIter<'a, 'b, F> { impl<'a, 'b, F: Filter<ChunksetFilterData<'a>>> Iterator for FilterChunkIter<'a, 'b, F> {
type Item = usize; type Item = SetIndex;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some((i, data)) = self.chunks.next() { if let Some((i, data)) = self.chunks.next() {
if self.filter.is_match(&data).is_pass() { if self.filter.is_match(&data).is_pass() {
return Some(i); return Some(SetIndex(i));
} }
} }
@ -414,14 +417,14 @@ pub struct FilterEntityIter<
impl<'a, 'b, Arch: Filter<ArchetypeFilterData<'a>>, Chunk: Filter<ChunksetFilterData<'a>>> Iterator impl<'a, 'b, Arch: Filter<ArchetypeFilterData<'a>>, Chunk: Filter<ChunksetFilterData<'a>>> Iterator
for FilterEntityIter<'a, 'b, Arch, Chunk> for FilterEntityIter<'a, 'b, Arch, Chunk>
{ {
type Item = (ArchetypeId, usize); type Item = (ArchetypeId, ChunkIndex);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {
if let Some((arch_id, ref mut chunks)) = self.chunks { if let Some((arch_id, ref mut chunks)) = self.chunks {
for (chunk_index, chunk_data) in chunks { for (chunk_index, chunk_data) in chunks {
if self.chunk_filter.is_match(&chunk_data).is_pass() { if self.chunk_filter.is_match(&chunk_data).is_pass() {
return Some((arch_id, chunk_index)); return Some((arch_id, ChunkIndex(chunk_index)));
} }
} }
} }
@ -794,6 +797,12 @@ impl_and_filter!(A => a, B => b, C => c);
impl_and_filter!(A => a, B => b, C => c, D => d); impl_and_filter!(A => a, B => b, C => c, D => d);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e); impl_and_filter!(A => a, B => b, C => c, D => d, E => e);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f); impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k);
impl_and_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l);
/// A filter which requires that any filter within `T` match. /// A filter which requires that any filter within `T` match.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -889,6 +898,12 @@ impl_or_filter!(A => a, B => b, C => c);
impl_or_filter!(A => a, B => b, C => c, D => d); impl_or_filter!(A => a, B => b, C => c, D => d);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e); impl_or_filter!(A => a, B => b, C => c, D => d, E => e);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f); impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k);
impl_or_filter!(A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l);
/// A filter qhich requires that all chunks contain entity data components of type `T`. /// A filter qhich requires that all chunks contain entity data components of type `T`.
#[derive(Debug)] #[derive(Debug)]

View file

@ -0,0 +1,59 @@
use crate::entity::Entity;
use crate::storage::ArchetypeData;
use crate::storage::Chunkset;
use crate::storage::ComponentStorage;
use std::fmt;
use std::ops::Deref;
use std::ops::Index;
use std::ops::IndexMut;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct SetIndex(pub usize);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ChunkIndex(pub usize);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ArchetypeIndex(pub usize);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ComponentIndex(pub usize);
macro_rules! impl_index {
($index_ty:ty: $output_ty:ty) => {
impl Index<$index_ty> for [$output_ty] {
type Output = $output_ty;
#[inline(always)]
fn index(&self, index: $index_ty) -> &Self::Output { &self[index.0] }
}
impl IndexMut<$index_ty> for [$output_ty] {
#[inline(always)]
fn index_mut(&mut self, index: $index_ty) -> &mut Self::Output { &mut self[index.0] }
}
impl Index<$index_ty> for Vec<$output_ty> {
type Output = $output_ty;
#[inline(always)]
fn index(&self, index: $index_ty) -> &Self::Output { &self[index.0] }
}
impl IndexMut<$index_ty> for Vec<$output_ty> {
#[inline(always)]
fn index_mut(&mut self, index: $index_ty) -> &mut Self::Output { &mut self[index.0] }
}
impl Deref for $index_ty {
type Target = usize;
#[inline(always)]
fn deref(&self) -> &usize { &self.0 }
}
impl fmt::Display for $index_ty {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
};
}
impl_index!(SetIndex: Chunkset);
impl_index!(ChunkIndex: ComponentStorage);
impl_index!(ArchetypeIndex: ArchetypeData);
impl_index!(ComponentIndex: Entity);

View file

@ -0,0 +1,30 @@
#![allow(dead_code)]
pub mod borrow;
pub mod command;
pub mod cons;
pub mod entity;
pub mod event;
pub mod filter;
pub mod index;
pub mod iterator;
pub mod query;
pub mod storage;
pub mod world;
#[cfg(feature = "serialize")]
pub mod serialize;
mod tuple;
mod zip;
pub mod prelude {
pub use crate::{
command::CommandBuffer,
entity::Entity,
event::Event,
filter::filter_fns::*,
query::{IntoQuery, Query, Read, Tagged, TryRead, TryWrite, Write},
world::{Universe, World},
};
}

View file

@ -16,6 +16,8 @@ use crate::filter::Filter;
use crate::filter::FilterResult; use crate::filter::FilterResult;
use crate::filter::Passthrough; use crate::filter::Passthrough;
use crate::filter::TagFilter; use crate::filter::TagFilter;
use crate::index::ChunkIndex;
use crate::index::SetIndex;
#[cfg(feature = "par-iter")] #[cfg(feature = "par-iter")]
use crate::iterator::{FissileEnumerate, FissileIterator}; use crate::iterator::{FissileEnumerate, FissileIterator};
use crate::storage::ArchetypeData; use crate::storage::ArchetypeData;
@ -51,7 +53,8 @@ pub trait View<'a>: Sized + Send + Sync + 'static {
fn fetch( fn fetch(
archetype: &'a ArchetypeData, archetype: &'a ArchetypeData,
chunk: &'a ComponentStorage, chunk: &'a ComponentStorage,
chunk_index: usize, chunk_index: ChunkIndex,
set_index: SetIndex,
) -> Self::Iter; ) -> Self::Iter;
/// Validates that the view does not break any component borrowing rules. /// Validates that the view does not break any component borrowing rules.
@ -126,7 +129,12 @@ impl<'a, T: Component> DefaultFilter for Read<T> {
impl<'a, T: Component> View<'a> for Read<T> { impl<'a, T: Component> View<'a> for Read<T> {
type Iter = RefIter<'a, T, Iter<'a, T>>; type Iter = RefIter<'a, T, Iter<'a, T>>;
fn fetch(_: &'a ArchetypeData, chunk: &'a ComponentStorage, _: usize) -> Self::Iter { fn fetch(
_: &'a ArchetypeData,
chunk: &'a ComponentStorage,
_: ChunkIndex,
_: SetIndex,
) -> Self::Iter {
let (slice_borrow, slice) = unsafe { let (slice_borrow, slice) = unsafe {
chunk chunk
.components(ComponentTypeId::of::<T>()) .components(ComponentTypeId::of::<T>())
@ -178,7 +186,12 @@ impl<'a, T: Component> DefaultFilter for TryRead<T> {
impl<'a, T: Component> View<'a> for TryRead<T> { impl<'a, T: Component> View<'a> for TryRead<T> {
type Iter = TryRefIter<'a, T, Iter<'a, T>>; type Iter = TryRefIter<'a, T, Iter<'a, T>>;
fn fetch(_: &'a ArchetypeData, chunk: &'a ComponentStorage, _: usize) -> Self::Iter { fn fetch(
_: &'a ArchetypeData,
chunk: &'a ComponentStorage,
_: ChunkIndex,
_: SetIndex,
) -> Self::Iter {
unsafe { unsafe {
chunk chunk
.components(ComponentTypeId::of::<T>()) .components(ComponentTypeId::of::<T>())
@ -225,7 +238,12 @@ impl<'a, T: Component> View<'a> for Write<T> {
type Iter = RefIterMut<'a, T, IterMut<'a, T>>; type Iter = RefIterMut<'a, T, IterMut<'a, T>>;
#[inline] #[inline]
fn fetch(_: &'a ArchetypeData, chunk: &'a ComponentStorage, _: usize) -> Self::Iter { fn fetch(
_: &'a ArchetypeData,
chunk: &'a ComponentStorage,
_: ChunkIndex,
_: SetIndex,
) -> Self::Iter {
let (slice_borrow, slice) = unsafe { let (slice_borrow, slice) = unsafe {
chunk chunk
.components(ComponentTypeId::of::<T>()) .components(ComponentTypeId::of::<T>())
@ -280,7 +298,12 @@ impl<'a, T: Component> DefaultFilter for TryWrite<T> {
impl<'a, T: Component> View<'a> for TryWrite<T> { impl<'a, T: Component> View<'a> for TryWrite<T> {
type Iter = TryRefIterMut<'a, T, IterMut<'a, T>>; type Iter = TryRefIterMut<'a, T, IterMut<'a, T>>;
fn fetch(_: &'a ArchetypeData, chunk: &'a ComponentStorage, _: usize) -> Self::Iter { fn fetch(
_: &'a ArchetypeData,
chunk: &'a ComponentStorage,
_: ChunkIndex,
_: SetIndex,
) -> Self::Iter {
unsafe { unsafe {
chunk chunk
.components(ComponentTypeId::of::<T>()) .components(ComponentTypeId::of::<T>())
@ -335,7 +358,8 @@ impl<'a, T: Tag> View<'a> for Tagged<T> {
fn fetch( fn fetch(
archetype: &'a ArchetypeData, archetype: &'a ArchetypeData,
chunk: &'a ComponentStorage, chunk: &'a ComponentStorage,
chunk_index: usize, _: ChunkIndex,
SetIndex(set_index): SetIndex,
) -> Self::Iter { ) -> Self::Iter {
let data = unsafe { let data = unsafe {
archetype archetype
@ -348,7 +372,7 @@ impl<'a, T: Tag> View<'a> for Tagged<T> {
) )
}) })
.data_slice::<T>() .data_slice::<T>()
.get_unchecked(chunk_index) .get_unchecked(set_index)
}; };
std::iter::repeat(data).take(chunk.len()) std::iter::repeat(data).take(chunk.len())
} }
@ -395,6 +419,10 @@ macro_rules! impl_view_tuple {
impl<$( $ty: ReadOnly ),* > ReadOnly for ($( $ty, )*) {} impl<$( $ty: ReadOnly ),* > ReadOnly for ($( $ty, )*) {}
impl<$( $ty: ViewElement ),*> ViewElement for ($( $ty, )*) {
type Component = ($( $ty::Component, )*);
}
impl<'a, $( $ty: ViewElement + View<'a> ),* > View<'a> for ($( $ty, )*) { impl<'a, $( $ty: ViewElement + View<'a> ),* > View<'a> for ($( $ty, )*) {
type Iter = crate::zip::Zip<($( $ty::Iter, )*)>; type Iter = crate::zip::Zip<($( $ty::Iter, )*)>;
@ -402,9 +430,10 @@ macro_rules! impl_view_tuple {
fn fetch( fn fetch(
archetype: &'a ArchetypeData, archetype: &'a ArchetypeData,
chunk: &'a ComponentStorage, chunk: &'a ComponentStorage,
chunk_index: usize, chunk_index: ChunkIndex,
set_index: SetIndex,
) -> Self::Iter { ) -> Self::Iter {
crate::zip::multizip(($( $ty::fetch(archetype.clone(), chunk.clone(), chunk_index), )*)) crate::zip::multizip(($( $ty::fetch(archetype.clone(), chunk.clone(), chunk_index, set_index), )*))
} }
fn validate() -> bool { fn validate() -> bool {
@ -449,26 +478,33 @@ impl_view_tuple!(A, B, C);
impl_view_tuple!(A, B, C, D); impl_view_tuple!(A, B, C, D);
impl_view_tuple!(A, B, C, D, E); impl_view_tuple!(A, B, C, D, E);
impl_view_tuple!(A, B, C, D, E, F); impl_view_tuple!(A, B, C, D, E, F);
impl_view_tuple!(A, B, C, D, E, F, G);
impl_view_tuple!(A, B, C, D, E, F, G, H);
impl_view_tuple!(A, B, C, D, E, F, G, H, I);
impl_view_tuple!(A, B, C, D, E, F, G, H, I, J);
impl_view_tuple!(A, B, C, D, E, F, G, H, I, J, K);
impl_view_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
/// A type-safe view of a chunk of entities all of the same data layout. /// A type-safe view of a chunk of entities all of the same data layout.
pub struct Chunk<'a, V: for<'b> View<'b>> { pub struct Chunk<'a, V: for<'b> View<'b>> {
archetype: &'a ArchetypeData, archetype: &'a ArchetypeData,
components: &'a ComponentStorage, components: &'a ComponentStorage,
index: usize, chunk_index: ChunkIndex,
set_index: SetIndex,
view: PhantomData<V>, view: PhantomData<V>,
} }
impl<'a, V: for<'b> View<'b>> Chunk<'a, V> { impl<'a, V: for<'b> View<'b>> Chunk<'a, V> {
pub fn new(archetype: &'a ArchetypeData, set: usize, index: usize) -> Self { pub fn new(archetype: &'a ArchetypeData, set_index: SetIndex, chunk_index: ChunkIndex) -> Self {
Self { Self {
components: unsafe { components: unsafe {
archetype archetype
.chunksets() .chunkset_unchecked(set_index)
.get_unchecked(set) .chunk_unchecked(chunk_index)
.get_unchecked(index)
}, },
archetype, archetype,
index: set, chunk_index,
set_index,
view: PhantomData, view: PhantomData,
} }
} }
@ -480,7 +516,12 @@ impl<'a, V: for<'b> View<'b>> Chunk<'a, V> {
/// Get an iterator of all data contained within the chunk. /// Get an iterator of all data contained within the chunk.
#[inline] #[inline]
pub fn iter(&mut self) -> <V as View<'a>>::Iter { pub fn iter(&mut self) -> <V as View<'a>>::Iter {
V::fetch(self.archetype, self.components, self.index) V::fetch(
self.archetype,
self.components,
self.chunk_index,
self.set_index,
)
} }
/// Get an iterator of all data and entity IDs contained within the chunk. /// Get an iterator of all data and entity IDs contained within the chunk.
@ -488,7 +529,12 @@ impl<'a, V: for<'b> View<'b>> Chunk<'a, V> {
pub fn iter_entities_mut(&mut self) -> ZipEntities<'a, V> { pub fn iter_entities_mut(&mut self) -> ZipEntities<'a, V> {
ZipEntities { ZipEntities {
entities: self.entities(), entities: self.entities(),
data: V::fetch(self.archetype, self.components, self.index), data: V::fetch(
self.archetype,
self.components,
self.chunk_index,
self.set_index,
),
index: 0, index: 0,
view: PhantomData, view: PhantomData,
} }
@ -500,7 +546,7 @@ impl<'a, V: for<'b> View<'b>> Chunk<'a, V> {
.tags() .tags()
.get(TagTypeId::of::<T>()) .get(TagTypeId::of::<T>())
.map(|tags| unsafe { tags.data_slice::<T>() }) .map(|tags| unsafe { tags.data_slice::<T>() })
.map(|slice| unsafe { slice.get_unchecked(self.index) }) .map(|slice| unsafe { slice.get_unchecked(*self.set_index) })
} }
/// Get a slice of component data. /// Get a slice of component data.
@ -578,7 +624,11 @@ where
chunk_filter: &'filter FChunk, chunk_filter: &'filter FChunk,
archetypes: Enumerate<FArch::Iter>, archetypes: Enumerate<FArch::Iter>,
set_frontier: Option<(&'data ArchetypeData, Take<Enumerate<FChunkset::Iter>>)>, set_frontier: Option<(&'data ArchetypeData, Take<Enumerate<FChunkset::Iter>>)>,
chunk_frontier: Option<(&'data ArchetypeData, usize, Take<Enumerate<FChunk::Iter>>)>, chunk_frontier: Option<(
&'data ArchetypeData,
SetIndex,
Take<Enumerate<FChunk::Iter>>,
)>,
} }
impl<'data, 'filter, V, FArch, FChunkset, FChunk> impl<'data, 'filter, V, FArch, FChunkset, FChunk>
@ -589,13 +639,13 @@ where
FChunkset: Filter<ChunksetFilterData<'data>>, FChunkset: Filter<ChunksetFilterData<'data>>,
FChunk: Filter<ChunkFilterData<'data>>, FChunk: Filter<ChunkFilterData<'data>>,
{ {
fn next_set(&mut self) -> Option<(&'data ArchetypeData, usize)> { fn next_set(&mut self) -> Option<(&'data ArchetypeData, SetIndex)> {
loop { loop {
// if we are looping through an archetype, find the next set // if we are looping through an archetype, find the next set
if let Some((ref arch, ref mut chunks)) = self.set_frontier { if let Some((ref arch, ref mut chunks)) = self.set_frontier {
for (set_index, filter_data) in chunks { for (set_index, filter_data) in chunks {
if self.chunkset_filter.is_match(&filter_data).is_pass() { if self.chunkset_filter.is_match(&filter_data).is_pass() {
return Some((arch, set_index)); return Some((arch, SetIndex(set_index)));
} }
} }
} }
@ -648,14 +698,14 @@ where
if let Some((ref arch, set_index, ref mut set)) = self.chunk_frontier { if let Some((ref arch, set_index, ref mut set)) = self.chunk_frontier {
for (chunk_index, filter_data) in set { for (chunk_index, filter_data) in set {
if self.chunk_filter.is_match(&filter_data).is_pass() { if self.chunk_filter.is_match(&filter_data).is_pass() {
return Some(Chunk::new(arch, set_index, chunk_index)); return Some(Chunk::new(arch, set_index, ChunkIndex(chunk_index)));
} }
} }
} }
// we have completed the set, find the next // we have completed the set, find the next
if let Some((ref arch, set_index)) = self.next_set() { if let Some((ref arch, set_index)) = self.next_set() {
let chunks = unsafe { arch.chunksets().get_unchecked(set_index) }.occupied(); let chunks = unsafe { arch.chunkset_unchecked(set_index) }.occupied();
self.chunk_frontier = Some(( self.chunk_frontier = Some((
arch, arch,
set_index, set_index,
@ -746,7 +796,7 @@ where
/// Queries can be constructed from any `View` type, including tuples of `View`s. /// Queries can be constructed from any `View` type, including tuples of `View`s.
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -763,7 +813,7 @@ where
/// The view determines what data is accessed, and whether it is accessed mutably or not. /// The view determines what data is accessed, and whether it is accessed mutably or not.
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -779,7 +829,7 @@ where
/// types accessed by the view. However, additional filters can be specified if needed: /// types accessed by the view. However, additional filters can be specified if needed:
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -796,7 +846,7 @@ where
/// Filters can be combined with bitwise operators: /// Filters can be combined with bitwise operators:
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -815,7 +865,7 @@ where
/// Filters can be iterated through to pull data out of a `World`: /// Filters can be iterated through to pull data out of a `World`:
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -840,7 +890,7 @@ where
/// This allows you to run code for each tag value, or to retrieve a contiguous data slice. /// This allows you to run code for each tag value, or to retrieve a contiguous data slice.
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -865,7 +915,7 @@ where
#[derivative(Clone(bound = "F: Clone"))] #[derivative(Clone(bound = "F: Clone"))]
pub struct Query<V: for<'a> View<'a>, F: EntityFilter> { pub struct Query<V: for<'a> View<'a>, F: EntityFilter> {
view: PhantomData<V>, view: PhantomData<V>,
pub(crate) filter: F, pub filter: F,
} }
impl<V, F> Query<V, F> impl<V, F> Query<V, F>
@ -1370,7 +1420,7 @@ where
)>, )>,
chunk_frontier: Option<( chunk_frontier: Option<(
&'data ArchetypeData, &'data ArchetypeData,
usize, SetIndex,
FissileEnumerate<FChunk::Iter>, FissileEnumerate<FChunk::Iter>,
usize, usize,
)>, )>,
@ -1388,7 +1438,7 @@ where
FChunkset::Iter: FissileIterator, FChunkset::Iter: FissileIterator,
FChunk::Iter: FissileIterator, FChunk::Iter: FissileIterator,
{ {
fn next_set(&mut self) -> Option<(&'data ArchetypeData, usize)> { fn next_set(&mut self) -> Option<(&'data ArchetypeData, SetIndex)> {
loop { loop {
// if we are looping through an archetype, find the next set // if we are looping through an archetype, find the next set
if let Some((ref arch, ref mut chunks, index_bound)) = self.set_frontier { if let Some((ref arch, ref mut chunks, index_bound)) = self.set_frontier {
@ -1396,7 +1446,7 @@ where
if set_index < index_bound if set_index < index_bound
&& self.chunkset_filter.is_match(&filter_data).is_pass() && self.chunkset_filter.is_match(&filter_data).is_pass()
{ {
return Some((arch, set_index)); return Some((arch, SetIndex(set_index)));
} }
} }
} }
@ -1453,14 +1503,14 @@ where
if chunk_index < index_bound if chunk_index < index_bound
&& self.chunk_filter.is_match(&filter_data).is_pass() && self.chunk_filter.is_match(&filter_data).is_pass()
{ {
return Some(Chunk::new(arch, set_index, chunk_index)); return Some(Chunk::new(arch, set_index, ChunkIndex(chunk_index)));
} }
} }
} }
// we have completed the set, find the next // we have completed the set, find the next
if let Some((ref arch, set_index)) = self.next_set() { if let Some((ref arch, set_index)) = self.next_set() {
let chunks = unsafe { arch.chunksets().get_unchecked(set_index) }.occupied(); let chunks = unsafe { arch.chunkset_unchecked(set_index) }.occupied();
self.chunk_frontier = Some(( self.chunk_frontier = Some((
arch, arch,
set_index, set_index,

View file

@ -1,5 +1,6 @@
use crate::{ use crate::{
entity::{Entity, EntityAllocator}, entity::{Entity, EntityAllocator},
index::{ArchetypeIndex, ChunkIndex, SetIndex},
storage::{ storage::{
ArchetypeData, ArchetypeDescription, Chunkset, ComponentMeta, ComponentTypeId, TagMeta, ArchetypeData, ArchetypeDescription, Chunkset, ComponentMeta, ComponentTypeId, TagMeta,
TagStorage, TagTypeId, TagStorage, TagTypeId,
@ -246,7 +247,7 @@ struct ArchetypeDescriptionDeserialize<'a, 'b, WD: WorldDeserializer> {
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
for ArchetypeDescriptionDeserialize<'a, 'b, WD> for ArchetypeDescriptionDeserialize<'a, 'b, WD>
{ {
type Value = usize; type Value = ArchetypeIndex;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, <D as Deserializer<'de>>::Error> fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
where where
@ -262,6 +263,7 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
.archetypes() .archetypes()
.iter() .iter()
.position(|a| a.description() == &archetype_desc) .position(|a| a.description() == &archetype_desc)
.map(ArchetypeIndex)
.unwrap_or_else(|| { .unwrap_or_else(|| {
let (idx, _) = storage.alloc_archetype(archetype_desc); let (idx, _) = storage.alloc_archetype(archetype_desc);
idx idx
@ -269,7 +271,7 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
} }
} }
type ChunkSetMapping = HashMap<usize, usize>; type ChunkSetMapping = HashMap<usize, SetIndex>;
struct TagsDeserializer<'a, 'b, WD: WorldDeserializer> { struct TagsDeserializer<'a, 'b, WD: WorldDeserializer> {
user: &'b WD, user: &'b WD,
@ -306,11 +308,11 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for TagsDeserializ
world_tag_storages world_tag_storages
}; };
let num_world_values = world_tag_storages.iter().map(|ts| ts.len()).nth(0); let num_world_values = world_tag_storages.iter().map(|ts| ts.len()).next();
let num_tag_values = deserialized_tags let num_tag_values = deserialized_tags
.iter() .iter()
.map(|ts| ts.len()) .map(|ts| ts.len())
.nth(0) .next()
.unwrap_or(0); .unwrap_or(0);
let mut chunksets_to_add = Vec::new(); let mut chunksets_to_add = Vec::new();
for i in 0..num_tag_values { for i in 0..num_tag_values {
@ -344,7 +346,7 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for TagsDeserializ
// All temporary TagStorages in `deserialized_tags` will be forgotten later // All temporary TagStorages in `deserialized_tags` will be forgotten later
// because we move data into World when allocating a new chunkset // because we move data into World when allocating a new chunkset
if let Some(world_idx) = matching_idx { if let Some(world_idx) = matching_idx {
chunkset_map.insert(i, world_idx); chunkset_map.insert(i, SetIndex(world_idx));
for tag_idx in 0..tag_types.len() { for tag_idx in 0..tag_types.len() {
unsafe { unsafe {
let (_, tag_meta) = tag_types[tag_idx]; let (_, tag_meta) = tag_types[tag_idx];
@ -438,7 +440,7 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
struct ChunkSetDeserializer<'a, 'b, WD: WorldDeserializer> { struct ChunkSetDeserializer<'a, 'b, WD: WorldDeserializer> {
user: &'b WD, user: &'b WD,
world: &'a mut World, world: &'a mut World,
archetype_idx: usize, archetype_idx: ArchetypeIndex,
chunkset_map: &'a ChunkSetMapping, chunkset_map: &'a ChunkSetMapping,
} }
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for ChunkSetDeserializer<'a, 'b, WD> { impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for ChunkSetDeserializer<'a, 'b, WD> {
@ -483,8 +485,8 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> Visitor<'de> for ChunkSetDeserializer<'
struct ChunkListDeserializer<'a, 'b, WD: WorldDeserializer> { struct ChunkListDeserializer<'a, 'b, WD: WorldDeserializer> {
user: &'b WD, user: &'b WD,
world: &'a mut World, world: &'a mut World,
archetype_idx: usize, archetype_idx: ArchetypeIndex,
chunkset_idx: Option<usize>, chunkset_idx: Option<SetIndex>,
} }
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
for ChunkListDeserializer<'a, 'b, WD> for ChunkListDeserializer<'a, 'b, WD>
@ -535,8 +537,8 @@ enum ChunkField {
struct ChunkDeserializer<'a, 'b, WD: WorldDeserializer> { struct ChunkDeserializer<'a, 'b, WD: WorldDeserializer> {
user: &'b WD, user: &'b WD,
world: &'a mut World, world: &'a mut World,
archetype_idx: usize, archetype_idx: ArchetypeIndex,
chunkset_idx: usize, chunkset_idx: SetIndex,
} }
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for ChunkDeserializer<'a, 'b, WD> { impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for ChunkDeserializer<'a, 'b, WD> {
type Value = (); type Value = ();
@ -612,11 +614,11 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> Visitor<'de> for ChunkDeserializer<'a,
struct EntitiesDeserializer<'a, 'b, WD: WorldDeserializer> { struct EntitiesDeserializer<'a, 'b, WD: WorldDeserializer> {
user: &'b WD, user: &'b WD,
world: &'a mut World, world: &'a mut World,
archetype_idx: usize, archetype_idx: ArchetypeIndex,
chunkset_idx: usize, chunkset_idx: SetIndex,
} }
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for EntitiesDeserializer<'a, 'b, WD> { impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for EntitiesDeserializer<'a, 'b, WD> {
type Value = Vec<(usize, usize)>; type Value = Vec<(ChunkIndex, usize)>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, <D as Deserializer<'de>>::Error> fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
where where
@ -657,9 +659,9 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> for EntitiesDeseri
struct ComponentsDeserializer<'a, 'b, WD: WorldDeserializer> { struct ComponentsDeserializer<'a, 'b, WD: WorldDeserializer> {
user: &'b WD, user: &'b WD,
world: &'a mut World, world: &'a mut World,
archetype_idx: usize, archetype_idx: ArchetypeIndex,
chunkset_idx: usize, chunkset_idx: SetIndex,
chunk_ranges: &'a Vec<(usize, usize)>, chunk_ranges: &'a Vec<(ChunkIndex, usize)>,
} }
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
for ComponentsDeserializer<'a, 'b, WD> for ComponentsDeserializer<'a, 'b, WD>
@ -711,7 +713,7 @@ struct ComponentDataDeserializer<'a, 'b, WD: WorldDeserializer> {
comp_type: &'a ComponentTypeId, comp_type: &'a ComponentTypeId,
comp_meta: &'a ComponentMeta, comp_meta: &'a ComponentMeta,
chunkset: &'a mut Chunkset, chunkset: &'a mut Chunkset,
chunk_ranges: &'a Vec<(usize, usize)>, chunk_ranges: &'a Vec<(ChunkIndex, usize)>,
} }
impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de> impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
for ComponentDataDeserializer<'a, 'b, WD> for ComponentDataDeserializer<'a, 'b, WD>
@ -728,19 +730,16 @@ impl<'de, 'a, 'b, WD: WorldDeserializer> DeserializeSeed<'de>
self.comp_type, self.comp_type,
self.comp_meta, self.comp_meta,
&mut || -> Option<(NonNull<u8>, usize)> { &mut || -> Option<(NonNull<u8>, usize)> {
self.chunk_ranges.get(range_idx).map(|chunk_range| { self.chunk_ranges.get(range_idx).map(|&(chunk, size)| {
range_idx += 1; range_idx += 1;
let chunk = &mut self.chunkset[chunk_range.0]; let chunk = &mut self.chunkset[chunk];
unsafe { unsafe {
let comp_storage = (&mut *chunk.writer().get().1.get()) let comp_storage = (&mut *chunk.writer().get().1.get())
.get_mut(*self.comp_type) .get_mut(*self.comp_type)
.expect( .expect(
"expected ComponentResourceSet when deserializing component data", "expected ComponentResourceSet when deserializing component data",
); );
( (comp_storage.writer().reserve_raw(size), size)
comp_storage.writer().reserve_raw(chunk_range.1),
chunk_range.1,
)
} }
}) })
}, },

View file

@ -0,0 +1,2 @@
pub mod de;
pub mod ser;

View file

@ -9,13 +9,16 @@ use crate::filter::ChunkFilterData;
use crate::filter::ChunksetFilterData; use crate::filter::ChunksetFilterData;
use crate::filter::EntityFilter; use crate::filter::EntityFilter;
use crate::filter::Filter; use crate::filter::Filter;
use crate::index::ArchetypeIndex;
use crate::index::ChunkIndex;
use crate::index::ComponentIndex;
use crate::index::SetIndex;
use crate::iterator::FissileZip; use crate::iterator::FissileZip;
use crate::iterator::SliceVecIter; use crate::iterator::SliceVecIter;
use crate::world::TagSet; use crate::world::TagSet;
use crate::world::WorldId; use crate::world::WorldId;
use derivative::Derivative; use derivative::Derivative;
use fxhash::FxHashMap; use fxhash::FxHashMap;
use smallvec::Drain;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::any::type_name; use std::any::type_name;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
@ -26,8 +29,6 @@ use std::ops::Deref;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::ops::RangeBounds; use std::ops::RangeBounds;
use std::ptr::NonNull; use std::ptr::NonNull;
use std::slice::Iter;
use std::slice::IterMut;
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@ -86,7 +87,6 @@ impl TagTypeId {
pub fn of<T: Component>() -> Self { Self(type_name::<T>(), 0) } pub fn of<T: Component>() -> Self { Self(type_name::<T>(), 0) }
} }
/// A `Component` is per-entity data that can be attached to a single entity. /// A `Component` is per-entity data that can be attached to a single entity.
pub trait Component: Send + Sync + 'static {} pub trait Component: Send + Sync + 'static {}
@ -207,13 +207,13 @@ impl Storage {
pub(crate) fn alloc_archetype( pub(crate) fn alloc_archetype(
&mut self, &mut self,
desc: ArchetypeDescription, desc: ArchetypeDescription,
) -> (usize, &mut ArchetypeData) { ) -> (ArchetypeIndex, &mut ArchetypeData) {
let id = ArchetypeId(self.world_id, self.archetypes.len()); let index = ArchetypeIndex(self.archetypes.len());
let id = ArchetypeId(self.world_id, index);
let archetype = ArchetypeData::new(id, desc); let archetype = ArchetypeData::new(id, desc);
self.push(archetype); self.push(archetype);
let index = self.archetypes.len() - 1;
let archetype = &mut self.archetypes[index]; let archetype = &mut self.archetypes[index];
(index, archetype) (index, archetype)
} }
@ -222,10 +222,10 @@ impl Storage {
let desc = archetype.description(); let desc = archetype.description();
self.component_types self.component_types
.0 .0
.push(desc.components.iter().map(|(t, _)| *t)); .push(desc.components.iter().map(|&(t, _)| t));
self.tag_types.0.push(desc.tags.iter().map(|(t, _)| *t)); self.tag_types.0.push(desc.tags.iter().map(|&(t, _)| t));
let index = self.archetypes.len(); let index = ArchetypeIndex(self.archetypes.len());
let archetype_data = ArchetypeFilterData { let archetype_data = ArchetypeFilterData {
component_types: &self.component_types, component_types: &self.component_types,
tag_types: &self.tag_types, tag_types: &self.tag_types,
@ -235,7 +235,7 @@ impl Storage {
trace!( trace!(
world = id.world().index(), world = id.world().index(),
archetype = id.index(), archetype = *id.index(),
components = ?desc.component_names, components = ?desc.component_names,
tags = ?desc.tag_names, tags = ?desc.tag_names,
"Created Archetype" "Created Archetype"
@ -270,6 +270,46 @@ impl Storage {
) -> std::vec::Drain<ArchetypeData> { ) -> std::vec::Drain<ArchetypeData> {
self.archetypes.drain(range) self.archetypes.drain(range)
} }
pub(crate) fn archetype(
&self,
ArchetypeIndex(index): ArchetypeIndex,
) -> Option<&ArchetypeData> {
self.archetypes().get(index)
}
pub(crate) fn archetype_mut(
&mut self,
ArchetypeIndex(index): ArchetypeIndex,
) -> Option<&mut ArchetypeData> {
self.archetypes_mut().get_mut(index)
}
pub(crate) unsafe fn archetype_unchecked(
&self,
ArchetypeIndex(index): ArchetypeIndex,
) -> &ArchetypeData {
self.archetypes().get_unchecked(index)
}
pub(crate) unsafe fn archetype_unchecked_mut(
&mut self,
ArchetypeIndex(index): ArchetypeIndex,
) -> &mut ArchetypeData {
self.archetypes_mut().get_unchecked_mut(index)
}
pub(crate) fn chunk(&self, loc: EntityLocation) -> Option<&ComponentStorage> {
self.archetype(loc.archetype())
.and_then(|atd| atd.chunkset(loc.set()))
.and_then(|cs| cs.chunk(loc.chunk()))
}
pub(crate) fn chunk_mut(&mut self, loc: EntityLocation) -> Option<&mut ComponentStorage> {
self.archetype_mut(loc.archetype())
.and_then(|atd| atd.chunkset_mut(loc.set()))
.and_then(|cs| cs.chunk_mut(loc.chunk()))
}
} }
/// Stores metadata decribing the type of a tag. /// Stores metadata decribing the type of a tag.
@ -405,19 +445,21 @@ impl<'a> Filter<ArchetypeFilterData<'a>> for ArchetypeDescription {
} }
} }
const MAX_CHUNK_SIZE: usize = 16 * 1024; const MAX_CHUNK_SIZE: usize = 16 * 1024 * 10;
const COMPONENT_STORAGE_ALIGNMENT: usize = 64; const COMPONENT_STORAGE_ALIGNMENT: usize = 64;
/// Unique ID of an archetype. /// Unique ID of an archetype.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ArchetypeId(WorldId, usize); pub struct ArchetypeId(WorldId, ArchetypeIndex);
impl ArchetypeId { impl ArchetypeId {
pub(crate) fn new(world_id: WorldId, index: usize) -> Self { ArchetypeId(world_id, index) } pub(crate) fn new(world_id: WorldId, index: ArchetypeIndex) -> Self {
ArchetypeId(world_id, index)
}
fn index(self) -> usize { self.1 } pub fn index(self) -> ArchetypeIndex { self.1 }
fn world(self) -> WorldId { self.0 } pub fn world(self) -> WorldId { self.0 }
} }
/// Contains all of the tags attached to the entities in each chunk. /// Contains all of the tags attached to the entities in each chunk.
@ -425,7 +467,7 @@ pub struct Tags(pub(crate) SmallVec<[(TagTypeId, TagStorage); 3]>);
impl Tags { impl Tags {
fn new(mut data: SmallVec<[(TagTypeId, TagStorage); 3]>) -> Self { fn new(mut data: SmallVec<[(TagTypeId, TagStorage); 3]>) -> Self {
data.sort_by_key(|(t, _)| *t); data.sort_by_key(|&(t, _)| t);
Self(data) Self(data)
} }
@ -439,7 +481,7 @@ impl Tags {
#[inline] #[inline]
pub fn get(&self, type_id: TagTypeId) -> Option<&TagStorage> { pub fn get(&self, type_id: TagTypeId) -> Option<&TagStorage> {
self.0 self.0
.binary_search_by_key(&type_id, |(t, _)| *t) .binary_search_by_key(&type_id, |&(t, _)| t)
.ok() .ok()
.map(|i| unsafe { &self.0.get_unchecked(i).1 }) .map(|i| unsafe { &self.0.get_unchecked(i).1 })
} }
@ -448,19 +490,23 @@ impl Tags {
#[inline] #[inline]
pub fn get_mut(&mut self, type_id: TagTypeId) -> Option<&mut TagStorage> { pub fn get_mut(&mut self, type_id: TagTypeId) -> Option<&mut TagStorage> {
self.0 self.0
.binary_search_by_key(&type_id, |(t, _)| *t) .binary_search_by_key(&type_id, |&(t, _)| t)
.ok() .ok()
.map(move |i| unsafe { &mut self.0.get_unchecked_mut(i).1 }) .map(move |i| unsafe { &mut self.0.get_unchecked_mut(i).1 })
} }
pub(crate) fn tag_set(&self, chunk: usize) -> DynamicTagSet { pub(crate) fn tag_set(&self, SetIndex(index): SetIndex) -> DynamicTagSet {
let mut tags = DynamicTagSet { tags: Vec::new() }; let mut tags = DynamicTagSet { tags: Vec::new() };
unsafe { unsafe {
for (type_id, storage) in self.0.iter() { for &(type_id, ref storage) in self.0.iter() {
let (ptr, _, count) = storage.data_raw(); let (ptr, element_size, count) = storage.data_raw();
debug_assert!(chunk < count, "chunk index out of bounds"); debug_assert!(index < count, "set index out of bounds");
tags.push(*type_id, *storage.element(), ptr); tags.push(
type_id,
*storage.element(),
NonNull::new(ptr.as_ptr().add(element_size * index)).unwrap(),
);
} }
} }
@ -497,7 +543,7 @@ impl DynamicTagSet {
.tags .tags
.iter() .iter()
.enumerate() .enumerate()
.find(|(_, (t, _, _))| *t == type_id) .find(|(_, &(t, _, _))| t == type_id)
{ {
let (_, meta, ptr) = self.tags.remove(i); let (_, meta, ptr) = self.tags.remove(i);
unsafe { unsafe {
@ -516,8 +562,8 @@ impl DynamicTagSet {
impl TagSet for DynamicTagSet { impl TagSet for DynamicTagSet {
fn write_tags(&self, tags: &mut Tags) { fn write_tags(&self, tags: &mut Tags) {
for (type_id, meta, ptr) in self.tags.iter() { for &(type_id, ref meta, ptr) in self.tags.iter() {
let storage = tags.get_mut(*type_id).unwrap(); let storage = tags.get_mut(type_id).unwrap();
unsafe { unsafe {
if meta.drop_fn.is_some() && !meta.is_zero_sized() { if meta.drop_fn.is_some() && !meta.is_zero_sized() {
// clone the value into temp storage then move it into the chunk // clone the value into temp storage then move it into the chunk
@ -572,7 +618,7 @@ impl ArchetypeData {
let tags = desc let tags = desc
.tags .tags
.iter() .iter()
.map(|(type_id, meta)| (*type_id, TagStorage::new(*meta))) .map(|&(type_id, meta)| (type_id, TagStorage::new(meta)))
.collect(); .collect();
// create component data layout // create component data layout
@ -588,12 +634,12 @@ impl ArchetypeData {
); );
let mut data_capacity = 0usize; let mut data_capacity = 0usize;
let mut component_data_offsets = Vec::new(); let mut component_data_offsets = Vec::new();
for (type_id, meta) in desc.components.iter() { for &(type_id, meta) in desc.components.iter() {
data_capacity = align_up( data_capacity = align_up(
align_up(data_capacity, COMPONENT_STORAGE_ALIGNMENT), align_up(data_capacity, COMPONENT_STORAGE_ALIGNMENT),
meta.align, meta.align,
); );
component_data_offsets.push((*type_id, data_capacity, *meta)); component_data_offsets.push((type_id, data_capacity, meta));
data_capacity += meta.size * entity_capacity; data_capacity += meta.size * entity_capacity;
} }
let data_alignment = let data_alignment =
@ -614,10 +660,17 @@ impl ArchetypeData {
} }
} }
pub(crate) fn delete_all(&mut self) {
for set in &mut self.chunk_sets {
// Clearing the chunk will Drop all the data
set.chunks.clear();
}
}
pub(crate) fn subscribe(&mut self, subscriber: Subscriber) { pub(crate) fn subscribe(&mut self, subscriber: Subscriber) {
self.subscribers.push(subscriber.clone()); self.subscribers.push(subscriber.clone());
for i in 0..self.chunk_sets.len() { for i in (0..self.chunk_sets.len()).map(SetIndex) {
let filter = ChunksetFilterData { let filter = ChunksetFilterData {
archetype_data: self, archetype_data: self,
}; };
@ -631,7 +684,7 @@ impl ArchetypeData {
pub(crate) fn set_subscribers(&mut self, subscribers: Subscribers) { pub(crate) fn set_subscribers(&mut self, subscribers: Subscribers) {
self.subscribers = subscribers; self.subscribers = subscribers;
for i in 0..self.chunk_sets.len() { for i in (0..self.chunk_sets.len()).map(SetIndex) {
let filter = ChunksetFilterData { let filter = ChunksetFilterData {
archetype_data: self, archetype_data: self,
}; };
@ -651,10 +704,10 @@ impl ArchetypeData {
let mut set_match = None; let mut set_match = None;
for self_index in 0..self.chunk_sets.len() { for self_index in 0..self.chunk_sets.len() {
let mut matches = true; let mut matches = true;
for (type_id, tags) in self.tags.0.iter() { for &(type_id, ref tags) in self.tags.0.iter() {
unsafe { unsafe {
let (self_tag_ptr, size, _) = tags.data_raw(); let (self_tag_ptr, size, _) = tags.data_raw();
let (other_tag_ptr, _, _) = other_tags.get(*type_id).unwrap().data_raw(); let (other_tag_ptr, _, _) = other_tags.get(type_id).unwrap().data_raw();
if !tags.element().equals( if !tags.element().equals(
self_tag_ptr.as_ptr().add(self_index * size), self_tag_ptr.as_ptr().add(self_index * size),
@ -675,16 +728,18 @@ impl ArchetypeData {
if let Some(chunk_set) = set_match { if let Some(chunk_set) = set_match {
// if we found a match, move the chunks into the set // if we found a match, move the chunks into the set
let target = &mut self.chunk_sets[chunk_set]; let target = &mut self.chunk_sets[chunk_set];
for chunk in set.drain(..) { for mut chunk in set.drain(..) {
chunk.mark_modified();
target.push(chunk); target.push(chunk);
} }
} else { } else {
// if we did not find a match, clone the tags and move the set // if we did not find a match, clone the tags and move the set
set.mark_modified();
self.push(set, |self_tags| { self.push(set, |self_tags| {
for (type_id, other_tags) in other_tags.0.iter() { for &(type_id, ref other_tags) in other_tags.0.iter() {
unsafe { unsafe {
let (src, _, _) = other_tags.data_raw(); let (src, _, _) = other_tags.data_raw();
let dst = self_tags.get_mut(*type_id).unwrap().alloc_ptr(); let dst = self_tags.get_mut(type_id).unwrap().alloc_ptr();
other_tags.element().clone(src.as_ptr(), dst); other_tags.element().clone(src.as_ptr(), dst);
} }
} }
@ -695,9 +750,18 @@ impl ArchetypeData {
self.tags.validate(self.chunk_sets.len()); self.tags.validate(self.chunk_sets.len());
} }
pub(crate) fn enumerate_entities<'a>( /// Iterate all entities in existence by iterating across archetypes, chunk sets, and chunks
pub(crate) fn iter_entities<'a>(&'a self) -> impl Iterator<Item = Entity> + 'a {
self.chunk_sets.iter().flat_map(move |set| {
set.chunks
.iter()
.flat_map(move |chunk| chunk.entities().iter().copied())
})
}
pub(crate) fn iter_entity_locations<'a>(
&'a self, &'a self,
archetype_index: usize, archetype_index: ArchetypeIndex,
) -> impl Iterator<Item = (Entity, EntityLocation)> + 'a { ) -> impl Iterator<Item = (Entity, EntityLocation)> + 'a {
self.chunk_sets self.chunk_sets
.iter() .iter()
@ -711,14 +775,14 @@ impl ArchetypeData {
.entities() .entities()
.iter() .iter()
.enumerate() .enumerate()
.map(move |(entity_index, entity)| { .map(move |(entity_index, &entity)| {
( (
*entity, entity,
EntityLocation::new( EntityLocation::new(
archetype_index, archetype_index,
set_index, SetIndex(set_index),
chunk_index, ChunkIndex(chunk_index),
entity_index, ComponentIndex(entity_index),
), ),
) )
}) })
@ -730,7 +794,7 @@ impl ArchetypeData {
initialize(&mut self.tags); initialize(&mut self.tags);
self.chunk_sets.push(set); self.chunk_sets.push(set);
let index = self.chunk_sets.len() - 1; let index = SetIndex(self.chunk_sets.len() - 1);
let filter = ChunksetFilterData { let filter = ChunksetFilterData {
archetype_data: self, archetype_data: self,
}; };
@ -743,35 +807,39 @@ impl ArchetypeData {
/// Allocates a new chunk set. Returns the index of the new set. /// Allocates a new chunk set. Returns the index of the new set.
/// ///
/// `initialize` is expected to push the new chunkset's tag values onto the tags collection. /// `initialize` is expected to push the new chunkset's tag values onto the tags collection.
pub(crate) fn alloc_chunk_set<F: FnMut(&mut Tags)>(&mut self, initialize: F) -> usize { pub(crate) fn alloc_chunk_set<F: FnMut(&mut Tags)>(&mut self, initialize: F) -> SetIndex {
self.push(Chunkset::default(), initialize); self.push(Chunkset::default(), initialize);
self.chunk_sets.len() - 1 SetIndex(self.chunk_sets.len() - 1)
} }
/// Finds a chunk with space free for at least `minimum_space` entities, creating a chunk if needed. /// Finds a chunk with space free for at least `minimum_space` entities, creating a chunk if needed.
pub(crate) fn get_free_chunk(&mut self, set_index: usize, minimum_space: usize) -> usize { pub(crate) fn get_free_chunk(
&mut self,
set_index: SetIndex,
minimum_space: usize,
) -> ChunkIndex {
let count = { let count = {
let chunks = &mut self.chunk_sets[set_index]; let chunks = &mut self.chunk_sets[set_index];
let len = chunks.len(); let len = chunks.len();
for (i, chunk) in chunks.iter_mut().enumerate() { for (i, chunk) in chunks.iter_mut().enumerate() {
let space_left = chunk.capacity() - chunk.len(); let space_left = chunk.capacity() - chunk.len();
if space_left >= minimum_space { if space_left >= minimum_space {
return i; return ChunkIndex(i);
} }
} }
len ChunkIndex(len)
}; };
let chunk = self let chunk = self
.component_layout .component_layout
.alloc_storage(ChunkId(self.id, set_index, count)); .alloc_storage(ChunkId(self.id, set_index, count));
unsafe { self.chunk_sets.get_unchecked_mut(set_index).push(chunk) }; unsafe { self.chunkset_unchecked_mut(set_index).push(chunk) };
trace!( trace!(
world = self.id.world().index(), world = self.id.world().index(),
archetype = self.id.index(), archetype = *self.id.index(),
chunkset = set_index, chunkset = *set_index,
chunk = count, chunk = *count,
components = ?self.desc.component_names, components = ?self.desc.component_names,
tags = ?self.desc.tag_names, tags = ?self.desc.tag_names,
"Created chunk" "Created chunk"
@ -808,13 +876,16 @@ impl ArchetypeData {
) -> bool { ) -> bool {
trace!( trace!(
world = self.id().world().index(), world = self.id().world().index(),
archetype = self.id().index(), archetype = *self.id().index(),
"Defragmenting archetype" "Defragmenting archetype"
); );
let arch_index = self.id.index(); let arch_index = self.id.index();
for (i, chunkset) in self.chunk_sets.iter_mut().enumerate() { for (i, chunkset) in self.chunk_sets.iter_mut().enumerate() {
let complete = chunkset.defrag(budget, |e, chunk, component| { let complete = chunkset.defrag(budget, |e, chunk, component| {
on_moved(e, EntityLocation::new(arch_index, i, chunk, component)); on_moved(
e,
EntityLocation::new(arch_index, SetIndex(i), chunk, component),
);
}); });
if !complete { if !complete {
return false; return false;
@ -823,6 +894,25 @@ impl ArchetypeData {
true true
} }
pub(crate) fn chunkset(&self, SetIndex(index): SetIndex) -> Option<&Chunkset> {
self.chunksets().get(index)
}
pub(crate) fn chunkset_mut(&mut self, SetIndex(index): SetIndex) -> Option<&mut Chunkset> {
self.chunksets_mut().get_mut(index)
}
pub(crate) unsafe fn chunkset_unchecked(&self, SetIndex(index): SetIndex) -> &Chunkset {
self.chunksets().get_unchecked(index)
}
pub(crate) unsafe fn chunkset_unchecked_mut(
&mut self,
SetIndex(index): SetIndex,
) -> &mut Chunkset {
self.chunksets_mut().get_unchecked_mut(index)
}
} }
fn align_up(addr: usize, align: usize) -> usize { (addr + (align - 1)) & align.wrapping_neg() } fn align_up(addr: usize, align: usize) -> usize { (addr + (align - 1)) & align.wrapping_neg() }
@ -845,9 +935,9 @@ impl ComponentStorageLayout {
let storage_info = self let storage_info = self
.data_layout .data_layout
.iter() .iter()
.map(|(ty, _, meta)| { .map(|&(ty, _, ref meta)| {
( (
*ty, ty,
ComponentResourceSet { ComponentResourceSet {
ptr: AtomicRefCell::new(meta.align as *mut u8), ptr: AtomicRefCell::new(meta.align as *mut u8),
capacity: self.capacity, capacity: self.capacity,
@ -867,7 +957,7 @@ impl ComponentStorageLayout {
component_offsets: self component_offsets: self
.data_layout .data_layout
.iter() .iter()
.map(|(ty, offset, _)| (*ty, *offset)) .map(|&(ty, offset, _)| (ty, offset))
.collect(), .collect(),
component_layout: self.alloc_layout, component_layout: self.alloc_layout,
component_info: UnsafeCell::new(Components::new(storage_info)), component_info: UnsafeCell::new(Components::new(storage_info)),
@ -907,7 +997,7 @@ impl Chunkset {
let id = chunk.id(); let id = chunk.id();
self.chunks.push(chunk); self.chunks.push(chunk);
let index = self.chunks.len() - 1; let index = ChunkIndex(self.chunks.len() - 1);
let filter = ChunkFilterData { let filter = ChunkFilterData {
chunks: &self.chunks, chunks: &self.chunks,
}; };
@ -919,7 +1009,7 @@ impl Chunkset {
pub(crate) fn subscribe(&mut self, subscriber: Subscriber) { pub(crate) fn subscribe(&mut self, subscriber: Subscriber) {
self.subscribers.push(subscriber.clone()); self.subscribers.push(subscriber.clone());
for i in 0..self.chunks.len() { for i in (0..self.chunks.len()).map(ChunkIndex) {
let filter = ChunkFilterData { let filter = ChunkFilterData {
chunks: &self.chunks, chunks: &self.chunks,
}; };
@ -933,7 +1023,7 @@ impl Chunkset {
pub(crate) fn set_subscribers(&mut self, subscribers: Subscribers) { pub(crate) fn set_subscribers(&mut self, subscribers: Subscribers) {
self.subscribers = subscribers; self.subscribers = subscribers;
for i in 0..self.chunks.len() { for i in (0..self.chunks.len()).map(ChunkIndex) {
let filter = ChunkFilterData { let filter = ChunkFilterData {
chunks: &self.chunks, chunks: &self.chunks,
}; };
@ -943,6 +1033,12 @@ impl Chunkset {
} }
} }
fn mark_modified(&mut self) {
for chunk in self.chunks.iter_mut() {
chunk.mark_modified();
}
}
pub(crate) fn drain<R: RangeBounds<usize>>( pub(crate) fn drain<R: RangeBounds<usize>>(
&mut self, &mut self,
range: R, range: R,
@ -988,7 +1084,7 @@ impl Chunkset {
/// new component index. /// new component index.
/// ///
/// Returns whether or not the chunkset has been fully defragmented. /// Returns whether or not the chunkset has been fully defragmented.
fn defrag<F: FnMut(Entity, usize, usize)>( fn defrag<F: FnMut(Entity, ChunkIndex, ComponentIndex)>(
&mut self, &mut self,
budget: &mut usize, budget: &mut usize,
mut on_moved: F, mut on_moved: F,
@ -1034,11 +1130,16 @@ impl Chunkset {
*budget -= 1; *budget -= 1;
// move the last entity // move the last entity
let swapped = source.move_entity(target, source.len() - 1); let comp_index = ComponentIndex(source.len() - 1);
let swapped = source.move_entity(target, comp_index);
assert!(swapped.is_none()); assert!(swapped.is_none());
// notify move // notify move
on_moved(*target.entities.last().unwrap(), first, target.len() - 1); on_moved(
*target.entities.last().unwrap(),
ChunkIndex(first),
comp_index,
);
// exit if we cant move any more // exit if we cant move any more
if target.is_full() || source.is_empty() { if target.is_full() || source.is_empty() {
@ -1047,22 +1148,47 @@ impl Chunkset {
} }
} }
} }
pub(crate) fn chunk(&self, ChunkIndex(index): ChunkIndex) -> Option<&ComponentStorage> {
self.chunks.get(index)
}
pub(crate) fn chunk_mut(
&mut self,
ChunkIndex(index): ChunkIndex,
) -> Option<&mut ComponentStorage> {
self.chunks.get_mut(index)
}
pub(crate) unsafe fn chunk_unchecked(
&self,
ChunkIndex(index): ChunkIndex,
) -> &ComponentStorage {
self.chunks.get_unchecked(index)
}
pub(crate) unsafe fn chunk_unchecked_mut(
&mut self,
ChunkIndex(index): ChunkIndex,
) -> &mut ComponentStorage {
self.chunks.get_unchecked_mut(index)
}
} }
/// Unique ID of a chunk. /// Unique ID of a chunk.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ChunkId(ArchetypeId, usize, usize); pub struct ChunkId(ArchetypeId, SetIndex, ChunkIndex);
impl ChunkId { impl ChunkId {
pub(crate) fn new(archetype: ArchetypeId, set: usize, index: usize) -> Self { pub(crate) fn new(archetype: ArchetypeId, set: SetIndex, index: ChunkIndex) -> Self {
ChunkId(archetype, set, index) ChunkId(archetype, set, index)
} }
pub fn archetype_id(&self) -> ArchetypeId { self.0 } pub fn archetype_id(&self) -> ArchetypeId { self.0 }
pub(crate) fn set(&self) -> usize { self.1 } pub(crate) fn set(&self) -> SetIndex { self.1 }
pub(crate) fn index(&self) -> usize { self.2 } pub(crate) fn index(&self) -> ChunkIndex { self.2 }
} }
/// A set of component slices located on a chunk. /// A set of component slices located on a chunk.
@ -1070,7 +1196,7 @@ pub struct Components(SmallVec<[(ComponentTypeId, ComponentResourceSet); 5]>);
impl Components { impl Components {
pub(crate) fn new(mut data: SmallVec<[(ComponentTypeId, ComponentResourceSet); 5]>) -> Self { pub(crate) fn new(mut data: SmallVec<[(ComponentTypeId, ComponentResourceSet); 5]>) -> Self {
data.sort_by_key(|(t, _)| *t); data.sort_by_key(|&(t, _)| t);
Self(data) Self(data)
} }
@ -1078,7 +1204,7 @@ impl Components {
#[inline] #[inline]
pub fn get(&self, type_id: ComponentTypeId) -> Option<&ComponentResourceSet> { pub fn get(&self, type_id: ComponentTypeId) -> Option<&ComponentResourceSet> {
self.0 self.0
.binary_search_by_key(&type_id, |(t, _)| *t) .binary_search_by_key(&type_id, |&(t, _)| t)
.ok() .ok()
.map(|i| unsafe { &self.0.get_unchecked(i).1 }) .map(|i| unsafe { &self.0.get_unchecked(i).1 })
} }
@ -1087,16 +1213,24 @@ impl Components {
#[inline] #[inline]
pub fn get_mut(&mut self, type_id: ComponentTypeId) -> Option<&mut ComponentResourceSet> { pub fn get_mut(&mut self, type_id: ComponentTypeId) -> Option<&mut ComponentResourceSet> {
self.0 self.0
.binary_search_by_key(&type_id, |(t, _)| *t) .binary_search_by_key(&type_id, |&(t, _)| t)
.ok() .ok()
.map(move |i| unsafe { &mut self.0.get_unchecked_mut(i).1 }) .map(move |i| unsafe { &mut self.0.get_unchecked_mut(i).1 })
} }
fn iter(&mut self) -> Iter<(ComponentTypeId, ComponentResourceSet)> { self.0.iter() } fn iter(&mut self) -> impl Iterator<Item = &(ComponentTypeId, ComponentResourceSet)> + '_ {
self.0.iter()
}
fn iter_mut(&mut self) -> IterMut<(ComponentTypeId, ComponentResourceSet)> { self.0.iter_mut() } fn iter_mut(
&mut self,
) -> impl Iterator<Item = &mut (ComponentTypeId, ComponentResourceSet)> + '_ {
self.0.iter_mut()
}
fn drain(&mut self) -> Drain<(ComponentTypeId, ComponentResourceSet)> { self.0.drain() } fn drain(&mut self) -> impl Iterator<Item = (ComponentTypeId, ComponentResourceSet)> + '_ {
self.0.drain(..)
}
} }
/// Stores a chunk of entities and their component data of a specific data layout. /// Stores a chunk of entities and their component data of a specific data layout.
@ -1125,10 +1259,10 @@ impl<'a> StorageWriter<'a> {
impl<'a> Drop for StorageWriter<'a> { impl<'a> Drop for StorageWriter<'a> {
fn drop(&mut self) { fn drop(&mut self) {
self.storage.update_count_gauge(); self.storage.update_count_gauge();
for entity in self.storage.entities.iter().skip(self.initial_count) { for &entity in self.storage.entities.iter().skip(self.initial_count) {
self.storage self.storage
.subscribers .subscribers
.send(Event::EntityInserted(*entity, self.storage.id())); .send(Event::EntityInserted(entity, self.storage.id()));
} }
} }
} }
@ -1168,10 +1302,25 @@ impl ComponentStorage {
unsafe { &*self.component_info.get() }.get(component_type) unsafe { &*self.component_info.get() }.get(component_type)
} }
/// Increments all component versions, forcing the chunk to be seen as modified for all queries.
fn mark_modified(&mut self) {
unsafe {
let components = &mut *self.component_info.get();
for (_, component) in components.iter_mut() {
// touch each slice mutably to increment its version
let _ = component.data_raw_mut();
}
}
}
/// Removes an entity from the chunk by swapping it with the last entry. /// Removes an entity from the chunk by swapping it with the last entry.
/// ///
/// Returns the ID of the entity which was swapped into the removed entity's position. /// Returns the ID of the entity which was swapped into the removed entity's position.
pub fn swap_remove(&mut self, index: usize, drop: bool) -> Option<Entity> { pub fn swap_remove(
&mut self,
ComponentIndex(index): ComponentIndex,
drop: bool,
) -> Option<Entity> {
let removed = self.entities.swap_remove(index); let removed = self.entities.swap_remove(index);
for (_, component) in unsafe { &mut *self.component_info.get() }.iter_mut() { for (_, component) in unsafe { &mut *self.component_info.get() }.iter_mut() {
component.writer().swap_remove(index, drop); component.writer().swap_remove(index, drop);
@ -1196,16 +1345,20 @@ impl ComponentStorage {
/// the target chunk. Any components left over will be dropped. /// the target chunk. Any components left over will be dropped.
/// ///
/// Returns the ID of the entity which was swapped into the removed entity's position. /// Returns the ID of the entity which was swapped into the removed entity's position.
pub fn move_entity(&mut self, target: &mut ComponentStorage, index: usize) -> Option<Entity> { pub fn move_entity(
debug_assert!(index < self.len()); &mut self,
target: &mut ComponentStorage,
index: ComponentIndex,
) -> Option<Entity> {
debug_assert!(*index < self.len());
debug_assert!(!target.is_full()); debug_assert!(!target.is_full());
if !target.is_allocated() { if !target.is_allocated() {
target.allocate(); target.allocate();
} }
trace!(index, source = ?self.id, destination = ?target.id, "Moving entity"); trace!(index = *index, source = ?self.id, destination = ?target.id, "Moving entity");
let entity = unsafe { *self.entities.get_unchecked(index) }; let entity = unsafe { *self.entities.get_unchecked(*index) };
target.entities.push(entity); target.entities.push(entity);
let self_components = unsafe { &mut *self.component_info.get() }; let self_components = unsafe { &mut *self.component_info.get() };
@ -1214,9 +1367,9 @@ impl ComponentStorage {
for (comp_type, accessor) in self_components.iter_mut() { for (comp_type, accessor) in self_components.iter_mut() {
if let Some(target_accessor) = target_components.get_mut(*comp_type) { if let Some(target_accessor) = target_components.get_mut(*comp_type) {
// move the component into the target chunk // move the component into the target chunk
let (ptr, element_size, _) = accessor.data_raw();
unsafe { unsafe {
let component = ptr.add(element_size * index); let (ptr, element_size, _) = accessor.data_raw();
let component = ptr.add(element_size * *index);
target_accessor target_accessor
.writer() .writer()
.push_raw(NonNull::new_unchecked(component), 1); .push_raw(NonNull::new_unchecked(component), 1);
@ -1257,9 +1410,9 @@ impl ComponentStorage {
trace!( trace!(
world = self.id.archetype_id().world().index(), world = self.id.archetype_id().world().index(),
archetype = self.id.archetype_id().index(), archetype = *self.id.archetype_id().index(),
chunkset = self.id.set(), chunkset = *self.id.set(),
chunk = self.id.index(), chunk = *self.id.index(),
layout = ?self.component_layout, layout = ?self.component_layout,
"Freeing chunk memory" "Freeing chunk memory"
); );
@ -1283,9 +1436,9 @@ impl ComponentStorage {
trace!( trace!(
world = self.id.archetype_id().world().index(), world = self.id.archetype_id().world().index(),
archetype = self.id.archetype_id().index(), archetype = *self.id.archetype_id().index(),
chunkset = self.id.set(), chunkset = *self.id.set(),
chunk = self.id.index(), chunk = *self.id.index(),
layout = ?self.component_layout, layout = ?self.component_layout,
"Allocating chunk memory" "Allocating chunk memory"
); );
@ -1298,8 +1451,8 @@ impl ComponentStorage {
// update accessor pointers // update accessor pointers
for (type_id, component) in (&mut *self.component_info.get()).iter_mut() { for (type_id, component) in (&mut *self.component_info.get()).iter_mut() {
let offset = self.component_offsets.get(type_id).unwrap(); let &offset = self.component_offsets.get(type_id).unwrap();
*component.ptr.get_mut() = ptr.add(*offset); *component.ptr.get_mut() = ptr.add(offset);
} }
} }
@ -1356,6 +1509,12 @@ impl Drop for ComponentStorage {
} }
} }
for e in &self.entities {
self.subscribers.send(Event::EntityRemoved(*e, self.id()));
}
self.update_count_gauge();
// free the chunk's memory // free the chunk's memory
unsafe { unsafe {
std::alloc::dealloc(ptr.as_ptr(), self.component_layout); std::alloc::dealloc(ptr.as_ptr(), self.component_layout);
@ -1385,12 +1544,10 @@ impl ComponentResourceSet {
/// ///
/// # Safety /// # Safety
/// ///
/// Access to the component data within the slice is runtime borrow checked. /// Access to the component data within the slice is runtime borrow checked in debug builds.
/// This call will panic if borrowing rules are broken. /// This call will panic if borrowing rules are broken in debug, and is undefined behavior in release.
pub fn data_raw(&self) -> (Ref<*mut u8>, usize, usize) { pub unsafe fn data_raw(&self) -> (Ref<*mut u8>, usize, usize) {
(self.ptr.get(), self.element_size, unsafe { (self.ptr.get(), self.element_size, *self.count.get())
*self.count.get()
})
} }
/// Gets a raw pointer to the start of the component slice. /// Gets a raw pointer to the start of the component slice.
@ -1399,21 +1556,19 @@ impl ComponentResourceSet {
/// ///
/// # Safety /// # Safety
/// ///
/// Access to the component data within the slice is runtime borrow checked. /// Access to the component data within the slice is runtime borrow checked in debug builds.
/// This call will panic if borrowing rules are broken. /// This call will panic if borrowing rules are broken in debug, and is undefined behavior in release.
/// ///
/// # Panics /// # Panics
/// ///
/// Will panic when an internal u64 counter overflows. /// Will panic when an internal u64 counter overflows.
/// It will happen in 50000 years if you do 10000 mutations a millisecond. /// It will happen in 50000 years if you do 10000 mutations a millisecond.
pub fn data_raw_mut(&self) -> (RefMut<*mut u8>, usize, usize) { pub unsafe fn data_raw_mut(&self) -> (RefMut<*mut u8>, usize, usize) {
// this version increment is not thread safe // this version increment is not thread safe
// - but the pointer `get_mut` ensures exclusive access at runtime // - but the pointer `get_mut` ensures exclusive access at runtime
let ptr = self.ptr.get_mut(); let ptr = self.ptr.get_mut();
unsafe { *self.version.get() = next_version();
*self.version.get() = next_version(); (ptr, self.element_size, *self.count.get())
};
(ptr, self.element_size, unsafe { *self.count.get() })
} }
/// Gets a shared reference to the slice of components. /// Gets a shared reference to the slice of components.
@ -1426,7 +1581,7 @@ impl ComponentResourceSet {
/// This call will panic if borrowing rules are broken. /// This call will panic if borrowing rules are broken.
pub unsafe fn data_slice<T>(&self) -> RefMap<&[T]> { pub unsafe fn data_slice<T>(&self) -> RefMap<&[T]> {
let (ptr, _size, count) = self.data_raw(); let (ptr, _size, count) = self.data_raw();
ptr.map_into(|ptr| std::slice::from_raw_parts(*ptr as *const _ as *const T, count)) ptr.map_into(|&ptr| std::slice::from_raw_parts(ptr as *const _ as *const T, count))
} }
/// Gets a mutable reference to the slice of components. /// Gets a mutable reference to the slice of components.
@ -1444,7 +1599,7 @@ impl ComponentResourceSet {
/// It will happen in 50000 years if you do 10000 mutations a millisecond. /// It will happen in 50000 years if you do 10000 mutations a millisecond.
pub unsafe fn data_slice_mut<T>(&self) -> RefMapMut<&mut [T]> { pub unsafe fn data_slice_mut<T>(&self) -> RefMapMut<&mut [T]> {
let (ptr, _size, count) = self.data_raw_mut(); let (ptr, _size, count) = self.data_raw_mut();
ptr.map_into(|ptr| std::slice::from_raw_parts_mut(*ptr as *mut _ as *mut T, count)) ptr.map_into(|&mut ptr| std::slice::from_raw_parts_mut(ptr as *mut _ as *mut T, count))
} }
/// Creates a writer for pushing components into or removing from the vec. /// Creates a writer for pushing components into or removing from the vec.
@ -1572,7 +1727,7 @@ impl<'a> ComponentWriter<'a> {
/// # Safety /// # Safety
/// ///
/// Ensure that this function is only ever called once on a given index. /// Ensure that this function is only ever called once on a given index.
pub unsafe fn drop_in_place(&mut self, index: usize) { pub unsafe fn drop_in_place(&mut self, ComponentIndex(index): ComponentIndex) {
if let Some(drop_fn) = self.accessor.drop_fn { if let Some(drop_fn) = self.accessor.drop_fn {
let size = self.accessor.element_size; let size = self.accessor.element_size;
let to_remove = self.ptr.add(size * index); let to_remove = self.ptr.add(size * index);
@ -1805,10 +1960,9 @@ mod test {
let chunk_index = data.get_free_chunk(set, 1); let chunk_index = data.get_free_chunk(set, 1);
let components = data let components = data
.chunksets_mut() .chunkset_mut(set)
.get_mut(set)
.unwrap() .unwrap()
.get_mut(chunk_index) .chunk_mut(chunk_index)
.unwrap(); .unwrap();
let mut writer = components.writer(); let mut writer = components.writer();
let (chunk_entities, chunk_components) = writer.get(); let (chunk_entities, chunk_components) = writer.get();
@ -1840,10 +1994,9 @@ mod test {
let chunk_index = data.get_free_chunk(set, 1); let chunk_index = data.get_free_chunk(set, 1);
let chunk = data let chunk = data
.chunksets_mut() .chunkset_mut(set)
.get_mut(set)
.unwrap() .unwrap()
.get_mut(chunk_index) .chunk_mut(chunk_index)
.unwrap(); .unwrap();
assert!(!chunk.is_allocated()); assert!(!chunk.is_allocated());
@ -1870,10 +2023,9 @@ mod test {
let chunk_index = data.get_free_chunk(set, 1); let chunk_index = data.get_free_chunk(set, 1);
let chunk = data let chunk = data
.chunksets_mut() .chunkset_mut(set)
.get_mut(set)
.unwrap() .unwrap()
.get_mut(chunk_index) .chunk_mut(chunk_index)
.unwrap(); .unwrap();
assert!(!chunk.is_allocated()); assert!(!chunk.is_allocated());
@ -1894,7 +2046,7 @@ mod test {
assert!(chunk.is_allocated()); assert!(chunk.is_allocated());
chunk.swap_remove(0, true); chunk.swap_remove(ComponentIndex(0), true);
assert!(!chunk.is_allocated()); assert!(!chunk.is_allocated());
} }
@ -1914,10 +2066,9 @@ mod test {
let set = data.alloc_chunk_set(|_| {}); let set = data.alloc_chunk_set(|_| {});
let chunk_index = data.get_free_chunk(set, 1); let chunk_index = data.get_free_chunk(set, 1);
let components = data let components = data
.chunksets_mut() .chunkset_mut(set)
.get_mut(set)
.unwrap() .unwrap()
.get_mut(chunk_index) .chunk_mut(chunk_index)
.unwrap(); .unwrap();
let entities = [ let entities = [
@ -2045,10 +2196,9 @@ mod test {
let chunk_index = data.get_free_chunk(set, 1); let chunk_index = data.get_free_chunk(set, 1);
let components = data let components = data
.chunksets_mut() .chunkset_mut(set)
.get_mut(set)
.unwrap() .unwrap()
.get_mut(chunk_index) .chunk_mut(chunk_index)
.unwrap(); .unwrap();
let mut writer = components.writer(); let mut writer = components.writer();
let (chunk_entities, chunk_components) = writer.get(); let (chunk_entities, chunk_components) = writer.get();
@ -2080,10 +2230,9 @@ mod test {
let chunk_index = data.get_free_chunk(set, 1); let chunk_index = data.get_free_chunk(set, 1);
let components = data let components = data
.chunksets_mut() .chunkset_mut(set)
.get_mut(set)
.unwrap() .unwrap()
.get_mut(chunk_index) .chunk_mut(chunk_index)
.unwrap(); .unwrap();
let mut writer = components.writer(); let mut writer = components.writer();
let (chunk_entities, chunk_components) = writer.get(); let (chunk_entities, chunk_components) = writer.get();

View file

@ -0,0 +1,35 @@
[package]
name = "legion-systems"
version = "0.2.1"
description = "High performance entity component system (ECS) library"
authors = ["Thomas Gillen <thomas.gillen@googlemail.com>"]
repository = "https://github.com/TomGillen/legion"
keywords = ["ecs", "game"]
categories = ["game-engines", "data-structures"]
readme = "readme.md"
license = "MIT"
edition = "2018"
[badges]
travis-ci = { repository = "TomGillen/legion", branch = "master" }
[features]
par-iter = ["rayon", "legion-core/par-iter"]
par-schedule = ["rayon", "crossbeam-queue"]
[dependencies]
legion-core = { path = "../legion_core", version = "0.2.1", default-features = false }
downcast-rs = "1.0"
itertools = "0.8"
rayon = { version = "1.2", optional = true }
crossbeam-queue = { version = "0.2.0", optional = true }
crossbeam-channel = "0.4.0"
derivative = "1"
bit-set = "0.5"
paste = "0.1"
tracing = "0.1"
fxhash = "0.2"
[dev-dependencies]
tracing-subscriber = "0.2"

View file

@ -0,0 +1,16 @@
pub mod resource;
pub mod schedule;
mod system;
pub use bit_set;
pub use system::*;
pub mod prelude {
pub use crate::{
bit_set::BitSet,
resource::{ResourceSet, Resources},
schedule::{Executor, Runnable, Schedulable, Schedule},
System, SystemBuilder,
};
}

View file

@ -1,13 +1,42 @@
use crate::borrow::{AtomicRefCell, Ref, RefMut, DowncastTypename}; use legion_core::borrow::DowncastTypename;
use crate::query::{Read, ReadOnly, Write};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use fxhash::FxHashMap; use fxhash::FxHashMap;
use legion_core::borrow::{AtomicRefCell, Ref, RefMut};
use legion_core::query::{Read, ReadOnly, Write};
use std::{ use std::{
any::type_name, any::{Any, type_name},
marker::PhantomData, marker::PhantomData,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
impl DowncastTypename for dyn Resource {
#[inline(always)]
fn downcast_typename_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is_typename::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe { Some(&mut *(self.as_any_mut() as *mut dyn Any as *mut T)) }
} else {
None
}
}
#[inline(always)]
fn downcast_typename_ref<T: Any>(&self) -> Option<&T> {
if self.is_typename::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe { Some(&*(self.as_any() as *const dyn Any as *const T)) }
} else {
None
}
}
#[inline(always)]
fn is_typename<T: Any>(&self) -> bool {
true
// TODO: it would be nice to add type safety here, but the type names don't match
// println!("{} {}", type_name_of_val(self), type_name::<T>());
// type_name_of_val(self) == type_name::<T>()
}
}
#[cfg(not(feature = "ffi"))] #[cfg(not(feature = "ffi"))]
/// A type ID identifying a component type. /// A type ID identifying a component type.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
@ -39,7 +68,8 @@ impl ResourceTypeId {
/// struct TypeA(usize); /// struct TypeA(usize);
/// struct TypeB(usize); /// struct TypeB(usize);
/// ///
/// use legion::prelude::*; /// use legion_core::prelude::*;
/// use legion_systems::prelude::*;
/// let mut resources = Resources::default(); /// let mut resources = Resources::default();
/// resources.insert(TypeA(55)); /// resources.insert(TypeA(55));
/// resources.insert(TypeB(12)); /// resources.insert(TypeB(12));

View file

@ -1,9 +1,15 @@
use crate::system::SystemId;
use crate::{ use crate::{
borrow::RefMut, command::CommandBuffer, resource::ResourceTypeId, storage::ComponentTypeId, resource::{ResourceTypeId, Resources},
world::World, system::SystemId,
}; };
use bit_set::BitSet; use bit_set::BitSet;
use legion_core::{
borrow::RefMut,
command::CommandBuffer,
storage::ComponentTypeId,
world::{World, WorldId},
};
use std::cell::UnsafeCell;
#[cfg(feature = "par-schedule")] #[cfg(feature = "par-schedule")]
use tracing::{span, trace, Level}; use tracing::{span, trace, Level};
@ -52,13 +58,38 @@ impl ArchetypeAccess {
/// Trait describing a schedulable type. This is implemented by `System` /// Trait describing a schedulable type. This is implemented by `System`
pub trait Runnable { pub trait Runnable {
/// Gets the name of the system.
fn name(&self) -> &SystemId; fn name(&self) -> &SystemId;
/// Gets the resources and component types read by the system.
fn reads(&self) -> (&[ResourceTypeId], &[ComponentTypeId]); fn reads(&self) -> (&[ResourceTypeId], &[ComponentTypeId]);
/// Gets the resources and component types written by the system.
fn writes(&self) -> (&[ResourceTypeId], &[ComponentTypeId]); fn writes(&self) -> (&[ResourceTypeId], &[ComponentTypeId]);
/// Prepares the system for execution against a world.
fn prepare(&mut self, world: &World); fn prepare(&mut self, world: &World);
/// Gets the set of archetypes the system will access when run,
/// as determined when the system was last prepared.
fn accesses_archetypes(&self) -> &ArchetypeAccess; fn accesses_archetypes(&self) -> &ArchetypeAccess;
fn run(&self, world: &World);
fn command_buffer_mut(&self) -> RefMut<CommandBuffer>; /// Runs the system.
///
/// # Safety
///
/// The shared references to world and resources may result in
/// unsound mutable aliasing if other code is accessing the same components or
/// resources as this system. Prefer to use `run` when possible.
unsafe fn run_unsafe(&mut self, world: &World, resources: &Resources);
/// Gets the system's command buffer.
fn command_buffer_mut(&self, world: WorldId) -> Option<RefMut<CommandBuffer>>;
/// Runs the system.
fn run(&mut self, world: &mut World, resources: &mut Resources) {
unsafe { self.run_unsafe(world, resources) };
}
} }
/// Executes a sequence of systems, potentially in parallel, and then commits their command buffers. /// Executes a sequence of systems, potentially in parallel, and then commits their command buffers.
@ -67,7 +98,7 @@ pub trait Runnable {
/// may run some systems in parallel. The order in which side-effects (e.g. writes to resources /// may run some systems in parallel. The order in which side-effects (e.g. writes to resources
/// or entities) are observed is maintained. /// or entities) are observed is maintained.
pub struct Executor { pub struct Executor {
systems: Vec<Box<dyn Schedulable>>, systems: Vec<SystemBox>,
#[cfg(feature = "par-schedule")] #[cfg(feature = "par-schedule")]
static_dependants: Vec<Vec<usize>>, static_dependants: Vec<Vec<usize>>,
#[cfg(feature = "par-schedule")] #[cfg(feature = "par-schedule")]
@ -78,13 +109,38 @@ pub struct Executor {
awaiting: Vec<AtomicUsize>, awaiting: Vec<AtomicUsize>,
} }
struct SystemBox(UnsafeCell<Box<dyn Schedulable>>);
// NOT SAFE:
// This type is only safe to use as Send and Sync within
// the constraints of how it is used inside Executor
unsafe impl Send for SystemBox {}
unsafe impl Sync for SystemBox {}
impl SystemBox {
#[cfg(feature = "par-schedule")]
unsafe fn get(&self) -> &dyn Schedulable { std::ops::Deref::deref(&*self.0.get()) }
#[allow(clippy::mut_from_ref)]
unsafe fn get_mut(&self) -> &mut dyn Schedulable {
std::ops::DerefMut::deref_mut(&mut *self.0.get())
}
}
impl Executor { impl Executor {
/// Constructs a new executor for all systems to be run in a single stage. /// Constructs a new executor for all systems to be run in a single stage.
/// ///
/// Systems are provided in the order in which side-effects (e.g. writes to resources or entities) /// Systems are provided in the order in which side-effects (e.g. writes to resources or entities)
/// are to be observed. /// are to be observed.
#[cfg(not(feature = "par-schedule"))] #[cfg(not(feature = "par-schedule"))]
pub fn new(systems: Vec<Box<dyn Schedulable>>) -> Self { Self { systems } } pub fn new(systems: Vec<Box<dyn Schedulable>>) -> Self {
Self {
systems: systems
.into_iter()
.map(|s| SystemBox(UnsafeCell::new(s)))
.collect(),
}
}
/// Constructs a new executor for all systems to be run in a single stage. /// Constructs a new executor for all systems to be run in a single stage.
/// ///
@ -208,7 +264,10 @@ impl Executor {
static_dependants, static_dependants,
dynamic_dependants, dynamic_dependants,
static_dependency_counts, static_dependency_counts,
systems, systems: systems
.into_iter()
.map(|s| SystemBox(UnsafeCell::new(s)))
.collect(),
} }
} else { } else {
Executor { Executor {
@ -216,17 +275,22 @@ impl Executor {
static_dependants: Vec::with_capacity(0), static_dependants: Vec::with_capacity(0),
dynamic_dependants: Vec::with_capacity(0), dynamic_dependants: Vec::with_capacity(0),
static_dependency_counts: Vec::with_capacity(0), static_dependency_counts: Vec::with_capacity(0),
systems, systems: systems
.into_iter()
.map(|s| SystemBox(UnsafeCell::new(s)))
.collect(),
} }
} }
} }
/// Converts this executor into a vector of its component systems. /// Converts this executor into a vector of its component systems.
pub fn into_vec(self) -> Vec<Box<dyn Schedulable>> { self.systems } pub fn into_vec(self) -> Vec<Box<dyn Schedulable>> {
self.systems.into_iter().map(|s| s.0.into_inner()).collect()
}
/// Executes all systems and then flushes their command buffers. /// Executes all systems and then flushes their command buffers.
pub fn execute(&mut self, world: &mut World) { pub fn execute(&mut self, world: &mut World, resources: &mut Resources) {
self.run_systems(world); self.run_systems(world, resources);
self.flush_command_buffers(world); self.flush_command_buffers(world);
} }
@ -234,15 +298,10 @@ impl Executor {
/// ///
/// Only enabled with par-schedule is disabled /// Only enabled with par-schedule is disabled
#[cfg(not(feature = "par-schedule"))] #[cfg(not(feature = "par-schedule"))]
pub fn run_systems(&mut self, world: &mut World) { pub fn run_systems(&mut self, world: &mut World, resources: &mut Resources) {
// preflush command buffers
// This also handles the first case of allocating them.
self.systems
.iter()
.for_each(|system| system.command_buffer_mut().write(world));
self.systems.iter_mut().for_each(|system| { self.systems.iter_mut().for_each(|system| {
system.run(world); let system = unsafe { system.get_mut() };
system.run(world, resources);
}); });
} }
@ -253,19 +312,14 @@ impl Executor {
/// ///
/// Call from within `rayon::ThreadPool::install()` to execute within a specific thread pool. /// Call from within `rayon::ThreadPool::install()` to execute within a specific thread pool.
#[cfg(feature = "par-schedule")] #[cfg(feature = "par-schedule")]
pub fn run_systems(&mut self, world: &mut World) { pub fn run_systems(&mut self, world: &mut World, resources: &mut Resources) {
// preflush command buffers
// This also handles the first case of allocating them.
self.systems
.iter()
.for_each(|system| system.command_buffer_mut().write(world));
rayon::join( rayon::join(
|| {}, || {},
|| { || {
match self.systems.len() { match self.systems.len() {
1 => { 1 => {
self.systems[0].run(world); // safety: we have exlusive access to all systems, world and resources here
unsafe { self.systems[0].get_mut().run(world, resources) };
} }
_ => { _ => {
let systems = &mut self.systems; let systems = &mut self.systems;
@ -273,7 +327,9 @@ impl Executor {
let awaiting = &mut self.awaiting; let awaiting = &mut self.awaiting;
// prepare all systems - archetype filters are pre-executed here // prepare all systems - archetype filters are pre-executed here
systems.par_iter_mut().for_each(|sys| sys.prepare(world)); systems
.par_iter_mut()
.for_each(|sys| unsafe { sys.get_mut() }.prepare(world));
// determine dynamic dependencies // determine dynamic dependencies
izip!( izip!(
@ -283,10 +339,11 @@ impl Executor {
) )
.par_bridge() .par_bridge()
.for_each(|(sys, static_dep, dyn_dep)| { .for_each(|(sys, static_dep, dyn_dep)| {
let archetypes = sys.accesses_archetypes(); // safety: systems is held exclusively, and we are only reading each system
let archetypes = unsafe { sys.get() }.accesses_archetypes();
for i in (0..dyn_dep.len()).rev() { for i in (0..dyn_dep.len()).rev() {
let dep = dyn_dep[i]; let dep = dyn_dep[i];
let other = &systems[dep]; let other = unsafe { systems[dep].get() };
// if the archetype sets intersect, // if the archetype sets intersect,
// then we can move the dynamic dependant into the static dependants set // then we can move the dynamic dependant into the static dependants set
@ -309,7 +366,9 @@ impl Executor {
(0..systems.len()) (0..systems.len())
.filter(|i| awaiting[*i].load(Ordering::SeqCst) == 0) .filter(|i| awaiting[*i].load(Ordering::SeqCst) == 0)
.for_each(|i| { .for_each(|i| {
self.run_recursive(i, world); // safety: we are at the root of the execution tree, so we know each
// index is exclusive here
unsafe { self.run_recursive(i, world, resources) };
}); });
} }
} }
@ -320,14 +379,23 @@ impl Executor {
/// Flushes the recorded command buffers for all systems. /// Flushes the recorded command buffers for all systems.
pub fn flush_command_buffers(&mut self, world: &mut World) { pub fn flush_command_buffers(&mut self, world: &mut World) {
self.systems.iter().for_each(|system| { self.systems.iter().for_each(|system| {
system.command_buffer_mut().write(world); // safety: systems are exlcusive due to &mut self
let system = unsafe { system.get_mut() };
if let Some(mut cmd) = system.command_buffer_mut(world.id()) {
cmd.write(world);
}
}); });
} }
/// Recursively execute through the generated depedency cascade and exhaust it. /// Recursively execute through the generated depedency cascade and exhaust it.
///
/// # Safety
///
/// Ensure the system indexed by `i` is only accessed once.
#[cfg(feature = "par-schedule")] #[cfg(feature = "par-schedule")]
fn run_recursive(&self, i: usize, world: &World) { unsafe fn run_recursive(&self, i: usize, world: &World, resources: &Resources) {
self.systems[i].run(world); // safety: the caller ensures nothing else is accessing systems[i]
self.systems[i].get_mut().run_unsafe(world, resources);
self.static_dependants[i].par_iter().for_each(|dep| { self.static_dependants[i].par_iter().for_each(|dep| {
match self.awaiting[*dep].compare_exchange( match self.awaiting[*dep].compare_exchange(
@ -337,7 +405,8 @@ impl Executor {
Ordering::Relaxed, Ordering::Relaxed,
) { ) {
Ok(_) => { Ok(_) => {
self.run_recursive(*dep, world); // safety: each dependency is unique, so run_recursive is safe to call
self.run_recursive(*dep, world, resources);
} }
Err(_) => { Err(_) => {
self.awaiting[*dep].fetch_sub(1, Ordering::Relaxed); self.awaiting[*dep].fetch_sub(1, Ordering::Relaxed);
@ -378,18 +447,21 @@ impl Builder {
} }
/// Adds a thread local function to the schedule. This function will be executed on the main thread. /// Adds a thread local function to the schedule. This function will be executed on the main thread.
pub fn add_thread_local_fn<F: FnMut(&mut World) + 'static>(mut self, f: F) -> Self { pub fn add_thread_local_fn<F: FnMut(&mut World, &mut Resources) + 'static>(
mut self,
f: F,
) -> Self {
self.finalize_executor(); self.finalize_executor();
self.steps.push(Step::ThreadLocalFn( self.steps.push(Step::ThreadLocalFn(
Box::new(f) as Box<dyn FnMut(&mut World)> Box::new(f) as Box<dyn FnMut(&mut World, &mut Resources)>
)); ));
self self
} }
/// Adds a thread local system to the schedule. This system will be executed on the main thread. /// Adds a thread local system to the schedule. This system will be executed on the main thread.
pub fn add_thread_local<S: Into<Box<dyn Runnable>>>(self, system: S) -> Self { pub fn add_thread_local<S: Into<Box<dyn Runnable>>>(self, system: S) -> Self {
let system = system.into(); let mut system = system.into();
self.add_thread_local_fn(move |world| system.run(world)) self.add_thread_local_fn(move |world, resources| system.run(world, resources))
} }
/// Finalizes the builder into a `Schedule`. /// Finalizes the builder into a `Schedule`.
@ -412,7 +484,7 @@ pub enum Step {
/// Flush system command buffers. /// Flush system command buffers.
FlushCmdBuffers, FlushCmdBuffers,
/// A thread local function. /// A thread local function.
ThreadLocalFn(Box<dyn FnMut(&mut World)>), ThreadLocalFn(Box<dyn FnMut(&mut World, &mut Resources)>),
} }
/// A schedule of systems for execution. /// A schedule of systems for execution.
@ -420,11 +492,13 @@ pub enum Step {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # use legion_systems::prelude::*;
/// # let find_collisions = SystemBuilder::new("find_collisions").build(|_,_,_,_| {}); /// # let find_collisions = SystemBuilder::new("find_collisions").build(|_,_,_,_| {});
/// # let calculate_acceleration = SystemBuilder::new("calculate_acceleration").build(|_,_,_,_| {}); /// # let calculate_acceleration = SystemBuilder::new("calculate_acceleration").build(|_,_,_,_| {});
/// # let update_positions = SystemBuilder::new("update_positions").build(|_,_,_,_| {}); /// # let update_positions = SystemBuilder::new("update_positions").build(|_,_,_,_| {});
/// # let mut world = World::new(); /// let mut world = World::new();
/// let mut resources = Resources::default();
/// let mut schedule = Schedule::builder() /// let mut schedule = Schedule::builder()
/// .add_system(find_collisions) /// .add_system(find_collisions)
/// .flush() /// .flush()
@ -432,7 +506,7 @@ pub enum Step {
/// .add_system(update_positions) /// .add_system(update_positions)
/// .build(); /// .build();
/// ///
/// schedule.execute(&mut world); /// schedule.execute(&mut world, &mut resources);
/// ``` /// ```
pub struct Schedule { pub struct Schedule {
steps: Vec<Step>, steps: Vec<Step>,
@ -443,18 +517,18 @@ impl Schedule {
pub fn builder() -> Builder { Builder::default() } pub fn builder() -> Builder { Builder::default() }
/// Executes all of the steps in the schedule. /// Executes all of the steps in the schedule.
pub fn execute(&mut self, world: &mut World) { pub fn execute(&mut self, world: &mut World, resources: &mut Resources) {
let mut waiting_flush: Vec<&mut Executor> = Vec::new(); let mut waiting_flush: Vec<&mut Executor> = Vec::new();
for step in &mut self.steps { for step in &mut self.steps {
match step { match step {
Step::Systems(executor) => { Step::Systems(executor) => {
executor.run_systems(world); executor.run_systems(world, resources);
waiting_flush.push(executor); waiting_flush.push(executor);
} }
Step::FlushCmdBuffers => waiting_flush Step::FlushCmdBuffers => waiting_flush
.drain(..) .drain(..)
.for_each(|e| e.flush_command_buffers(world)), .for_each(|e| e.flush_command_buffers(world)),
Step::ThreadLocalFn(function) => function(world), Step::ThreadLocalFn(function) => function(world, resources),
} }
} }
} }
@ -480,6 +554,7 @@ mod tests {
use super::*; use super::*;
use crate::prelude::*; use crate::prelude::*;
use itertools::sorted; use itertools::sorted;
use legion_core::prelude::*;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[test] #[test]
@ -490,7 +565,8 @@ mod tests {
#[derive(Default)] #[derive(Default)]
struct Resource; struct Resource;
world.resources.insert(Resource); let mut resources = Resources::default();
resources.insert(Resource);
let order = Arc::new(Mutex::new(Vec::new())); let order = Arc::new(Mutex::new(Vec::new()));
@ -513,7 +589,7 @@ mod tests {
.add_system(system_three) .add_system(system_three)
.build(); .build();
schedule.execute(&mut world); schedule.execute(&mut world, &mut resources);
let order = order.lock().unwrap(); let order = order.lock().unwrap();
let sorted: Vec<usize> = sorted(order.clone()).collect(); let sorted: Vec<usize> = sorted(order.clone()).collect();
@ -524,12 +600,13 @@ mod tests {
fn flush() { fn flush() {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
let mut resources = Resources::default();
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
struct TestComp(f32, f32, f32); struct TestComp(f32, f32, f32);
let system_one = SystemBuilder::new("one").build(move |cmd, _, _, _| { let system_one = SystemBuilder::new("one").build(move |cmd, _, _, _| {
cmd.insert((), vec![(TestComp(0., 0., 0.),)]).unwrap(); cmd.insert((), vec![(TestComp(0., 0., 0.),)]);
}); });
let system_two = SystemBuilder::new("two") let system_two = SystemBuilder::new("two")
.with_query(Write::<TestComp>::query()) .with_query(Write::<TestComp>::query())
@ -545,6 +622,6 @@ mod tests {
.add_system(system_three) .add_system(system_three)
.build(); .build();
schedule.execute(&mut world); schedule.execute(&mut world, &mut resources);
} }
} }

View file

@ -1,31 +1,32 @@
use crate::borrow::{AtomicRefCell, Ref, RefMut}; use crate::resource::{Resource, ResourceSet, ResourceTypeId, Resources};
use crate::command::CommandBuffer;
use crate::cons::{ConsAppend, ConsFlatten};
use crate::entity::Entity;
use crate::filter::EntityFilter;
use crate::query::ReadOnly;
use crate::query::{ChunkDataIter, ChunkEntityIter, ChunkViewIter, Query, Read, View, Write};
use crate::resource::{Resource, ResourceSet, ResourceTypeId};
use crate::schedule::ArchetypeAccess; use crate::schedule::ArchetypeAccess;
use crate::schedule::{Runnable, Schedulable}; use crate::schedule::{Runnable, Schedulable};
use crate::storage::Tag;
use crate::storage::{Component, ComponentTypeId, TagTypeId};
use crate::world::World;
use bit_set::BitSet; use bit_set::BitSet;
use derivative::Derivative; use derivative::Derivative;
use fxhash::FxHashMap;
use legion_core::borrow::{AtomicRefCell, Ref, RefMut};
use legion_core::command::CommandBuffer;
use legion_core::cons::{ConsAppend, ConsFlatten};
use legion_core::entity::Entity;
use legion_core::filter::EntityFilter;
use legion_core::index::ArchetypeIndex;
use legion_core::query::ReadOnly;
use legion_core::query::{ChunkDataIter, ChunkEntityIter, ChunkViewIter, Query, Read, View, Write};
use legion_core::storage::Tag;
use legion_core::storage::{Component, ComponentTypeId, TagTypeId};
use legion_core::world::World;
use legion_core::world::WorldId;
use std::any::TypeId; use std::any::TypeId;
use std::borrow::Cow; use std::borrow::Cow;
use std::marker::PhantomData; use std::marker::PhantomData;
use tracing::{debug, info, span, Level}; use tracing::{debug, info, span, Level};
#[cfg(feature = "par-iter")] #[cfg(feature = "par-iter")]
use crate::filter::{ArchetypeFilterData, ChunkFilterData, ChunksetFilterData, Filter}; use legion_core::{
filter::{ArchetypeFilterData, ChunkFilterData, ChunksetFilterData, Filter},
#[cfg(feature = "par-iter")] iterator::FissileIterator,
use crate::iterator::FissileIterator; query::Chunk,
};
#[cfg(feature = "par-iter")]
use crate::query::Chunk;
/// Structure used by `SystemAccess` for describing access to the provided `T` /// Structure used by `SystemAccess` for describing access to the provided `T`
#[derive(Derivative, Debug, Clone)] #[derive(Derivative, Debug, Clone)]
@ -559,7 +560,7 @@ macro_rules! impl_queryset_tuple {
$( $(
let storage = world.storage(); let storage = world.storage();
$ty.filter.iter_archetype_indexes(storage).for_each(|id| { bitset.insert(id); }); $ty.filter.iter_archetype_indexes(storage).for_each(|ArchetypeIndex(id)| { bitset.insert(id); });
)* )*
} }
unsafe fn prepare(&mut self) -> Self::Queries { unsafe fn prepare(&mut self) -> Self::Queries {
@ -585,9 +586,11 @@ where
type Queries = SystemQuery<AV, AF>; type Queries = SystemQuery<AV, AF>;
fn filter_archetypes(&mut self, world: &World, bitset: &mut BitSet) { fn filter_archetypes(&mut self, world: &World, bitset: &mut BitSet) {
let storage = world.storage(); let storage = world.storage();
self.filter.iter_archetype_indexes(storage).for_each(|id| { self.filter
bitset.insert(id); .iter_archetype_indexes(storage)
}); .for_each(|ArchetypeIndex(id)| {
bitset.insert(id);
});
} }
unsafe fn prepare(&mut self) -> Self::Queries { SystemQuery::<AV, AF>::new(self) } unsafe fn prepare(&mut self) -> Self::Queries { SystemQuery::<AV, AF>::new(self) }
} }
@ -652,9 +655,8 @@ impl SubWorld {
fn validate_archetype_access(&self, entity: Entity) -> bool { fn validate_archetype_access(&self, entity: Entity) -> bool {
unsafe { unsafe {
if let Some(archetypes) = self.archetypes { if let Some(archetypes) = self.archetypes {
if let Some(location) = (*self.world).entity_allocator.get_location(entity.index()) if let Some(location) = (*self.world).get_entity_location(entity) {
{ return (*archetypes).contains(*location.archetype());
return (*archetypes).contains(location.archetype());
} }
} }
} }
@ -806,7 +808,7 @@ where
>, >,
{ {
name: SystemId, name: SystemId,
resources: R, _resources: PhantomData<R>,
queries: AtomicRefCell<Q>, queries: AtomicRefCell<Q>,
run_fn: AtomicRefCell<F>, run_fn: AtomicRefCell<F>,
archetypes: ArchetypeAccess, archetypes: ArchetypeAccess,
@ -816,7 +818,7 @@ where
access: SystemAccess, access: SystemAccess,
// We pre-allocate a command buffer for ourself. Writes are self-draining so we never have to rellocate. // We pre-allocate a command buffer for ourself. Writes are self-draining so we never have to rellocate.
command_buffer: AtomicRefCell<CommandBuffer>, command_buffer: FxHashMap<WorldId, AtomicRefCell<CommandBuffer>>,
} }
impl<R, Q, F> Runnable for System<R, Q, F> impl<R, Q, F> Runnable for System<R, Q, F>
@ -848,28 +850,29 @@ where
fn accesses_archetypes(&self) -> &ArchetypeAccess { &self.archetypes } fn accesses_archetypes(&self) -> &ArchetypeAccess { &self.archetypes }
fn command_buffer_mut(&self) -> RefMut<CommandBuffer> { self.command_buffer.get_mut() } fn command_buffer_mut(&self, world: WorldId) -> Option<RefMut<CommandBuffer>> {
self.command_buffer.get(&world).map(|cmd| cmd.get_mut())
}
fn run(&self, world: &World) { unsafe fn run_unsafe(&mut self, world: &World, resources: &Resources) {
let span = span!(Level::INFO, "System", system = %self.name); let span = span!(Level::INFO, "System", system = %self.name);
let _guard = span.enter(); let _guard = span.enter();
debug!("Initializing"); debug!("Initializing");
let mut resources = unsafe { R::fetch_unchecked(&world.resources) }; let mut resources = R::fetch_unchecked(resources);
let mut queries = self.queries.get_mut(); let mut queries = self.queries.get_mut();
let mut prepared_queries = unsafe { queries.prepare() }; let mut prepared_queries = queries.prepare();
let mut world_shim = let mut world_shim = SubWorld::new(world, &self.access.components, &self.archetypes);
unsafe { SubWorld::new(world, &self.access.components, &self.archetypes) }; let cmd = self
.command_buffer
// Give the command buffer a new entity block. .entry(world.id())
// This should usually just pull a free block, or allocate a new one... .or_insert_with(|| AtomicRefCell::new(CommandBuffer::new(world)));
// TODO: The BlockAllocator should *ensure* keeping at least 1 free block so this prevents an allocation
info!("Running"); info!("Running");
use std::ops::DerefMut; use std::ops::DerefMut;
let mut borrow = self.run_fn.get_mut(); let mut borrow = self.run_fn.get_mut();
borrow.deref_mut().run( borrow.deref_mut().run(
&mut self.command_buffer.get_mut(), &mut cmd.get_mut(),
&mut world_shim, &mut world_shim,
&mut resources, &mut resources,
&mut prepared_queries, &mut prepared_queries,
@ -924,7 +927,8 @@ where
/// as singular closures for a given system - providing queries which should be cached for that /// as singular closures for a given system - providing queries which should be cached for that
/// system, as well as resource access and other metadata. /// system, as well as resource access and other metadata.
/// ```rust /// ```rust
/// # use legion::prelude::*; /// # use legion_core::prelude::*;
/// # use legion_systems::prelude::*;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
/// # struct Position; /// # struct Position;
/// # #[derive(Copy, Clone, Debug, PartialEq)] /// # #[derive(Copy, Clone, Debug, PartialEq)]
@ -1119,7 +1123,7 @@ where
Box::new(System { Box::new(System {
name: self.name, name: self.name,
run_fn: AtomicRefCell::new(run_fn), run_fn: AtomicRefCell::new(run_fn),
resources: self.resources.flatten(), _resources: PhantomData::<<R as ConsFlatten>::Output>,
queries: AtomicRefCell::new(self.queries.flatten()), queries: AtomicRefCell::new(self.queries.flatten()),
archetypes: if self.access_all_archetypes { archetypes: if self.access_all_archetypes {
ArchetypeAccess::All ArchetypeAccess::All
@ -1131,7 +1135,7 @@ where
components: self.component_access, components: self.component_access,
tags: Access::default(), tags: Access::default(),
}, },
command_buffer: AtomicRefCell::new(CommandBuffer::default()), command_buffer: FxHashMap::default(),
}) })
} }
@ -1154,7 +1158,7 @@ where
Box::new(System { Box::new(System {
name: self.name, name: self.name,
run_fn: AtomicRefCell::new(run_fn), run_fn: AtomicRefCell::new(run_fn),
resources: self.resources.flatten(), _resources: PhantomData::<<R as ConsFlatten>::Output>,
queries: AtomicRefCell::new(self.queries.flatten()), queries: AtomicRefCell::new(self.queries.flatten()),
archetypes: if self.access_all_archetypes { archetypes: if self.access_all_archetypes {
ArchetypeAccess::All ArchetypeAccess::All
@ -1166,7 +1170,7 @@ where
components: self.component_access, components: self.component_access,
tags: Access::default(), tags: Access::default(),
}, },
command_buffer: AtomicRefCell::new(CommandBuffer::default()), command_buffer: FxHashMap::default(),
}) })
} }
} }
@ -1174,8 +1178,8 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::prelude::*;
use crate::schedule::*; use crate::schedule::*;
use legion_core::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -1206,8 +1210,10 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
world.resources.insert(TestResource(123));
world.resources.insert(TestResourceTwo(123)); let mut resources = Resources::default();
resources.insert(TestResource(123));
resources.insert(TestResourceTwo(123));
let components = vec![ let components = vec![
(Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)), (Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)),
@ -1290,7 +1296,7 @@ mod tests {
let systems = vec![system_one, system_two, system_three, system_four]; let systems = vec![system_one, system_two, system_three, system_four];
let mut executor = Executor::new(systems); let mut executor = Executor::new(systems);
executor.execute(&mut world); executor.execute(&mut world, &mut resources);
assert_eq!(*(runs.lock().unwrap()), order); assert_eq!(*(runs.lock().unwrap()), order);
} }
@ -1301,7 +1307,9 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
world.resources.insert(TestResource(123));
let mut resources = Resources::default();
resources.insert(TestResource(123));
let components = vec![ let components = vec![
(Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)), (Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)),
@ -1333,7 +1341,7 @@ mod tests {
assert_eq!(components.len(), count); assert_eq!(components.len(), count);
}); });
system.prepare(&world); system.prepare(&world);
system.run(&world); system.run(&mut world, &mut resources);
} }
#[test] #[test]
@ -1342,7 +1350,9 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
world.resources.insert(TestResource(123));
let mut resources = Resources::default();
resources.insert(TestResource(123));
let components = vec![ let components = vec![
(Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)), (Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)),
@ -1367,7 +1377,7 @@ mod tests {
}); });
system.prepare(&world); system.prepare(&world);
system.run(&world); system.run(&mut world, &mut resources);
} }
#[test] #[test]
@ -1376,6 +1386,7 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
let mut resources = Resources::default();
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
pub struct Balls(u32); pub struct Balls(u32);
@ -1410,14 +1421,14 @@ mod tests {
}); });
system.prepare(&world); system.prepare(&world);
system.run(&world); system.run(&mut world, &mut resources);
world world
.add_component(*(expected.keys().nth(0).unwrap()), Balls::default()) .add_component(*(expected.keys().nth(0).unwrap()), Balls::default())
.unwrap(); .unwrap();
system.prepare(&world); system.prepare(&world);
system.run(&world); system.run(&mut world, &mut resources);
} }
#[test] #[test]
@ -1426,6 +1437,7 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
let mut resources = Resources::default();
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
pub struct Balls(u32); pub struct Balls(u32);
@ -1461,12 +1473,15 @@ mod tests {
}); });
system.prepare(&world); system.prepare(&world);
system.run(&world); system.run(&mut world, &mut resources);
system.command_buffer_mut().write(&mut world); system
.command_buffer_mut(world.id())
.unwrap()
.write(&mut world);
system.prepare(&world); system.prepare(&world);
system.run(&world); system.run(&mut world, &mut resources);
} }
#[test] #[test]
@ -1480,7 +1495,9 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
world.resources.insert(AtomicRes::default());
let mut resources = Resources::default();
resources.insert(AtomicRes::default());
let system1 = SystemBuilder::<()>::new("TestSystem1") let system1 = SystemBuilder::<()>::new("TestSystem1")
.write_resource::<AtomicRes>() .write_resource::<AtomicRes>()
@ -1521,13 +1538,12 @@ mod tests {
let mut executor = Executor::new(systems); let mut executor = Executor::new(systems);
pool.install(|| { pool.install(|| {
for _ in 0..1000 { for _ in 0..1000 {
executor.execute(&mut world); executor.execute(&mut world, &mut resources);
} }
}); });
assert_eq!( assert_eq!(
world resources
.resources
.get::<AtomicRes>() .get::<AtomicRes>()
.unwrap() .unwrap()
.0 .0
@ -1548,7 +1564,9 @@ mod tests {
let universe = Universe::new(); let universe = Universe::new();
let mut world = universe.create_world(); let mut world = universe.create_world();
world.resources.insert(AtomicRes::default());
let mut resources = Resources::default();
resources.insert(AtomicRes::default());
let system1 = SystemBuilder::<()>::new("TestSystem1") let system1 = SystemBuilder::<()>::new("TestSystem1")
.read_resource::<AtomicRes>() .read_resource::<AtomicRes>()
@ -1589,7 +1607,7 @@ mod tests {
let mut executor = Executor::new(systems); let mut executor = Executor::new(systems);
pool.install(|| { pool.install(|| {
for _ in 0..1000 { for _ in 0..1000 {
executor.execute(&mut world); executor.execute(&mut world, &mut resources);
} }
}); });
} }
@ -1722,7 +1740,7 @@ mod tests {
let mut executor = Executor::new(systems); let mut executor = Executor::new(systems);
pool.install(|| { pool.install(|| {
for _ in 0..1000 { for _ in 0..1000 {
executor.execute(&mut world); executor.execute(&mut world, &mut Resources::default());
} }
}); });
} }

View file

@ -2,8 +2,8 @@
[![Build Status][build_img]][build_lnk] [![Crates.io][crates_img]][crates_lnk] [![Docs.rs][doc_img]][doc_lnk] [![Build Status][build_img]][build_lnk] [![Crates.io][crates_img]][crates_lnk] [![Docs.rs][doc_img]][doc_lnk]
[build_img]: https://img.shields.io/travis/TomGillen/legion/master.svg [build_img]: https://github.com/TomGillen/legion/workflows/CI/badge.svg
[build_lnk]: https://travis-ci.org/TomGillen/legion [build_lnk]: https://github.com/TomGillen/legion/actions
[crates_img]: https://img.shields.io/crates/v/legion.svg [crates_img]: https://img.shields.io/crates/v/legion.svg
[crates_lnk]: https://crates.io/crates/legion [crates_lnk]: https://crates.io/crates/legion
[doc_img]: https://docs.rs/legion/badge.svg [doc_img]: https://docs.rs/legion/badge.svg
@ -11,6 +11,14 @@
Legion aims to be a feature rich high performance ECS library for Rust game projects with minimal boilerplate. Legion aims to be a feature rich high performance ECS library for Rust game projects with minimal boilerplate.
## Bevy Fork Info
This is a fork that enables dynamic plugin loading in bevy.
Here are the changes made:
* ResourceTypeId, ComponentTypeId, TagTypeId use static str (std::any::type_name) instead of TypeId (std::any::TypeId is not constant across rust binaries)
* Implement "DowncastTypeName" to allow downcasting based on type name
## Benchmarks ## Benchmarks
Based on the [ecs_bench](https://github.com/lschmierer/ecs_bench) project. Based on the [ecs_bench](https://github.com/lschmierer/ecs_bench) project.

View file

@ -225,41 +225,12 @@
//! * `par-schedule`: Configures system schedulers to try and run systems in parallel where possible (enabled by default). //! * `par-schedule`: Configures system schedulers to try and run systems in parallel where possible (enabled by default).
//! * `log`: Configures `tracing` to redirect events to the `log` crate. This is a convenience feature for applications //! * `log`: Configures `tracing` to redirect events to the `log` crate. This is a convenience feature for applications
//! that use `log` and do not wish to interact with `tracing`. //! that use `log` and do not wish to interact with `tracing`.
//! * `events`: Enables eventing APIs on worlds (enabled by default).
#![allow(dead_code)] #![allow(dead_code)]
pub mod borrow; pub use legion_core::*;
pub mod command; pub use legion_systems as systems;
#[cfg(feature = "serde-1")]
pub mod de;
pub mod entity;
pub mod event;
pub mod filter;
pub mod iterator;
pub mod query;
pub mod resource;
pub mod schedule;
#[cfg(feature = "serde-1")]
pub mod ser;
pub mod storage;
pub mod system;
pub mod world;
mod cons;
mod tuple;
mod zip;
pub use bit_set;
pub mod prelude { pub mod prelude {
pub use crate::command::CommandBuffer; pub use legion_core::prelude::*;
pub use crate::entity::Entity; pub use legion_systems::prelude::*;
pub use crate::event::Event;
pub use crate::filter::filter_fns::*;
pub use crate::query::{IntoQuery, Query, Read, Tagged, TryRead, TryWrite, Write};
pub use crate::resource::{ResourceSet, Resources};
pub use crate::schedule::{Executor, Runnable, Schedulable, Schedule};
pub use crate::system::{System, SystemBuilder};
pub use crate::world::{Universe, World};
pub use bit_set::BitSet;
} }

View file

@ -64,7 +64,7 @@ fn query_try_read_entity_data() {
let query = TryRead::<Rot>::query(); let query = TryRead::<Rot>::query();
let rots = query let rots = query
.iter(&mut world) .iter(&world)
.map(|x| x.map(|x| *x)) .map(|x| x.map(|x| *x))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(rots.iter().filter(|x| x.is_none()).count(), 1); assert_eq!(rots.iter().filter(|x| x.is_none()).count(), 1);
@ -381,7 +381,7 @@ fn query_read_shared_data() {
let query = Tagged::<Static>::query(); let query = Tagged::<Static>::query();
let mut count = 0; let mut count = 0;
for marker in query.iter(&mut world) { for marker in query.iter(&world) {
assert_eq!(Static, *marker); assert_eq!(Static, *marker);
count += 1; count += 1;
} }
@ -587,3 +587,22 @@ fn query_iter_chunks_tag() {
} }
} }
} }
#[test]
fn query_iter_tag() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
let mut world = universe.create_world();
world.insert((Static, Model(0)), vec![(0u32,)]);
world.insert((Static, Model(1)), vec![(1u32,)]);
world.insert((Static, Model(2)), vec![(2u32,)]);
let query = <(Tagged<Static>, Tagged<Model>, Read<u32>)>::query();
for (s, m, c) in query.iter(&world) {
assert_eq!(&Static, s);
assert_eq!(&Model(*c), m);
}
}

View file

@ -1,4 +1,5 @@
use legion::prelude::*; use legion::prelude::*;
use std::collections::HashSet;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
struct Pos(f32, f32, f32); struct Pos(f32, f32, f32);
@ -85,7 +86,7 @@ fn get_shared() {
]; ];
let mut entities: Vec<Entity> = Vec::new(); let mut entities: Vec<Entity> = Vec::new();
for e in world.insert(shared, components.clone()) { for e in world.insert(shared, components) {
entities.push(*e); entities.push(*e);
} }
@ -121,7 +122,7 @@ fn delete() {
]; ];
let mut entities: Vec<Entity> = Vec::new(); let mut entities: Vec<Entity> = Vec::new();
for e in world.insert(shared, components.clone()) { for e in world.insert(shared, components) {
entities.push(*e); entities.push(*e);
} }
@ -135,6 +136,45 @@ fn delete() {
} }
} }
#[test]
fn delete_all() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
let mut world = universe.create_world();
let shared = (Static, Model(5));
let components = vec![
(Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3)),
(Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)),
];
let mut entities: Vec<Entity> = Vec::new();
for e in world.insert(shared, components) {
entities.push(*e);
}
// Check that the entity allocator knows about the entities
for e in entities.iter() {
assert_eq!(true, world.is_alive(*e));
}
// Check that the entities are in storage
let query = <(Read<Pos>, Read<Rot>)>::query();
assert_eq!(2, query.iter(&world).count());
world.delete_all();
// Check that the entity allocator no longer knows about the entities
for e in entities.iter() {
assert_eq!(false, world.is_alive(*e));
}
// Check that the entities are removed from storage
let query = <(Read<Pos>, Read<Rot>)>::query();
assert_eq!(0, query.iter(&world).count());
}
#[test] #[test]
fn delete_last() { fn delete_last() {
let _ = tracing_subscriber::fmt::try_init(); let _ = tracing_subscriber::fmt::try_init();
@ -260,15 +300,15 @@ fn mutate_add_component() {
let query_without_scale = <(Read<Pos>, Read<Rot>)>::query(); let query_without_scale = <(Read<Pos>, Read<Rot>)>::query();
let query_with_scale = <(Read<Pos>, Read<Rot>, Read<Scale>)>::query(); let query_with_scale = <(Read<Pos>, Read<Rot>, Read<Scale>)>::query();
assert_eq!(3, query_without_scale.iter(&mut world).count()); assert_eq!(3, query_without_scale.iter(&world).count());
assert_eq!(0, query_with_scale.iter(&mut world).count()); assert_eq!(0, query_with_scale.iter(&world).count());
world world
.add_component(*entities.get(1).unwrap(), Scale(0.5, 0.5, 0.5)) .add_component(*entities.get(1).unwrap(), Scale(0.5, 0.5, 0.5))
.unwrap(); .unwrap();
assert_eq!(3, query_without_scale.iter(&mut world).count()); assert_eq!(3, query_without_scale.iter(&world).count());
assert_eq!(1, query_with_scale.iter(&mut world).count()); assert_eq!(1, query_with_scale.iter(&world).count());
} }
#[test] #[test]
@ -290,13 +330,15 @@ fn mutate_remove_component() {
let query_without_rot = Read::<Pos>::query().filter(!component::<Rot>()); let query_without_rot = Read::<Pos>::query().filter(!component::<Rot>());
let query_with_rot = <(Read<Pos>, Read<Rot>)>::query(); let query_with_rot = <(Read<Pos>, Read<Rot>)>::query();
assert_eq!(0, query_without_rot.iter(&mut world).count()); assert_eq!(0, query_without_rot.iter(&world).count());
assert_eq!(3, query_with_rot.iter(&mut world).count()); assert_eq!(3, query_with_rot.iter(&world).count());
world.remove_component::<Rot>(*entities.get(1).unwrap()); world
.remove_component::<Rot>(*entities.get(1).unwrap())
.unwrap();
assert_eq!(1, query_without_rot.iter(&mut world).count()); assert_eq!(1, query_without_rot.iter(&world).count());
assert_eq!(2, query_with_rot.iter(&mut world).count()); assert_eq!(2, query_with_rot.iter(&world).count());
} }
#[test] #[test]
@ -318,13 +360,13 @@ fn mutate_add_tag() {
let query_without_static = <(Read<Pos>, Read<Rot>)>::query(); let query_without_static = <(Read<Pos>, Read<Rot>)>::query();
let query_with_static = <(Read<Pos>, Read<Rot>, Tagged<Static>)>::query(); let query_with_static = <(Read<Pos>, Read<Rot>, Tagged<Static>)>::query();
assert_eq!(3, query_without_static.iter(&mut world).count()); assert_eq!(3, query_without_static.iter(&world).count());
assert_eq!(0, query_with_static.iter(&mut world).count()); assert_eq!(0, query_with_static.iter(&world).count());
world.add_tag(*entities.get(1).unwrap(), Static); world.add_tag(*entities.get(1).unwrap(), Static).unwrap();
assert_eq!(3, query_without_static.iter(&mut world).count()); assert_eq!(3, query_without_static.iter(&world).count());
assert_eq!(1, query_with_static.iter(&mut world).count()); assert_eq!(1, query_with_static.iter(&world).count());
} }
#[test] #[test]
@ -346,13 +388,15 @@ fn mutate_remove_tag() {
let query_without_static = <(Read<Pos>, Read<Rot>)>::query().filter(!tag::<Static>()); let query_without_static = <(Read<Pos>, Read<Rot>)>::query().filter(!tag::<Static>());
let query_with_static = <(Read<Pos>, Read<Rot>, Tagged<Static>)>::query(); let query_with_static = <(Read<Pos>, Read<Rot>, Tagged<Static>)>::query();
assert_eq!(0, query_without_static.iter(&mut world).count()); assert_eq!(0, query_without_static.iter(&world).count());
assert_eq!(3, query_with_static.iter(&mut world).count()); assert_eq!(3, query_with_static.iter(&world).count());
world.remove_tag::<Static>(*entities.get(1).unwrap()); world
.remove_tag::<Static>(*entities.get(1).unwrap())
.unwrap();
assert_eq!(1, query_without_static.iter(&mut world).count()); assert_eq!(1, query_without_static.iter(&world).count());
assert_eq!(2, query_with_static.iter(&mut world).count()); assert_eq!(2, query_with_static.iter(&world).count());
} }
#[test] #[test]
@ -368,12 +412,48 @@ fn mutate_change_tag_minimum_test() {
let entities = world.insert(shared, components).to_vec(); let entities = world.insert(shared, components).to_vec();
tracing::trace!("STARTING CHANGE"); tracing::trace!("STARTING CHANGE");
world.add_tag(entities[0], Model(3)); world.add_tag(entities[0], Model(3)).unwrap();
tracing::trace!("CHANGED\n"); tracing::trace!("CHANGED\n");
assert_eq!(*world.get_tag::<Model>(entities[0]).unwrap(), Model(3)); assert_eq!(*world.get_tag::<Model>(entities[0]).unwrap(), Model(3));
} }
#[test]
fn delete_entities_on_drop() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
let mut world = universe.create_world();
let (tx, rx) = crossbeam_channel::unbounded::<legion::event::Event>();
let shared = (Model(5),);
let components = vec![(Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3))];
// Insert the data and store resulting entities in a HashSet
let mut entities = HashSet::new();
for entity in world.insert(shared, components) {
entities.insert(*entity);
}
world.subscribe(tx, legion::filter::filter_fns::any());
//ManuallyDrop::drop(&mut world);
std::mem::drop(world);
for e in rx.try_recv() {
match e {
legion::event::Event::EntityRemoved(entity, _chunk_id) => {
assert!(entities.remove(&entity));
}
_ => {}
}
}
// Verify that no extra entities are included
assert!(entities.is_empty());
}
#[test] #[test]
#[allow(clippy::suspicious_map)] #[allow(clippy::suspicious_map)]
fn mutate_change_tag() { fn mutate_change_tag() {
@ -394,11 +474,11 @@ fn mutate_change_tag() {
let query_model_3 = <(Read<Pos>, Read<Rot>)>::query().filter(tag_value(&Model(3))); let query_model_3 = <(Read<Pos>, Read<Rot>)>::query().filter(tag_value(&Model(3)));
let query_model_5 = <(Read<Pos>, Read<Rot>)>::query().filter(tag_value(&Model(5))); let query_model_5 = <(Read<Pos>, Read<Rot>)>::query().filter(tag_value(&Model(5)));
assert_eq!(3, query_model_5.iter(&mut world).count()); assert_eq!(3, query_model_5.iter(&world).count());
assert_eq!(0, query_model_3.iter(&mut world).count()); assert_eq!(0, query_model_3.iter(&world).count());
tracing::trace!("STARTING CHANGE"); tracing::trace!("STARTING CHANGE");
world.add_tag(*entities.get(1).unwrap(), Model(3)); world.add_tag(*entities.get(1).unwrap(), Model(3)).unwrap();
tracing::trace!("CHANGED\n"); tracing::trace!("CHANGED\n");
assert_eq!( assert_eq!(
@ -416,5 +496,55 @@ fn mutate_change_tag() {
Model(3) Model(3)
); );
assert_eq!(2, query_model_5.iter(&mut world).count()); assert_eq!(2, query_model_5.iter(&world).count());
}
// This test repeatedly creates a world with new entities and drops it, reproducing
// https://github.com/TomGillen/legion/issues/92
#[test]
fn lots_of_deletes() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
for _ in 0..10000 {
let shared = (Model(5),);
let components = vec![
(Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3)),
(Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)),
(Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)),
];
let mut world = universe.create_world();
world.insert(shared, components).to_vec();
}
}
#[test]
fn iter_entities() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
let mut world = universe.create_world();
let shared = (Model(5),);
let components = vec![
(Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3)),
(Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)),
(Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)),
];
// Insert the data and store resulting entities in a HashSet
let mut entities = HashSet::new();
for entity in world.insert(shared, components) {
entities.insert(*entity);
}
// Verify that all entities in iter_entities() are included
for entity in world.iter_entities() {
assert!(entities.remove(&entity));
}
// Verify that no extra entities are included
assert!(entities.is_empty());
} }

View file

@ -7,8 +7,7 @@ edition = "2018"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
# legion = { path = "../bevy_legion" } legion = { path = "../bevy_legion", features = ["serialize"]}
legion = { git = "https://github.com/TomGillen/legion", rev = "c5b9628630d4f9fc54b6843b5ce02d0669434a61", features = ["serialize"] }
glam = "0.8.3" glam = "0.8.3"
log = "0.4" log = "0.4"
rayon = "1.2" rayon = "1.2"

View file

@ -13,7 +13,7 @@ use serde::{
Deserialize, Deserializer, Serialize, Serializer, Deserialize, Deserializer, Serialize, Serializer,
}; };
use std::{ use std::{
any::TypeId, cell::RefCell, collections::HashMap, iter::FromIterator, marker::PhantomData, any::type_name, cell::RefCell, collections::HashMap, iter::FromIterator, marker::PhantomData,
ptr::NonNull, ptr::NonNull,
}; };
use type_uuid::TypeUuid; use type_uuid::TypeUuid;
@ -101,7 +101,7 @@ impl<'de, 'a, T: for<'b> Deserialize<'b> + 'static> Visitor<'de>
#[derive(Clone)] #[derive(Clone)]
pub struct TagRegistration { pub struct TagRegistration {
uuid: type_uuid::Bytes, uuid: type_uuid::Bytes,
ty: TypeId, ty: String,
tag_serialize_fn: fn(&TagStorage, &mut dyn FnMut(&dyn erased_serde::Serialize)), tag_serialize_fn: fn(&TagStorage, &mut dyn FnMut(&dyn erased_serde::Serialize)),
tag_deserialize_fn: fn( tag_deserialize_fn: fn(
deserializer: &mut dyn erased_serde::Deserializer, deserializer: &mut dyn erased_serde::Deserializer,
@ -123,7 +123,7 @@ impl TagRegistration {
>() -> Self { >() -> Self {
Self { Self {
uuid: T::UUID, uuid: T::UUID,
ty: TypeId::of::<T>(), ty: type_name::<T>().to_string(),
tag_serialize_fn: |tag_storage, serialize_fn| { tag_serialize_fn: |tag_storage, serialize_fn| {
// it's safe because we know this is the correct type due to lookup // it's safe because we know this is the correct type due to lookup
let slice = unsafe { tag_storage.data_slice::<T>() }; let slice = unsafe { tag_storage.data_slice::<T>() };
@ -150,7 +150,7 @@ impl TagRegistration {
#[derive(Clone)] #[derive(Clone)]
pub struct ComponentRegistration { pub struct ComponentRegistration {
uuid: type_uuid::Bytes, uuid: type_uuid::Bytes,
ty: TypeId, ty: String,
comp_serialize_fn: fn(&ComponentResourceSet, &mut dyn FnMut(&dyn erased_serde::Serialize)), comp_serialize_fn: fn(&ComponentResourceSet, &mut dyn FnMut(&dyn erased_serde::Serialize)),
comp_deserialize_fn: fn( comp_deserialize_fn: fn(
deserializer: &mut dyn erased_serde::Deserializer, deserializer: &mut dyn erased_serde::Deserializer,
@ -164,7 +164,7 @@ impl ComponentRegistration {
{ {
Self { Self {
uuid: T::UUID, uuid: T::UUID,
ty: TypeId::of::<T>(), ty: type_name::<T>().to_string(),
comp_serialize_fn: |comp_storage, serialize_fn| { comp_serialize_fn: |comp_storage, serialize_fn| {
// it's safe because we know this is the correct type due to lookup // it's safe because we know this is the correct type due to lookup
let slice = unsafe { comp_storage.data_slice::<T>() }; let slice = unsafe { comp_storage.data_slice::<T>() };
@ -192,8 +192,8 @@ struct SerializedArchetypeDescription {
} }
pub struct SerializeImpl { pub struct SerializeImpl {
pub tag_types: HashMap<TypeId, TagRegistration>, pub tag_types: HashMap<String, TagRegistration>,
pub comp_types: HashMap<TypeId, ComponentRegistration>, pub comp_types: HashMap<String, ComponentRegistration>,
pub entity_map: RefCell<HashMap<Entity, uuid::Bytes>>, pub entity_map: RefCell<HashMap<Entity, uuid::Bytes>>,
} }
@ -242,10 +242,10 @@ impl SerializeImpl {
impl legion::serialize::ser::WorldSerializer for SerializeImpl { impl legion::serialize::ser::WorldSerializer for SerializeImpl {
fn can_serialize_tag(&self, ty: &TagTypeId, _meta: &TagMeta) -> bool { fn can_serialize_tag(&self, ty: &TagTypeId, _meta: &TagMeta) -> bool {
self.tag_types.get(&ty.0).is_some() self.tag_types.get(ty.0).is_some()
} }
fn can_serialize_component(&self, ty: &ComponentTypeId, _meta: &ComponentMeta) -> bool { fn can_serialize_component(&self, ty: &ComponentTypeId, _meta: &ComponentMeta) -> bool {
self.comp_types.get(&ty.0).is_some() self.comp_types.get(ty.0).is_some()
} }
fn serialize_archetype_description<S: Serializer>( fn serialize_archetype_description<S: Serializer>(
&self, &self,
@ -255,13 +255,13 @@ impl legion::serialize::ser::WorldSerializer for SerializeImpl {
let tags_to_serialize = archetype_desc let tags_to_serialize = archetype_desc
.tags() .tags()
.iter() .iter()
.filter_map(|(ty, _)| self.tag_types.get(&ty.0)) .filter_map(|(ty, _)| self.tag_types.get(ty.0))
.map(|reg| reg.uuid) .map(|reg| reg.uuid)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let components_to_serialize = archetype_desc let components_to_serialize = archetype_desc
.components() .components()
.iter() .iter()
.filter_map(|(ty, _)| self.comp_types.get(&ty.0)) .filter_map(|(ty, _)| self.comp_types.get(ty.0))
.map(|reg| reg.uuid) .map(|reg| reg.uuid)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
SerializedArchetypeDescription { SerializedArchetypeDescription {
@ -277,7 +277,7 @@ impl legion::serialize::ser::WorldSerializer for SerializeImpl {
_component_meta: &ComponentMeta, _component_meta: &ComponentMeta,
components: &ComponentResourceSet, components: &ComponentResourceSet,
) -> Result<S::Ok, S::Error> { ) -> Result<S::Ok, S::Error> {
if let Some(reg) = self.comp_types.get(&component_type.0) { if let Some(reg) = self.comp_types.get(component_type.0) {
let result = RefCell::new(None); let result = RefCell::new(None);
let serializer = RefCell::new(Some(serializer)); let serializer = RefCell::new(Some(serializer));
{ {
@ -303,7 +303,7 @@ impl legion::serialize::ser::WorldSerializer for SerializeImpl {
_tag_meta: &TagMeta, _tag_meta: &TagMeta,
tags: &TagStorage, tags: &TagStorage,
) -> Result<S::Ok, S::Error> { ) -> Result<S::Ok, S::Error> {
if let Some(reg) = self.tag_types.get(&tag_type.0) { if let Some(reg) = self.tag_types.get(tag_type.0) {
let result = RefCell::new(None); let result = RefCell::new(None);
let serializer = RefCell::new(Some(serializer)); let serializer = RefCell::new(Some(serializer));
{ {
@ -337,8 +337,8 @@ impl legion::serialize::ser::WorldSerializer for SerializeImpl {
} }
pub struct DeserializeImpl { pub struct DeserializeImpl {
pub tag_types: HashMap<TypeId, TagRegistration>, pub tag_types: HashMap<String, TagRegistration>,
pub comp_types: HashMap<TypeId, ComponentRegistration>, pub comp_types: HashMap<String, ComponentRegistration>,
pub tag_types_by_uuid: HashMap<type_uuid::Bytes, TagRegistration>, pub tag_types_by_uuid: HashMap<type_uuid::Bytes, TagRegistration>,
pub comp_types_by_uuid: HashMap<type_uuid::Bytes, ComponentRegistration>, pub comp_types_by_uuid: HashMap<type_uuid::Bytes, ComponentRegistration>,
pub entity_map: RefCell<HashMap<uuid::Bytes, Entity>>, pub entity_map: RefCell<HashMap<uuid::Bytes, Entity>>,
@ -346,8 +346,8 @@ pub struct DeserializeImpl {
impl DeserializeImpl { impl DeserializeImpl {
pub fn new( pub fn new(
component_types: HashMap<TypeId, ComponentRegistration>, component_types: HashMap<String, ComponentRegistration>,
tag_types: HashMap<TypeId, TagRegistration>, tag_types: HashMap<String, TagRegistration>,
entity_map: RefCell<HashMap<Entity, uuid::Bytes>>, entity_map: RefCell<HashMap<Entity, uuid::Bytes>>,
) -> Self { ) -> Self {
DeserializeImpl { DeserializeImpl {
@ -399,7 +399,7 @@ impl legion::serialize::de::WorldDeserializer for DeserializeImpl {
_component_meta: &ComponentMeta, _component_meta: &ComponentMeta,
get_next_storage_fn: &mut dyn FnMut() -> Option<(NonNull<u8>, usize)>, get_next_storage_fn: &mut dyn FnMut() -> Option<(NonNull<u8>, usize)>,
) -> Result<(), <D as Deserializer<'de>>::Error> { ) -> Result<(), <D as Deserializer<'de>>::Error> {
if let Some(reg) = self.comp_types.get(&component_type.0) { if let Some(reg) = self.comp_types.get(component_type.0) {
let mut erased = erased_serde::Deserializer::erase(deserializer); let mut erased = erased_serde::Deserializer::erase(deserializer);
(reg.comp_deserialize_fn)(&mut erased, get_next_storage_fn) (reg.comp_deserialize_fn)(&mut erased, get_next_storage_fn)
.map_err(<<D as serde::Deserializer<'de>>::Error as serde::de::Error>::custom)?; .map_err(<<D as serde::Deserializer<'de>>::Error as serde::de::Error>::custom)?;
@ -415,7 +415,7 @@ impl legion::serialize::de::WorldDeserializer for DeserializeImpl {
_tag_meta: &TagMeta, _tag_meta: &TagMeta,
tags: &mut TagStorage, tags: &mut TagStorage,
) -> Result<(), <D as Deserializer<'de>>::Error> { ) -> Result<(), <D as Deserializer<'de>>::Error> {
if let Some(reg) = self.tag_types.get(&tag_type.0) { if let Some(reg) = self.tag_types.get(tag_type.0) {
let mut erased = erased_serde::Deserializer::erase(deserializer); let mut erased = erased_serde::Deserializer::erase(deserializer);
(reg.tag_deserialize_fn)(&mut erased, tags) (reg.tag_deserialize_fn)(&mut erased, tags)
.map_err(<<D as serde::Deserializer<'de>>::Error as serde::de::Error>::custom)?; .map_err(<<D as serde::Deserializer<'de>>::Error as serde::de::Error>::custom)?;