From 07a44c042371dad96a8d0bd0b9feddcd6a436174 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Fri, 23 Oct 2020 22:07:09 -0500 Subject: [PATCH] Implement Dynamic Systems and Components --- Cargo.toml | 15 + crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/hecs/Cargo.toml | 2 + crates/bevy_ecs/hecs/macros/src/lib.rs | 16 +- crates/bevy_ecs/hecs/src/access.rs | 69 +-- crates/bevy_ecs/hecs/src/archetype.rs | 44 +- crates/bevy_ecs/hecs/src/entity_builder.rs | 85 +++- crates/bevy_ecs/hecs/src/lib.rs | 6 +- crates/bevy_ecs/hecs/src/query.rs | 399 +++++++++++++----- crates/bevy_ecs/hecs/src/world.rs | 267 ++++++++++-- .../bevy_ecs/src/resource/resource_query.rs | 16 +- crates/bevy_ecs/src/system/into_system.rs | 24 +- crates/bevy_ecs/src/system/query/mod.rs | 226 ++++++++-- crates/bevy_ecs/src/system/system.rs | 148 ++++++- crates/bevy_render/src/draw.rs | 6 +- .../src/render_graph/nodes/pass_node.rs | 4 +- crates/bevy_scene/Cargo.toml | 3 + crates/bevy_scene/src/dynamic_scene.rs | 23 +- crates/bevy_scene/src/scene_spawner.rs | 23 +- examples/ecs/dynamic_components.rs | 160 +++++++ examples/ecs/dynamic_systems.rs | 150 +++++++ 21 files changed, 1402 insertions(+), 285 deletions(-) create mode 100644 examples/ecs/dynamic_components.rs create mode 100644 examples/ecs/dynamic_systems.rs diff --git a/Cargo.toml b/Cargo.toml index 8d18c2969c90af..7c6fdfa60ff367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,9 @@ render = ["bevy_pbr", "bevy_render", "bevy_sprite", "bevy_text", "bevy_ui"] png = ["bevy_render/png"] hdr = ["bevy_render/hdr"] +# Enable the dynamic systems and components API ( useful for developing scripting solutions ) +dynamic-api = ["bevy_ecs/dynamic-api", "bevy_scene/dynamic-api"] + # Audio format support (MP3 is enabled by default) mp3 = ["bevy_audio/mp3"] flac = ["bevy_audio/flac"] @@ -90,6 +93,8 @@ serde = { version = "1", features = ["derive"] } log = "0.4" ron = "0.6" anyhow = "1.0" +lazy_static = "1.4.0" + # bevy (Android) [target.'cfg(target_os = "android")'.dependencies] @@ -216,6 +221,16 @@ path = "examples/ecs/parallel_query.rs" name = "hierarchy" path = "examples/ecs/hierarchy.rs" +[[example]] +name = "dynamic_systems" +path = "examples/ecs/dynamic_systems.rs" +required-features = ["dynamic-api"] + +[[example]] +name = "dynamic_components" +path = "examples/ecs/dynamic_components.rs" +required-features = ["dynamic-api"] + [[example]] name = "breakout" path = "examples/game/breakout.rs" diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index a5efe3af53909f..d0bafe89a18ba8 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -15,6 +15,7 @@ categories = ["game-engines", "data-structures"] [features] profiler = [] +dynamic-api = ["bevy_hecs/dynamic-api"] [dependencies] bevy_hecs = { path = "hecs", features = ["macros", "serialize"], version = "0.3.0" } diff --git a/crates/bevy_ecs/hecs/Cargo.toml b/crates/bevy_ecs/hecs/Cargo.toml index 5c39508749d3a7..426bb8e4e127a8 100644 --- a/crates/bevy_ecs/hecs/Cargo.toml +++ b/crates/bevy_ecs/hecs/Cargo.toml @@ -25,6 +25,8 @@ std = [] # Enables derive(Bundle) macros = ["bevy_hecs_macros", "lazy_static"] serialize = ["serde"] +# Enables the dynamic components and systems APIs +dynamic-api = ["std"] [dependencies] bevy_hecs_macros = { path = "macros", version = "0.3.0", optional = true } diff --git a/crates/bevy_ecs/hecs/macros/src/lib.rs b/crates/bevy_ecs/hecs/macros/src/lib.rs index 1223214e5268c8..3b4c298df10a3a 100644 --- a/crates/bevy_ecs/hecs/macros/src/lib.rs +++ b/crates/bevy_ecs/hecs/macros/src/lib.rs @@ -186,7 +186,12 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream { let query_fn = &query_fns[0..query_count]; let query_fn_mut = &query_fn_muts[0..query_count]; tokens.extend(TokenStream::from(quote! { - impl<#(#lifetime,)* #(#query: HecsQuery,)*> QueryTuple for (#(Query<#lifetime, #query>,)*) { + impl<#(#lifetime,)* #(#query: HecsQuery,)*> QueryTuple for (#(Query<#lifetime, #query>,)*) + where + #( + #query::Fetch: for<'a> Fetch<'a, State = ()> + ),* + { unsafe fn new(world: &World, component_access: &TypeAccess) -> Self { ( #( @@ -200,12 +205,17 @@ pub fn impl_query_set(_input: TokenStream) -> TokenStream { fn get_accesses() -> Vec { vec![ - #(<#query::Fetch as Fetch>::access(),)* + #(<#query::Fetch as Fetch>::access(&()),)* ] } } - impl<#(#lifetime,)* #(#query: HecsQuery,)*> QuerySet<(#(Query<#lifetime, #query>,)*)> { + impl<#(#lifetime,)* #(#query: HecsQuery,)*> QuerySet<(#(Query<#lifetime, #query>,)*)> + where + #( + #query::Fetch: for<'a> Fetch<'a, State = ()> + ),* + { #(#query_fn)* #(#query_fn_mut)* } diff --git a/crates/bevy_ecs/hecs/src/access.rs b/crates/bevy_ecs/hecs/src/access.rs index 7d3dd1e5385ffd..16aa4d73a32797 100644 --- a/crates/bevy_ecs/hecs/src/access.rs +++ b/crates/bevy_ecs/hecs/src/access.rs @@ -1,7 +1,7 @@ use core::{any::TypeId, hash::Hash}; use std::{boxed::Box, vec::Vec}; -use crate::{Archetype, World}; +use crate::{Archetype, ComponentId, World}; use bevy_utils::HashSet; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] @@ -14,7 +14,7 @@ pub enum Access { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct ArchetypeComponent { pub archetype_index: u32, - pub component: TypeId, + pub component: ComponentId, } impl ArchetypeComponent { @@ -22,12 +22,12 @@ impl ArchetypeComponent { pub fn new(archetype_index: u32) -> Self { ArchetypeComponent { archetype_index, - component: TypeId::of::(), + component: TypeId::of::().into(), } } #[inline] - pub fn new_ty(archetype_index: u32, component: TypeId) -> Self { + pub fn new_component(archetype_index: u32, component: ComponentId) -> Self { ArchetypeComponent { archetype_index, component, @@ -35,31 +35,32 @@ impl ArchetypeComponent { } } +#[derive(Clone, Debug)] pub enum QueryAccess { None, - Read(TypeId, &'static str), - Write(TypeId, &'static str), + Read(ComponentId, &'static str), + Write(ComponentId, &'static str), Optional(Box), - With(TypeId, Box), - Without(TypeId, Box), + With(ComponentId, Box), + Without(ComponentId, Box), Union(Vec), } impl QueryAccess { pub fn read() -> QueryAccess { - QueryAccess::Read(TypeId::of::(), std::any::type_name::()) + QueryAccess::Read(TypeId::of::().into(), std::any::type_name::()) } pub fn write() -> QueryAccess { - QueryAccess::Write(TypeId::of::(), std::any::type_name::()) + QueryAccess::Write(TypeId::of::().into(), std::any::type_name::()) } pub fn with(access: QueryAccess) -> QueryAccess { - QueryAccess::With(TypeId::of::(), Box::new(access)) + QueryAccess::With(TypeId::of::().into(), Box::new(access)) } pub fn without(access: QueryAccess) -> QueryAccess { - QueryAccess::Without(TypeId::of::(), Box::new(access)) + QueryAccess::Without(TypeId::of::().into(), Box::new(access)) } pub fn optional(access: QueryAccess) -> QueryAccess { @@ -82,29 +83,29 @@ impl QueryAccess { } } - pub fn get_type_name(&self, type_id: TypeId) -> Option<&'static str> { + pub fn get_type_name(&self, component_id: ComponentId) -> Option<&'static str> { match self { QueryAccess::None => None, - QueryAccess::Read(current_type_id, name) => { - if type_id == *current_type_id { + QueryAccess::Read(current_component_id, name) => { + if component_id == *current_component_id { Some(*name) } else { None } } - QueryAccess::Write(current_type_id, name) => { - if type_id == *current_type_id { + QueryAccess::Write(current_component_id, name) => { + if component_id == *current_component_id { Some(*name) } else { None } } - QueryAccess::Optional(query_access) => query_access.get_type_name(type_id), - QueryAccess::With(_, query_access) => query_access.get_type_name(type_id), - QueryAccess::Without(_, query_access) => query_access.get_type_name(type_id), + QueryAccess::Optional(query_access) => query_access.get_type_name(component_id), + QueryAccess::With(_, query_access) => query_access.get_type_name(component_id), + QueryAccess::Without(_, query_access) => query_access.get_type_name(component_id), QueryAccess::Union(query_accesses) => { for query_access in query_accesses.iter() { - if let Some(name) = query_access.get_type_name(type_id) { + if let Some(name) = query_access.get_type_name(component_id) { return Some(name); } } @@ -125,9 +126,10 @@ impl QueryAccess { match self { QueryAccess::None => Some(Access::None), QueryAccess::Read(ty, _) => { - if archetype.has_type(*ty) { + if archetype.has_component(*ty) { if let Some(type_access) = type_access { - type_access.add_read(ArchetypeComponent::new_ty(archetype_index, *ty)); + type_access + .add_read(ArchetypeComponent::new_component(archetype_index, *ty)); } Some(Access::Read) } else { @@ -135,9 +137,10 @@ impl QueryAccess { } } QueryAccess::Write(ty, _) => { - if archetype.has_type(*ty) { + if archetype.has_component(*ty) { if let Some(type_access) = type_access { - type_access.add_write(ArchetypeComponent::new_ty(archetype_index, *ty)); + type_access + .add_write(ArchetypeComponent::new_component(archetype_index, *ty)); } Some(Access::Write) } else { @@ -157,14 +160,14 @@ impl QueryAccess { } } QueryAccess::With(ty, query_access) => { - if archetype.has_type(*ty) { + if archetype.has_component(*ty) { query_access.get_access(archetype, archetype_index, type_access) } else { None } } QueryAccess::Without(ty, query_access) => { - if !archetype.has_type(*ty) { + if !archetype.has_component(*ty) { query_access.get_access(archetype, archetype_index, type_access) } else { None @@ -308,7 +311,7 @@ mod tests { let e3_c = ArchetypeComponent::new::(e3_archetype); let mut a_type_access = TypeAccess::default(); - <(&A,) as Query>::Fetch::access() + <(&A,) as Query>::Fetch::access(&()) .get_world_archetype_access(&world, Some(&mut a_type_access)); assert_eq!( @@ -317,7 +320,7 @@ mod tests { ); let mut a_b_type_access = TypeAccess::default(); - <(&A, &B) as Query>::Fetch::access() + <(&A, &B) as Query>::Fetch::access(&()) .get_world_archetype_access(&world, Some(&mut a_b_type_access)); assert_eq!( @@ -326,7 +329,7 @@ mod tests { ); let mut a_bmut_type_access = TypeAccess::default(); - <(&A, &mut B) as Query>::Fetch::access() + <(&A, &mut B) as Query>::Fetch::access(&()) .get_world_archetype_access(&world, Some(&mut a_bmut_type_access)); assert_eq!( @@ -335,7 +338,7 @@ mod tests { ); let mut a_option_bmut_type_access = TypeAccess::default(); - <(Entity, &A, Option<&mut B>) as Query>::Fetch::access() + <(Entity, &A, Option<&mut B>) as Query>::Fetch::access(&()) .get_world_archetype_access(&world, Some(&mut a_option_bmut_type_access)); assert_eq!( @@ -344,7 +347,7 @@ mod tests { ); let mut a_with_b_type_access = TypeAccess::default(); - as Query>::Fetch::access() + as Query>::Fetch::access(&()) .get_world_archetype_access(&world, Some(&mut a_with_b_type_access)); assert_eq!( @@ -353,7 +356,7 @@ mod tests { ); let mut a_with_b_option_c_type_access = TypeAccess::default(); - )> as Query>::Fetch::access() + )> as Query>::Fetch::access(&()) .get_world_archetype_access(&world, Some(&mut a_with_b_option_c_type_access)); assert_eq!( diff --git a/crates/bevy_ecs/hecs/src/archetype.rs b/crates/bevy_ecs/hecs/src/archetype.rs index 8bcb14eb5ca20c..28e75d8322d773 100644 --- a/crates/bevy_ecs/hecs/src/archetype.rs +++ b/crates/bevy_ecs/hecs/src/archetype.rs @@ -26,7 +26,7 @@ use bevy_utils::AHasher; use core::{ any::TypeId, cell::UnsafeCell, - hash::{BuildHasherDefault, Hasher}, + hash::{BuildHasherDefault, Hash, Hasher}, mem, ptr::{self, NonNull}, }; @@ -243,7 +243,8 @@ impl Archetype { size: usize, index: usize, ) -> Option> { - debug_assert!(index < self.len); + // TODO(zicklag): I'm pretty sure that it is valid for the index to be zero + debug_assert!(index < self.len || index == 0); Some(NonNull::new_unchecked( (*self.data.get()) .as_ptr() @@ -500,11 +501,14 @@ impl TypeState { } /// Metadata required to store a component -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct TypeInfo { - id: ComponentId, - layout: Layout, - drop: unsafe fn(*mut u8), + /// The ID unique to the component type + pub(crate) id: ComponentId, + /// The memory layout of the component + pub(crate) layout: Layout, + /// The drop function for the component + pub(crate) drop: unsafe fn(*mut u8), } impl TypeInfo { @@ -521,6 +525,16 @@ impl TypeInfo { } } + /// Get the [`TypeInfo`] for an external type with the given layout and drop function + #[cfg(feature = "dynamic-api")] + pub fn of_external(external_id: u64, layout: Layout, drop: unsafe fn(*mut u8)) -> Self { + TypeInfo { + id: ComponentId::ExternalId(external_id), + layout, + drop, + } + } + #[allow(missing_docs)] #[inline] pub fn id(&self) -> ComponentId { @@ -538,6 +552,16 @@ impl TypeInfo { } } +#[allow(clippy::derive_hash_xor_eq)] +impl Hash for TypeInfo { + fn hash(&self, state: &mut H) { + self.id.hash(state); + state.write_usize(self.layout.size()); + state.write_usize(self.layout.align()); + self.drop.hash(state); + } +} + impl PartialOrd for TypeInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -555,14 +579,6 @@ impl Ord for TypeInfo { } } -impl PartialEq for TypeInfo { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for TypeInfo {} - fn align(x: usize, alignment: usize) -> usize { debug_assert!(alignment.is_power_of_two()); (x + alignment - 1) & (!alignment + 1) diff --git a/crates/bevy_ecs/hecs/src/entity_builder.rs b/crates/bevy_ecs/hecs/src/entity_builder.rs index 28b24cd0ab4980..0eacc2a7c58e3b 100644 --- a/crates/bevy_ecs/hecs/src/entity_builder.rs +++ b/crates/bevy_ecs/hecs/src/entity_builder.rs @@ -14,6 +14,8 @@ // modified by Bevy contributors +use bevy_utils::HashSet; + use crate::{ alloc::{ alloc::{alloc, dealloc, Layout}, @@ -24,12 +26,8 @@ use crate::{ world::ComponentId, }; -use bevy_utils::HashSet; -use core::{ - any::TypeId, - mem::{self, MaybeUninit}, - ptr, -}; +use core::{intrinsics::copy_nonoverlapping, mem::MaybeUninit, ptr}; +use ptr::slice_from_raw_parts; use crate::{archetype::TypeInfo, Component, DynamicBundle}; @@ -68,24 +66,59 @@ impl EntityBuilder { /// Add `component` to the entity pub fn add(&mut self, component: T) -> &mut Self { - if !self.id_set.insert(TypeId::of::().into()) { + self.add_with_typeinfo( + TypeInfo::of::(), + unsafe { + &*slice_from_raw_parts( + &component as *const T as *const u8, + std::mem::size_of::(), + ) + }, + true, + ); + std::mem::forget(component); + self + } + + /// Add a dynamic component given the component ID, the layout and the raw data slice + #[cfg(feature = "dynamic-api")] + pub fn add_dynamic(&mut self, info: TypeInfo, data: &[u8]) -> &mut Self { + self.add_with_typeinfo(info, data, false); + self + } + + fn add_with_typeinfo( + &mut self, + type_info: TypeInfo, + data: &[u8], + skip_size_check: bool, + ) -> &mut Self { + if !skip_size_check { + assert_eq!( + type_info.layout.size(), + data.len(), + "Data length does not match component size" + ); + } + + if !self.id_set.insert(type_info.id()) { return self; } - let end = self.cursor + mem::size_of::(); + let end = self.cursor + type_info.layout().size(); if end > self.storage.len() { self.grow(end); } - if mem::size_of::() != 0 { + if type_info.layout().size() != 0 { unsafe { - self.storage - .as_mut_ptr() - .add(self.cursor) - .cast::() - .write_unaligned(component); + copy_nonoverlapping( + data.as_ptr(), + self.storage.as_mut_ptr().add(self.cursor) as *mut u8, + data.len(), + ); } } - self.info.push((TypeInfo::of::(), self.cursor)); - self.cursor += mem::size_of::(); + self.info.push((type_info, self.cursor)); + self.cursor += type_info.layout().size(); self } @@ -195,3 +228,23 @@ impl Drop for BuiltEntity<'_> { self.builder.clear(); } } + +#[cfg(test)] +mod test { + #[cfg(feature = "dynamic-api")] + use super::*; + + #[cfg(feature = "dynamic-api")] + #[test] + #[should_panic(expected = "Data length does not match component size")] + fn dynamic_data_invalid_length_panics() { + const ID1: u64 = 242237625853274575; + let layout1 = Layout::from_size_align(2, 1).unwrap(); + + let mut builder = EntityBuilder::new(); + + // This should panic because we said above that the component size was 2, and we are trying + // to stick 3 bytes into it. + builder.add_dynamic(TypeInfo::of_external(ID1, layout1, |_| ()), &[1, 2, 3]); + } +} diff --git a/crates/bevy_ecs/hecs/src/lib.rs b/crates/bevy_ecs/hecs/src/lib.rs index 9d0799b77d96b6..b378792658a92e 100644 --- a/crates/bevy_ecs/hecs/src/lib.rs +++ b/crates/bevy_ecs/hecs/src/lib.rs @@ -74,15 +74,15 @@ mod query; mod serde; mod world; -pub use access::{ArchetypeComponent, QueryAccess, TypeAccess}; +pub use access::{Access, ArchetypeComponent, QueryAccess, TypeAccess}; pub use archetype::{Archetype, TypeState}; pub use borrow::{AtomicBorrow, Ref, RefMut}; pub use bundle::{Bundle, DynamicBundle, MissingComponent}; pub use entities::{Entity, EntityReserver, Location, NoSuchEntity}; pub use entity_builder::{BuiltEntity, EntityBuilder}; pub use query::{ - Added, Batch, BatchedIter, Changed, Mut, Mutated, Or, Query, QueryIter, ReadOnlyFetch, With, - Without, + Added, Batch, BatchedIter, Changed, DynamicFetch, DynamicFetchResult, DynamicQuery, Mut, + Mutated, Or, Query, QueryIter, ReadOnlyFetch, With, Without, }; pub use world::{ ArchetypesGeneration, Component, ComponentError, ComponentId, SpawnBatchIter, World, diff --git a/crates/bevy_ecs/hecs/src/query.rs b/crates/bevy_ecs/hecs/src/query.rs index b192a1974d2c83..bbbf6011cb7ae0 100644 --- a/crates/bevy_ecs/hecs/src/query.rs +++ b/crates/bevy_ecs/hecs/src/query.rs @@ -17,12 +17,16 @@ use core::{ marker::PhantomData, ops::{Deref, DerefMut}, - ptr::NonNull, + ptr::{slice_from_raw_parts, slice_from_raw_parts_mut, NonNull}, }; - use std::vec; -use crate::{access::QueryAccess, archetype::Archetype, Component, Entity, MissingComponent}; +use bevy_utils::HashMap; + +use crate::{ + access::QueryAccess, archetype::Archetype, ArchetypeComponent, Component, ComponentId, Entity, + MissingComponent, TypeAccess, TypeInfo, +}; /// A collection of component types to fetch from a `World` pub trait Query { @@ -41,24 +45,27 @@ pub trait Fetch<'a>: Sized { /// Type of value to be fetched type Item; + /// The query state + type State; + /// A value on which `get` may never be called #[allow(clippy::declare_interior_mutable_const)] // no const fn in traits const DANGLING: Self; /// How this query will access `archetype`, if at all - fn access() -> QueryAccess; + fn access(state: &Self::State) -> QueryAccess; /// Construct a `Fetch` for `archetype` if it should be traversed /// /// # Safety /// `offset` must be in bounds of `archetype` - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option; + unsafe fn get(state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option; /// if this returns true, the nth item should be skipped during iteration /// /// # Safety /// shouldn't be called if there is no current item - unsafe fn should_skip(&self, _n: usize) -> bool { + unsafe fn should_skip(&self, _state: &Self::State, _n: usize) -> bool { false } @@ -69,7 +76,137 @@ pub trait Fetch<'a>: Sized { /// - `release` must not be called while `'a` is still live /// - Bounds-checking must be performed externally /// - Any resulting borrows must be legal (e.g. no &mut to something another iterator might access) - unsafe fn fetch(&self, n: usize) -> Self::Item; + unsafe fn fetch(&self, state: &Self::State, n: usize) -> Self::Item; +} + +#[derive(Debug, Clone)] +pub struct DynamicQuery { + pub access: QueryAccess, + pub component_info: HashMap, +} + +impl DynamicQuery { + pub fn register_info(&mut self, info: TypeInfo) -> ComponentId { + let id = info.id; + self.component_info.insert(id, info); + + id + } +} + +impl Default for DynamicQuery { + fn default() -> Self { + DynamicQuery { + access: QueryAccess::None, + component_info: Default::default(), + } + } +} + +impl Query for DynamicQuery { + type Fetch = DynamicFetch; +} + +/// The result of a dynamic component query, containing references to the component's bytes +#[derive(Debug)] +pub struct DynamicFetchResult<'w> { + pub entity: Entity, + /// the list of immutable components returned + pub immutable: HashMap, + /// the list of mutable components returned + pub mutable: HashMap, +} + +/// A [`Fetch`] implementation for dynamic queries +#[derive(Debug, Clone)] +pub struct DynamicFetch { + // Optional only so that we have a way to specify `DANGLING` as a const + access: Option, TypeInfo)>>, + entity: NonNull, +} + +impl<'w> Fetch<'w> for DynamicFetch { + type Item = DynamicFetchResult<'w>; + type State = DynamicQuery; + + const DANGLING: Self = Self { + access: None, + entity: NonNull::dangling(), + }; + + fn access(query: &Self::State) -> QueryAccess { + query.access.clone() + } + + unsafe fn get(query: &Self::State, archetype: &'w Archetype, offset: usize) -> Option { + let mut access = TypeAccess::default(); + let map_access = |access: &ArchetypeComponent| { + let info = query + .component_info + .get(&access.component) + .expect("Missing component info for component in query"); + + ( + archetype + .get_dynamic(info.id, info.layout.size(), offset) + .expect("Archetype missing component"), + *info, + ) + }; + + query + .access + .get_access( + archetype, + 0, // TODO(zicklag): Is this fine as zero? I think it is unused in this context. + Some(&mut access), + ) + // Create a `DynamicFetch` by mapping the component query to the `TypeInfo` + .map(|_| DynamicFetch { + access: Some(TypeAccess::new( + access.iter_reads().map(map_access).collect(), + access.iter_writes().map(map_access).collect(), + )), + entity: archetype.entities(), + }) + } + + unsafe fn fetch(&self, _query: &Self::State, n: usize) -> Self::Item { + let mut immutable = HashMap::default(); + let mut mutable = HashMap::default(); + let access = self + .access + .as_ref() + .expect("Attempted to fetch from dangling `DynamicFetch`"); + + // Collect immutable components + for (nonnull, info) in access.iter_reads() { + immutable.insert( + info.id, + &*slice_from_raw_parts( + nonnull.as_ptr().add(n * info.layout.size()), + info.layout.size(), + ), + ); + } + + // Collect mutable components + for (nonnull, info) in access.iter_writes() { + mutable.insert( + info.id, + &mut *slice_from_raw_parts_mut( + nonnull.as_ptr().add(n * info.layout.size()), + info.layout.size(), + ), + ); + } + + DynamicFetchResult { + entity: *self.entity.as_ptr().add(n), + immutable, + mutable, + } + } } #[derive(Copy, Clone, Debug)] @@ -83,23 +220,24 @@ impl Query for Entity { impl<'a> Fetch<'a> for EntityFetch { type Item = Entity; + type State = (); const DANGLING: Self = Self(NonNull::dangling()); #[inline] - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { Some(EntityFetch(NonNull::new_unchecked( archetype.entities().as_ptr().add(offset), ))) } #[inline] - unsafe fn fetch(&self, n: usize) -> Self::Item { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Self::Item { *self.0.as_ptr().add(n) } #[inline] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::None } } @@ -116,22 +254,23 @@ impl UnfilteredFetch for FetchRead {} impl<'a, T: Component> Fetch<'a> for FetchRead { type Item = &'a T; + type State = (); const DANGLING: Self = Self(NonNull::dangling()); - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { archetype .get::() .map(|x| Self(NonNull::new_unchecked(x.as_ptr().add(offset)))) } #[inline] - unsafe fn fetch(&self, n: usize) -> &'a T { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> &'a T { &*self.0.as_ptr().add(n) } #[inline] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::read::() } } @@ -140,7 +279,10 @@ impl<'a, T: Component> Query for &'a mut T { type Fetch = FetchMut; } -impl Query for Option { +impl Query for Option +where + T::Fetch: for<'a> Fetch<'a, State = ()>, +{ type Fetch = TryFetch; } @@ -201,10 +343,11 @@ impl UnfilteredFetch for FetchMut {} impl<'a, T: Component> Fetch<'a> for FetchMut { type Item = Mut<'a, T>; + type State = (); const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling()); - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { archetype .get_with_type_state::() .map(|(components, type_state)| { @@ -216,7 +359,7 @@ impl<'a, T: Component> Fetch<'a> for FetchMut { } #[inline] - unsafe fn fetch(&self, n: usize) -> Mut<'a, T> { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Mut<'a, T> { Mut { value: &mut *self.0.as_ptr().add(n), mutated: &mut *self.1.as_ptr().add(n), @@ -224,43 +367,47 @@ impl<'a, T: Component> Fetch<'a> for FetchMut { } #[inline] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::write::() } } macro_rules! impl_or_query { ( $( $T:ident ),+ ) => { - impl<$( $T: Query ),+> Query for Or<($( $T ),+)> { + impl<$( $T: Query ),+> Query for Or<($( $T ),+)> + where + $($T::Fetch: for<'a> Fetch<'a, State = ()>),+ + { type Fetch = FetchOr<($( $T::Fetch ),+)>; } - impl<'a, $( $T: Fetch<'a> ),+> Fetch<'a> for FetchOr<($( $T ),+)> { + impl<'a, $( $T: Fetch<'a, State=()> ),+> Fetch<'a> for FetchOr<($( $T ),+)> { type Item = ($( $T::Item ),+); + type State = (); const DANGLING: Self = Self(($( $T::DANGLING ),+)); - fn access() -> QueryAccess { + fn access(_state: &Self::State, ) -> QueryAccess { QueryAccess::union(vec![ - $($T::access(),)+ + $($T::access(&()),)+ ]) } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { - Some(Self(( $( $T::get(archetype, offset)?),+ ))) + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { + Some(Self(( $( $T::get(&(), archetype, offset)?),+ ))) } #[allow(non_snake_case)] - unsafe fn fetch(&self, n: usize) -> Self::Item { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Self::Item { let ($( $T ),+) = &self.0; - ($( $T.fetch(n) ),+) + ($( $T.fetch(&(), n) ),+) } #[allow(non_snake_case)] - unsafe fn should_skip(&self, n: usize) -> bool { + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { let ($( $T ),+) = &self.0; - true $( && $T.should_skip(n) )+ + true $( && $T.should_skip(&(), n) )+ } } @@ -327,15 +474,16 @@ unsafe impl ReadOnlyFetch for FetchMutated {} impl<'a, T: Component> Fetch<'a> for FetchMutated { type Item = Mutated<'a, T>; + type State = (); const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling()); #[inline] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::read::() } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { archetype .get_with_type_state::() .map(|(components, type_state)| { @@ -346,13 +494,13 @@ impl<'a, T: Component> Fetch<'a> for FetchMutated { }) } - unsafe fn should_skip(&self, n: usize) -> bool { + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { // skip if the current item wasn't mutated !*self.1.as_ptr().add(n) } #[inline] - unsafe fn fetch(&self, n: usize) -> Self::Item { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Self::Item { Mutated { value: &*self.0.as_ptr().add(n), } @@ -383,15 +531,16 @@ unsafe impl ReadOnlyFetch for FetchAdded {} impl<'a, T: Component> Fetch<'a> for FetchAdded { type Item = Added<'a, T>; + type State = (); const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling()); #[inline] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::read::() } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { archetype .get_with_type_state::() .map(|(components, type_state)| { @@ -402,13 +551,13 @@ impl<'a, T: Component> Fetch<'a> for FetchAdded { }) } - unsafe fn should_skip(&self, n: usize) -> bool { + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { // skip if the current item wasn't added !*self.1.as_ptr().add(n) } #[inline] - unsafe fn fetch(&self, n: usize) -> Self::Item { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Self::Item { Added { value: &*self.0.as_ptr().add(n), } @@ -439,6 +588,7 @@ unsafe impl ReadOnlyFetch for FetchChanged {} impl<'a, T: Component> Fetch<'a> for FetchChanged { type Item = Changed<'a, T>; + type State = (); const DANGLING: Self = Self( NonNull::dangling(), @@ -447,11 +597,11 @@ impl<'a, T: Component> Fetch<'a> for FetchChanged { ); #[inline] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::read::() } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { archetype .get_with_type_state::() .map(|(components, type_state)| { @@ -463,13 +613,13 @@ impl<'a, T: Component> Fetch<'a> for FetchChanged { }) } - unsafe fn should_skip(&self, n: usize) -> bool { + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { // skip if the current item wasn't added or mutated !*self.1.as_ptr().add(n) && !*self.2.as_ptr().add(n) } #[inline] - unsafe fn fetch(&self, n: usize) -> Self::Item { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Self::Item { Changed { value: &*self.0.as_ptr().add(n), } @@ -481,26 +631,29 @@ pub struct TryFetch(Option); unsafe impl ReadOnlyFetch for TryFetch where T: ReadOnlyFetch {} impl UnfilteredFetch for TryFetch where T: UnfilteredFetch {} -impl<'a, T: Fetch<'a>> Fetch<'a> for TryFetch { +impl<'a, T: Fetch<'a, State = ()>> Fetch<'a> for TryFetch { type Item = Option; + type State = (); const DANGLING: Self = Self(None); #[inline] - fn access() -> QueryAccess { - QueryAccess::optional(T::access()) + fn access(_state: &Self::State) -> QueryAccess { + QueryAccess::optional(T::access(&())) } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { - Some(Self(T::get(archetype, offset))) + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { + Some(Self(T::get(&(), archetype, offset))) } - unsafe fn fetch(&self, n: usize) -> Option { - Some(self.0.as_ref()?.fetch(n)) + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Option { + Some(self.0.as_ref()?.fetch(&(), n)) } - unsafe fn should_skip(&self, n: usize) -> bool { - self.0.as_ref().map_or(false, |fetch| fetch.should_skip(n)) + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { + self.0 + .as_ref() + .map_or(false, |fetch| fetch.should_skip(&(), n)) } } @@ -522,41 +675,45 @@ impl<'a, T: Fetch<'a>> Fetch<'a> for TryFetch { /// ``` pub struct Without(PhantomData<(Q, fn(T))>); -impl Query for Without { +impl Query for Without +where + Q::Fetch: for<'a> Fetch<'a, State = ()>, +{ type Fetch = FetchWithout; } #[doc(hidden)] pub struct FetchWithout(F, PhantomData); -unsafe impl<'a, T: Component, F: Fetch<'a>> ReadOnlyFetch for FetchWithout where +unsafe impl<'a, T: Component, F: Fetch<'a, State = ()>> ReadOnlyFetch for FetchWithout where F: ReadOnlyFetch { } impl UnfilteredFetch for FetchWithout where F: UnfilteredFetch {} -impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWithout { +impl<'a, T: Component, F: Fetch<'a, State = ()>> Fetch<'a> for FetchWithout { type Item = F::Item; + type State = (); const DANGLING: Self = Self(F::DANGLING, PhantomData); #[inline] - fn access() -> QueryAccess { - QueryAccess::without::(F::access()) + fn access(_state: &Self::State) -> QueryAccess { + QueryAccess::without::(F::access(&())) } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { if archetype.has::() { return None; } - Some(Self(F::get(archetype, offset)?, PhantomData)) + Some(Self(F::get(&(), archetype, offset)?, PhantomData)) } - unsafe fn fetch(&self, n: usize) -> F::Item { - self.0.fetch(n) + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> F::Item { + self.0.fetch(&(), n) } - unsafe fn should_skip(&self, n: usize) -> bool { - self.0.should_skip(n) + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { + self.0.should_skip(&(), n) } } @@ -580,7 +737,10 @@ impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWithout { /// ``` pub struct With(PhantomData<(Q, fn(T))>); -impl Query for With { +impl Query for With +where + Q::Fetch: for<'a> Fetch<'a, State = ()>, +{ type Fetch = FetchWith; } @@ -589,29 +749,30 @@ pub struct FetchWith(F, PhantomData); unsafe impl<'a, T: Component, F: Fetch<'a>> ReadOnlyFetch for FetchWith where F: ReadOnlyFetch {} impl UnfilteredFetch for FetchWith where F: UnfilteredFetch {} -impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWith { +impl<'a, T: Component, F: Fetch<'a, State = ()>> Fetch<'a> for FetchWith { type Item = F::Item; + type State = (); const DANGLING: Self = Self(F::DANGLING, PhantomData); #[inline] - fn access() -> QueryAccess { - QueryAccess::with::(F::access()) + fn access(_state: &Self::State) -> QueryAccess { + QueryAccess::with::(F::access(&())) } - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { if !archetype.has::() { return None; } - Some(Self(F::get(archetype, offset)?, PhantomData)) + Some(Self(F::get(&(), archetype, offset)?, PhantomData)) } - unsafe fn fetch(&self, n: usize) -> F::Item { - self.0.fetch(n) + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> F::Item { + self.0.fetch(&(), n) } - unsafe fn should_skip(&self, n: usize) -> bool { - self.0.should_skip(n) + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { + self.0.should_skip(&(), n) } } @@ -621,14 +782,15 @@ struct ChunkInfo { } /// Iterator over the set of entities with the components in `Q` -pub struct QueryIter<'w, Q: Query> { +pub struct QueryIter<'s, 'w, Q: Query, S> { archetypes: &'w [Archetype], archetype_index: usize, chunk_info: ChunkInfo, chunk_position: usize, + state: &'s S, } -impl<'w, Q: Query> QueryIter<'w, Q> { +impl<'s, 'w, Q: Query, S> QueryIter<'s, 'w, Q, S> { // #[allow(clippy::declare_interior_mutable_const)] // no trait bounds on const fns // const EMPTY: Q::Fetch = Q::Fetch::DANGLING; const EMPTY: ChunkInfo = ChunkInfo { @@ -638,17 +800,21 @@ impl<'w, Q: Query> QueryIter<'w, Q> { /// Creates a new QueryIter #[inline] - pub(crate) fn new(archetypes: &'w [Archetype]) -> Self { + pub(crate) fn new(archetypes: &'w [Archetype], state: &'s S) -> Self { Self { archetypes, archetype_index: 0, chunk_info: Self::EMPTY, chunk_position: 0, + state, } } } -impl<'w, Q: Query> Iterator for QueryIter<'w, Q> { +impl<'s, 'w, Q: Query, S> Iterator for QueryIter<'s, 'w, Q, S> +where + Q::Fetch: for<'a> Fetch<'a, State = S>, +{ type Item = >::Item; #[inline] @@ -659,7 +825,7 @@ impl<'w, Q: Query> Iterator for QueryIter<'w, Q> { let archetype = self.archetypes.get(self.archetype_index)?; self.archetype_index += 1; self.chunk_position = 0; - self.chunk_info = Q::Fetch::get(archetype, 0) + self.chunk_info = Q::Fetch::get(self.state, archetype, 0) .map(|fetch| ChunkInfo { fetch, len: archetype.len(), @@ -671,13 +837,17 @@ impl<'w, Q: Query> Iterator for QueryIter<'w, Q> { if self .chunk_info .fetch - .should_skip(self.chunk_position as usize) + .should_skip(self.state, self.chunk_position as usize) { self.chunk_position += 1; continue; } - let item = Some(self.chunk_info.fetch.fetch(self.chunk_position as usize)); + let item = Some( + self.chunk_info + .fetch + .fetch(self.state, self.chunk_position as usize), + ); self.chunk_position += 1; return item; } @@ -687,38 +857,42 @@ impl<'w, Q: Query> Iterator for QueryIter<'w, Q> { // if the Fetch is an UnfilteredFetch, then we can cheaply compute the length of the query by getting // the length of each matching archetype -impl<'w, Q: Query> ExactSizeIterator for QueryIter<'w, Q> +impl<'s, 'w, Q: Query, S> ExactSizeIterator for QueryIter<'s, 'w, Q, S> where - Q::Fetch: UnfilteredFetch, + Q::Fetch: UnfilteredFetch + for<'a> Fetch<'a, State = S>, { fn len(&self) -> usize { self.archetypes .iter() - .filter(|&archetype| unsafe { Q::Fetch::get(archetype, 0).is_some() }) + .filter(|&archetype| unsafe { Q::Fetch::get(self.state, archetype, 0).is_some() }) .map(|x| x.len()) .sum() } } -struct ChunkIter { +struct ChunkIter<'s, Q: Query, S> { fetch: Q::Fetch, position: usize, len: usize, + state: &'s S, } -impl ChunkIter { +impl<'s, Q: Query, S> ChunkIter<'s, Q, S> +where + Q::Fetch: for<'a> Fetch<'a, State = S>, +{ unsafe fn next<'a>(&mut self) -> Option<>::Item> { loop { if self.position == self.len { return None; } - if self.fetch.should_skip(self.position as usize) { + if self.fetch.should_skip(self.state, self.position as usize) { self.position += 1; continue; } - let item = Some(self.fetch.fetch(self.position as usize)); + let item = Some(self.fetch.fetch(self.state, self.position as usize)); self.position += 1; return item; } @@ -726,31 +900,36 @@ impl ChunkIter { } /// Batched version of `QueryIter` -pub struct BatchedIter<'w, Q: Query> { +pub struct BatchedIter<'s, 'w, Q: Query, S> { archetypes: &'w [Archetype], archetype_index: usize, batch_size: usize, batch: usize, + state: &'s S, _marker: PhantomData, } -impl<'w, Q: Query> BatchedIter<'w, Q> { - pub(crate) fn new(archetypes: &'w [Archetype], batch_size: usize) -> Self { +impl<'s, 'w, Q: Query, S> BatchedIter<'s, 'w, Q, S> { + pub(crate) fn new(archetypes: &'w [Archetype], batch_size: usize, state: &'s S) -> Self { Self { archetypes, archetype_index: 0, batch_size, batch: 0, + state, _marker: Default::default(), } } } -unsafe impl<'w, Q: Query> Send for BatchedIter<'w, Q> {} -unsafe impl<'w, Q: Query> Sync for BatchedIter<'w, Q> {} +unsafe impl<'s, 'w, Q: Query, S> Send for BatchedIter<'s, 'w, Q, S> {} +unsafe impl<'s, 'w, Q: Query, S> Sync for BatchedIter<'s, 'w, Q, S> {} -impl<'w, Q: Query> Iterator for BatchedIter<'w, Q> { - type Item = Batch<'w, Q>; +impl<'s, 'w, Q: Query, S: 's> Iterator for BatchedIter<'s, 'w, Q, S> +where + Q::Fetch: for<'a> Fetch<'a, State = S>, +{ + type Item = Batch<'s, 'w, Q, S>; fn next(&mut self) -> Option { loop { @@ -761,7 +940,7 @@ impl<'w, Q: Query> Iterator for BatchedIter<'w, Q> { self.batch = 0; continue; } - if let Some(fetch) = unsafe { Q::Fetch::get(archetype, offset) } { + if let Some(fetch) = unsafe { Q::Fetch::get(self.state, archetype, offset) } { self.batch += 1; return Some(Batch { _marker: PhantomData, @@ -769,6 +948,7 @@ impl<'w, Q: Query> Iterator for BatchedIter<'w, Q> { fetch, position: 0, len: self.batch_size.min(archetype.len() - offset), + state: self.state, }, }); } else { @@ -784,12 +964,15 @@ impl<'w, Q: Query> Iterator for BatchedIter<'w, Q> { } /// A sequence of entities yielded by `BatchedIter` -pub struct Batch<'q, Q: Query> { +pub struct Batch<'s, 'q, Q: Query, S> { _marker: PhantomData<&'q ()>, - state: ChunkIter, + state: ChunkIter<'s, Q, S>, } -impl<'q, 'w, Q: Query> Iterator for Batch<'q, Q> { +impl<'s, 'q, 'w, Q: Query, S> Iterator for Batch<'s, 'q, Q, S> +where + Q::Fetch: for<'a> Fetch<'a, State = S>, +{ type Item = >::Item; fn next(&mut self) -> Option { @@ -798,43 +981,47 @@ impl<'q, 'w, Q: Query> Iterator for Batch<'q, Q> { } } -unsafe impl<'q, Q: Query> Send for Batch<'q, Q> {} -unsafe impl<'q, Q: Query> Sync for Batch<'q, Q> {} +unsafe impl<'s, 'q, Q: Query, S> Send for Batch<'s, 'q, Q, S> {} +unsafe impl<'s, 'q, Q: Query, S> Sync for Batch<'q, 's, Q, S> {} macro_rules! tuple_impl { ($($name: ident),*) => { - impl<'a, $($name: Fetch<'a>),*> Fetch<'a> for ($($name,)*) { + impl<'a, $($name: Fetch<'a, State = ()>),*> Fetch<'a> for ($($name,)*) { type Item = ($($name::Item,)*); + type State = (); const DANGLING: Self = ($($name::DANGLING,)*); #[allow(unused_variables, unused_mut)] - fn access() -> QueryAccess { + fn access(_state: &Self::State) -> QueryAccess { QueryAccess::union(vec![ - $($name::access(),)* + $($name::access(&()),)* ]) } #[allow(unused_variables)] - unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { - Some(($($name::get(archetype, offset)?,)*)) + unsafe fn get(_state: &Self::State, archetype: &'a Archetype, offset: usize) -> Option { + Some(($($name::get(&(), archetype, offset)?,)*)) } #[allow(unused_variables)] - unsafe fn fetch(&self, n: usize) -> Self::Item { + unsafe fn fetch(&self, _state: &Self::State, n: usize) -> Self::Item { #[allow(non_snake_case)] let ($($name,)*) = self; - ($($name.fetch(n),)*) + ($($name.fetch(&(), n),)*) } #[allow(unused_variables)] - unsafe fn should_skip(&self, n: usize) -> bool { + unsafe fn should_skip(&self, _state: &Self::State, n: usize) -> bool { #[allow(non_snake_case)] let ($($name,)*) = self; - $($name.should_skip(n)||)* false + $($name.should_skip(&(), n)||)* false } } - impl<$($name: Query),*> Query for ($($name,)*) { + impl<$($name: Query),*> Query for ($($name,)*) + where + $($name::Fetch: for<'a> Fetch<'a, State = ()>),* + { type Fetch = ($($name::Fetch,)*); } diff --git a/crates/bevy_ecs/hecs/src/world.rs b/crates/bevy_ecs/hecs/src/world.rs index a495cee32a8b24..a9f0017fcc6345 100644 --- a/crates/bevy_ecs/hecs/src/world.rs +++ b/crates/bevy_ecs/hecs/src/world.rs @@ -15,17 +15,11 @@ // modified by Bevy contributors use crate::{ - alloc::vec::Vec, borrow::EntityRef, query::ReadOnlyFetch, BatchedIter, EntityReserver, Fetch, - Mut, QueryIter, RefMut, + alloc::vec::Vec, archetype::ComponentIdMap, borrow::EntityRef, query::ReadOnlyFetch, + BatchedIter, EntityReserver, Fetch, Mut, QueryIter, RefMut, TypeInfo, }; use bevy_utils::{HashMap, HashSet}; -use core::{ - any::TypeId, - cmp::{Ord, Ordering}, - fmt, - hash::{Hash, Hasher}, - mem, ptr, -}; +use core::{any::TypeId, cmp::Ord, fmt, hash::Hash, mem, ptr}; #[cfg(feature = "std")] use std::error::Error; @@ -47,10 +41,11 @@ use crate::{ pub struct World { entities: Entities, index: HashMap, u32>, - removed_components: HashMap>, + removed_components: ComponentIdMap>, #[allow(missing_docs)] pub archetypes: Vec, archetype_generation: u64, + dynamic_component_info: HashMap, } impl World { @@ -66,7 +61,8 @@ impl World { index, archetypes, archetype_generation: 0, - removed_components: HashMap::default(), + removed_components: ComponentIdMap::default(), + dynamic_component_info: HashMap::default(), } } @@ -255,12 +251,20 @@ impl World { /// assert!(entities.contains(&(a, 123, true))); /// assert!(entities.contains(&(b, 456, false))); /// ``` - pub fn query(&self) -> QueryIter<'_, Q> + #[inline] + pub fn query(&self) -> QueryIter<'static, '_, Q, ()> + where + Q::Fetch: ReadOnlyFetch, + { + self.query_stateful(&()) + } + + pub fn query_stateful<'s, Q: Query, S>(&self, state: &'s S) -> QueryIter<'s, '_, Q, S> where Q::Fetch: ReadOnlyFetch, { // SAFE: read-only access to world and read only query prevents mutable access - unsafe { self.query_unchecked() } + unsafe { self.query_unchecked_stateful(state) } } /// Efficiently iterate over all entities that have certain components @@ -287,26 +291,61 @@ impl World { /// assert!(entities.contains(&(a, 123, true))); /// assert!(entities.contains(&(b, 456, false))); /// ``` - pub fn query_mut(&mut self) -> QueryIter<'_, Q> { + #[inline] + pub fn query_mut(&mut self) -> QueryIter<'static, '_, Q, ()> { + self.query_mut_stateful(&()) + } + + /// See [`query_mut`], except that this allows you to pass in query state if the query supports + /// it. + pub fn query_mut_stateful<'s, Q: Query, S>(&mut self, state: &'s S) -> QueryIter<'s, '_, Q, S> { // SAFE: unique mutable access - unsafe { self.query_unchecked() } + unsafe { self.query_unchecked_stateful(state) } } /// Like `query`, but instead of returning a single iterator it returns a "batched iterator", /// where each batch is `batch_size`. This is generally used for parallel iteration. - pub fn query_batched(&self, batch_size: usize) -> BatchedIter<'_, Q> + #[inline] + pub fn query_batched(&self, batch_size: usize) -> BatchedIter<'static, '_, Q, ()> + where + Q::Fetch: ReadOnlyFetch, + { + self.query_batched_stateful(batch_size, &()) + } + + /// See [`query_batched`], except that this allows you to pass in query state if the query + /// supports it. + pub fn query_batched_stateful<'s, Q: Query, S>( + &self, + batch_size: usize, + state: &'s S, + ) -> BatchedIter<'s, '_, Q, S> where Q::Fetch: ReadOnlyFetch, { // SAFE: read-only access to world and read only query prevents mutable access - unsafe { self.query_batched_unchecked(batch_size) } + unsafe { self.query_batched_unchecked_stateful(batch_size, state) } } /// Like `query`, but instead of returning a single iterator it returns a "batched iterator", /// where each batch is `batch_size`. This is generally used for parallel iteration. - pub fn query_batched_mut(&mut self, batch_size: usize) -> BatchedIter<'_, Q> { + #[inline] + pub fn query_batched_mut( + &mut self, + batch_size: usize, + ) -> BatchedIter<'static, '_, Q, ()> { + self.query_batched_mut_stateful(batch_size, &()) + } + + /// See [`query_batched_mut`], except that this allows you to pass in query + /// state if the query supports it. + pub fn query_batched_mut_stateful<'s, Q: Query, S>( + &mut self, + batch_size: usize, + state: &'s S, + ) -> BatchedIter<'s, '_, Q, S> { // SAFE: unique mutable access - unsafe { self.query_batched_unchecked(batch_size) } + unsafe { self.query_batched_unchecked_stateful(batch_size, state) } } /// Efficiently iterate over all entities that have certain components @@ -322,8 +361,22 @@ impl World { /// # Safety /// This does not check for mutable query correctness. To be safe, make sure mutable queries /// have unique access to the components they query. - pub unsafe fn query_unchecked(&self) -> QueryIter<'_, Q> { - QueryIter::new(&self.archetypes) + #[inline] + pub unsafe fn query_unchecked(&self) -> QueryIter<'static, '_, Q, ()> { + self.query_unchecked_stateful(&()) + } + + /// See [`query_unchecked`], except that this allows you to pass in query state if the query + /// supports it. + /// + /// # Safety + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + pub unsafe fn query_unchecked_stateful<'s, Q: Query, S>( + &self, + state: &'s S, + ) -> QueryIter<'s, '_, Q, S> { + QueryIter::new(&self.archetypes, state) } /// Like `query`, but instead of returning a single iterator it returns a "batched iterator", @@ -336,8 +389,23 @@ impl World { pub unsafe fn query_batched_unchecked( &self, batch_size: usize, - ) -> BatchedIter<'_, Q> { - BatchedIter::new(&self.archetypes, batch_size) + ) -> BatchedIter<'static, '_, Q, ()> { + self.query_batched_unchecked_stateful(batch_size, &()) + } + + /// See [`query_batched_unchecked`], except that this allows you to pass in query state if the + /// query supports it. + /// + /// # Safety + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + #[inline] + pub unsafe fn query_batched_unchecked_stateful<'s, Q: Query, S>( + &self, + batch_size: usize, + state: &'s S, + ) -> BatchedIter<'s, '_, Q, S> { + BatchedIter::new(&self.archetypes, batch_size, state) } /// Prepare a read only query against a single entity @@ -353,15 +421,29 @@ impl World { /// let (number, flag) = world.query_one::<(&i32, &bool)>(a).unwrap(); /// assert_eq!(*number, 123); /// ``` + #[inline] pub fn query_one( &self, entity: Entity, ) -> Result<::Item, NoSuchEntity> where - Q::Fetch: ReadOnlyFetch, + Q::Fetch: ReadOnlyFetch + for<'a> Fetch<'a, State = ()>, + { + self.query_one_stateful::(entity, &()) + } + + /// See [`query_one_mut`], except that this allows you to pass in query state if the query + /// supports it. + pub fn query_one_stateful<'s, Q: Query, S>( + &self, + entity: Entity, + state: &'s S, + ) -> Result<::Item, NoSuchEntity> + where + Q::Fetch: ReadOnlyFetch + for<'a> Fetch<'a, State = S>, { // SAFE: read-only access to world and read only query prevents mutable access - unsafe { self.query_one_unchecked::(entity) } + unsafe { self.query_one_unchecked_stateful::(entity, state) } } /// Prepare a query against a single entity @@ -378,12 +460,30 @@ impl World { /// if *flag { *number *= 2; } /// assert_eq!(*number, 246); /// ``` + #[inline] pub fn query_one_mut( &mut self, entity: Entity, - ) -> Result<::Item, NoSuchEntity> { + ) -> Result<::Item, NoSuchEntity> + where + Q::Fetch: for<'a> Fetch<'a, State = ()>, + { + self.query_one_mut_stateful::(entity, &()) + } + + /// See [`query_one_mut`], except that this allows you to pass in query state if the query + /// supports it. + #[inline] + pub fn query_one_mut_stateful<'s, Q: Query, S>( + &mut self, + entity: Entity, + state: &'s S, + ) -> Result<::Item, NoSuchEntity> + where + Q::Fetch: for<'a> Fetch<'a, State = S>, + { // SAFE: unique mutable access to world - unsafe { self.query_one_unchecked::(entity) } + unsafe { self.query_one_unchecked_stateful::(entity, state) } } /// Prepare a query against a single entity, without checking the safety of mutable queries @@ -393,14 +493,35 @@ impl World { /// # Safety /// This does not check for mutable query correctness. To be safe, make sure mutable queries /// have unique access to the components they query. + #[inline] pub unsafe fn query_one_unchecked( &self, entity: Entity, - ) -> Result<::Item, NoSuchEntity> { + ) -> Result<::Item, NoSuchEntity> + where + Q::Fetch: for<'a> Fetch<'a, State = ()>, + { + self.query_one_unchecked_stateful::(entity, &()) + } + + /// See [`query_one_unchecked`], except that this allows you to pass in query state if the query + /// supports it. + /// + /// # Safety + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + pub unsafe fn query_one_unchecked_stateful<'s, Q: Query, S>( + &self, + entity: Entity, + state: &'s S, + ) -> Result<::Item, NoSuchEntity> + where + Q::Fetch: for<'a> Fetch<'a, State = S>, + { let loc = self.entities.get(entity)?; - ::get(&self.archetypes[loc.archetype as usize], 0) - .filter(|fetch| !fetch.should_skip(loc.index)) - .map(|fetch| fetch.fetch(loc.index)) + ::get(state, &self.archetypes[loc.archetype as usize], 0) + .filter(|fetch| !fetch.should_skip(state, loc.index)) + .map(|fetch| fetch.fetch(state, loc.index)) .ok_or(NoSuchEntity) } @@ -516,6 +637,25 @@ impl World { let arch = &mut self.archetypes[loc.archetype as usize]; let mut info = arch.types().to_vec(); for ty in components.type_info() { + #[cfg(feature = "dynamic-api")] + // If this is a dynamic component + if let ComponentId::ExternalId(id) = ty.id() { + // If we've previously registered a component under this ID + if let Some(registered) = self.dynamic_component_info.get(&id) { + // Verify the type info is consistent with the information previously associated + // to the type id. + assert_eq!( + &ty, registered, + "Attempted to insert dynamic component with a different layout than \ + previously inserted component with the same type ID." + ); + // If we've never added this component before + } else { + // Register the type with its info + self.dynamic_component_info.insert(id, ty); + } + } + if let Some(ptr) = arch.get_dynamic(ty.id(), ty.layout().size(), loc.index) { ty.drop(ptr.as_ptr()); } else { @@ -898,8 +1038,12 @@ impl From for ComponentError { } } +#[cfg(feature = "dynamic-api")] +use std::{cmp::Ordering, hash::Hasher}; + /// Uniquely identifies a type of component. This is conceptually similar to /// Rust's [`TypeId`], but allows for external type IDs to be defined. +#[cfg(feature = "dynamic-api")] #[derive(Eq, PartialEq, Debug, Clone, Copy)] pub enum ComponentId { /// A Rust-native [`TypeId`] @@ -909,6 +1053,7 @@ pub enum ComponentId { ExternalId(u64), } +#[cfg(feature = "dynamic-api")] #[allow(clippy::derive_hash_xor_eq)] // Fine because we uphold k1 == k2 ⇒ hash(k1) == hash(k2) impl Hash for ComponentId { fn hash(&self, state: &mut H) { @@ -923,6 +1068,7 @@ impl Hash for ComponentId { } } +#[cfg(feature = "dynamic-api")] impl Ord for ComponentId { fn cmp(&self, other: &Self) -> Ordering { if self == other { @@ -944,18 +1090,34 @@ impl Ord for ComponentId { } } +#[cfg(feature = "dynamic-api")] impl PartialOrd for ComponentId { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } +#[cfg(feature = "dynamic-api")] impl From for ComponentId { fn from(item: TypeId) -> Self { ComponentId::RustTypeId(item) } } +/// A component identifier +/// +/// Without the `dynamic-api` feature enabled, this is just a newtype around a Rust [`TypeId`]. +#[cfg(not(feature = "dynamic-api"))] +#[derive(Eq, PartialEq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub struct ComponentId(pub TypeId); + +#[cfg(not(feature = "dynamic-api"))] +impl From for ComponentId { + fn from(item: TypeId) -> Self { + ComponentId(item) + } +} + /// Types that can be components, implemented automatically for all `Send + Sync + 'static` types /// /// This is just a convenient shorthand for `Send + Sync + 'static`, and never needs to be @@ -1097,3 +1259,46 @@ where self.inner.len() } } + +#[cfg(test)] +mod test { + #[test] + #[should_panic(expected = "Attempted to insert dynamic component with a different layout")] + #[cfg(feature = "dynamic-api")] + fn inconsistent_dynamic_component_info_panics() { + use super::*; + use crate::EntityBuilder; + use core::alloc::Layout; + + let mut world = World::new(); + + let mut builder = EntityBuilder::new(); + // Create an entity with a dynamic component with an id of 1 and size of 2 + let bundle1 = builder + .add_dynamic( + TypeInfo::of_external(1, Layout::from_size_align(2, 1).unwrap(), |_| ()), + &[1, 2], + ) + .build(); + let mut builder = EntityBuilder::new(); + + // Insert the entity + let entity = world.reserve_entity(); + world.insert(entity, bundle1).unwrap(); + + // Create an entity with a dynamic component with an id of 1 and size of 3. This is an + // error because the component cannot have the same id of a previously inserted component + // and also have a different layout. + let bundle2 = builder + .add_dynamic( + TypeInfo::of_external(1, Layout::from_size_align(3, 1).unwrap(), |_| ()), + &[1, 2, 3], + ) + .build(); + + // Insert the entity + let entity = world.reserve_entity(); + // This should panic + world.insert(entity, bundle2).unwrap(); + } +} diff --git a/crates/bevy_ecs/src/resource/resource_query.rs b/crates/bevy_ecs/src/resource/resource_query.rs index 254277bf629334..c71d141e82017f 100644 --- a/crates/bevy_ecs/src/resource/resource_query.rs +++ b/crates/bevy_ecs/src/resource/resource_query.rs @@ -1,6 +1,6 @@ use super::{FromResources, Resources}; use crate::{system::SystemId, Resource, ResourceIndex}; -use bevy_hecs::{smaller_tuples_too, TypeAccess}; +use bevy_hecs::{smaller_tuples_too, ComponentId, TypeAccess}; use core::{ ops::{Deref, DerefMut}, ptr::NonNull, @@ -181,7 +181,7 @@ pub trait FetchResource<'a>: Sized { /// Type of value to be fetched type Item: UnsafeClone; - fn access() -> TypeAccess; + fn access() -> TypeAccess; fn borrow(resources: &Resources); fn release(resources: &Resources); @@ -217,7 +217,7 @@ impl<'a, T: Resource> FetchResource<'a> for FetchResourceRead { resources.release::(); } - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); access.add_read(TypeId::of::().into()); access @@ -252,7 +252,7 @@ impl<'a, T: Resource> FetchResource<'a> for FetchResourceChanged { resources.release::(); } - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); access.add_read(TypeId::of::().into()); access @@ -284,7 +284,7 @@ impl<'a, T: Resource> FetchResource<'a> for FetchResourceWrite { resources.release_mut::(); } - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); access.add_write(TypeId::of::().into()); access @@ -330,7 +330,7 @@ impl<'a, T: Resource + FromResources> FetchResource<'a> for FetchResourceLocalMu resources.release_mut::(); } - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); access.add_write(TypeId::of::().into()); access @@ -363,7 +363,7 @@ macro_rules! tuple_impl { } #[allow(unused_mut)] - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); $(access.union(&$name::access());)* access @@ -424,7 +424,7 @@ macro_rules! tuple_impl_or { } #[allow(unused_mut)] - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); $(access.union(&$name::access());)* access diff --git a/crates/bevy_ecs/src/system/into_system.rs b/crates/bevy_ecs/src/system/into_system.rs index 502913a6979072..d731eaa4a867ca 100644 --- a/crates/bevy_ecs/src/system/into_system.rs +++ b/crates/bevy_ecs/src/system/into_system.rs @@ -2,10 +2,10 @@ pub use super::Query; use crate::{ resource::{FetchResource, ResourceQuery, Resources, UnsafeClone}, system::{Commands, System, SystemId, ThreadLocalExecution}, - QueryAccess, QuerySet, QueryTuple, TypeAccess, + QuerySet, QueryTuple, TypeAccess, }; -use bevy_hecs::{ArchetypeComponent, Fetch, Query as HecsQuery, World}; -use std::{any::TypeId, borrow::Cow}; +use bevy_hecs::{ArchetypeComponent, ComponentId, Fetch, Query as HecsQuery, QueryAccess, World}; +use std::borrow::Cow; #[derive(Debug)] pub(crate) struct SystemFn @@ -21,7 +21,7 @@ where pub thread_local_func: ThreadLocalF, pub init_func: Init, pub thread_local_execution: ThreadLocalExecution, - pub resource_access: TypeAccess, + pub resource_access: TypeAccess, pub name: Cow<'static, str>, pub id: SystemId, pub archetype_component_access: TypeAccess, @@ -48,7 +48,7 @@ where &self.archetype_component_access } - fn resource_access(&self) -> &TypeAccess { + fn resource_access(&self) -> &TypeAccess { &self.resource_access } @@ -95,7 +95,10 @@ macro_rules! impl_into_foreach_system { $(<<$resource as ResourceQuery>::Fetch as FetchResource>::Item,)* $(<<$component as HecsQuery>::Fetch as Fetch>::Item,)*)+ Send + Sync + 'static, - $($component: HecsQuery,)* + $( + $component: HecsQuery, + $component::Fetch: for<'a> Fetch<'a, State = ()>, + )* $($resource: ResourceQuery,)* { #[allow(non_snake_case)] @@ -106,7 +109,7 @@ macro_rules! impl_into_foreach_system { Box::new(SystemFn { state: ForEachState { commands: Commands::default(), - query_access: <($($component,)*) as HecsQuery>::Fetch::access(), + query_access: <($($component,)*) as HecsQuery>::Fetch::access(&()), }, thread_local_execution: ThreadLocalExecution::NextFlush, name: core::any::type_name::().into(), @@ -167,7 +170,10 @@ macro_rules! impl_into_query_system { $(QuerySet<$query_set>,)* ) + Send + Sync +'static, - $($query: HecsQuery,)* + $( + $query: HecsQuery, + $query::Fetch: for<'a> Fetch<'a, State = ()>, + )* $($query_set: QueryTuple,)* $($resource: ResourceQuery,)* { @@ -179,7 +185,7 @@ macro_rules! impl_into_query_system { fn system(mut self) -> Box { let id = SystemId::new(); let query_accesses = vec![ - $(vec![<$query::Fetch as Fetch>::access()],)* + $(vec![<$query::Fetch as Fetch>::access(&())],)* $($query_set::get_accesses(),)* ]; let query_type_names = vec![ diff --git a/crates/bevy_ecs/src/system/query/mod.rs b/crates/bevy_ecs/src/system/query/mod.rs index f7494ad3d8e643..c3f341edff905f 100644 --- a/crates/bevy_ecs/src/system/query/mod.rs +++ b/crates/bevy_ecs/src/system/query/mod.rs @@ -9,90 +9,223 @@ use bevy_hecs::{ use bevy_tasks::ParallelIterator; use std::marker::PhantomData; -/// Provides scoped access to a World according to a given [HecsQuery] +/// Provides scoped access to a World according to a given [`HecsQuery`] #[derive(Debug)] pub struct Query<'a, Q: HecsQuery> { - pub(crate) world: &'a World, - pub(crate) component_access: &'a TypeAccess, - _marker: PhantomData, + pub(crate) query: StatefulQuery<'a, Q, ()>, } -/// An error that occurs when using a [Query] +impl<'a, Q: HecsQuery> Query<'a, Q> +where + Q::Fetch: for<'b> Fetch<'b, State = ()>, +{ + #[inline] + pub fn new(world: &'a World, component_access: &'a TypeAccess) -> Self { + Self { + query: StatefulQuery { + world, + component_access, + state: (), + _marker: PhantomData::default(), + }, + } + } + + /// Iterates over the query results. This can only be called for read-only queries + pub fn iter(&self) -> QueryIter<'_, '_, Q, ()> + where + Q::Fetch: ReadOnlyFetch, + { + self.query.iter() + } + + /// Iterates over the query results + pub fn iter_mut(&mut self) -> QueryIter<'_, '_, Q, ()> { + self.query.iter_mut() + } + + /// Iterates over the query results + /// # Safety + /// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component + pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, '_, Q, ()> { + self.query.iter_unsafe() + } + + #[inline] + pub fn par_iter(&self, batch_size: usize) -> ParIter<'_, '_, Q, ()> + where + Q::Fetch: ReadOnlyFetch, + { + self.query.par_iter(batch_size) + } + + #[inline] + pub fn par_iter_mut(&mut self, batch_size: usize) -> ParIter<'_, '_, Q, ()> { + self.query.par_iter_mut(batch_size) + } + + /// Gets the query result for the given `entity` + pub fn get(&self, entity: Entity) -> Result<::Item, QueryError> + where + Q::Fetch: ReadOnlyFetch + for<'b> Fetch<'b, State = ()>, + { + self.query.get(entity) + } + + /// Gets the query result for the given `entity` + pub fn get_mut(&mut self, entity: Entity) -> Result<::Item, QueryError> + where + Q::Fetch: for<'b> Fetch<'b, State = ()>, + { + self.query.get_mut(entity) + } + + /// Gets the query result for the given `entity` + /// # Safety + /// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component + pub unsafe fn get_unsafe(&self, entity: Entity) -> Result<::Item, QueryError> + where + Q::Fetch: for<'b> Fetch<'b, State = ()>, + { + self.query.get_unsafe(entity) + } + + /// Gets a reference to the entity's component of the given type. This will fail if the entity does not have + /// the given component type or if the given component type does not match this query. + pub fn get_component(&self, entity: Entity) -> Result<&T, QueryError> { + self.query.get_component::(entity) + } + + /// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have + /// the given component type or if the given component type does not match this query. + pub fn get_component_mut( + &mut self, + entity: Entity, + ) -> Result, QueryError> { + self.query.get_component_mut::(entity) + } + + /// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have + /// the given component type + /// # Safety + /// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component + pub unsafe fn get_component_unsafe( + &self, + entity: Entity, + ) -> Result, QueryError> { + self.query.get_component_unsafe(entity) + } + + pub fn removed(&self) -> &[Entity] { + self.query.removed::() + } + + /// Sets the entity's component to the given value. This will fail if the entity does not already have + /// the given component type or if the given component type does not match this query. + pub fn set(&mut self, entity: Entity, component: T) -> Result<(), QueryError> { + self.query.set(entity, component) + } +} + +/// Provides scoped access to a World according to a given [`HecsQuery`]. Essentially identical to +/// [`Query`] except that it supports passing extra state into the query, useful for special kinds +/// of queries such as [`DynamicQuery`]. #[derive(Debug)] -pub enum QueryError { - CannotReadArchetype, - CannotWriteArchetype, - ComponentError(ComponentError), - NoSuchEntity, +pub struct StatefulQuery<'a, Q: HecsQuery, S> { + pub(crate) world: &'a World, + pub(crate) component_access: &'a TypeAccess, + pub(crate) state: S, + _marker: PhantomData, } -impl<'a, Q: HecsQuery> Query<'a, Q> { +impl<'a, Q: HecsQuery, S> StatefulQuery<'a, Q, S> +where + Q::Fetch: for<'b> Fetch<'b, State = S>, +{ #[inline] - pub fn new(world: &'a World, component_access: &'a TypeAccess) -> Self { + pub fn new( + world: &'a World, + component_access: &'a TypeAccess, + state: S, + ) -> Self { Self { world, component_access, + state, _marker: PhantomData::default(), } } /// Iterates over the query results. This can only be called for read-only queries - pub fn iter(&self) -> QueryIter<'_, Q> + pub fn iter(&self) -> QueryIter<'_, '_, Q, S> where Q::Fetch: ReadOnlyFetch, { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict - unsafe { self.world.query_unchecked() } + unsafe { self.world.query_unchecked_stateful(&self.state) } } /// Iterates over the query results - pub fn iter_mut(&mut self) -> QueryIter<'_, Q> { + pub fn iter_mut(&mut self) -> QueryIter<'_, '_, Q, S> { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict - unsafe { self.world.query_unchecked() } + unsafe { self.world.query_unchecked_stateful(&self.state) } } /// Iterates over the query results /// # Safety /// This allows aliased mutability. You must make sure this call does not result in multiple mutable references to the same component - pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, Q> { + pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, '_, Q, S> { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict - self.world.query_unchecked() + self.world.query_unchecked_stateful(&self.state) } #[inline] - pub fn par_iter(&self, batch_size: usize) -> ParIter<'_, Q> + pub fn par_iter(&self, batch_size: usize) -> ParIter<'_, '_, Q, S> where Q::Fetch: ReadOnlyFetch, { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict - unsafe { ParIter::new(self.world.query_batched_unchecked(batch_size)) } + unsafe { + ParIter::new( + self.world + .query_batched_unchecked_stateful(batch_size, &self.state), + ) + } } #[inline] - pub fn par_iter_mut(&mut self, batch_size: usize) -> ParIter<'_, Q> { + pub fn par_iter_mut(&mut self, batch_size: usize) -> ParIter<'_, '_, Q, S> { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict - unsafe { ParIter::new(self.world.query_batched_unchecked(batch_size)) } + unsafe { + ParIter::new( + self.world + .query_batched_unchecked_stateful(batch_size, &self.state), + ) + } } /// Gets the query result for the given `entity` pub fn get(&self, entity: Entity) -> Result<::Item, QueryError> where - Q::Fetch: ReadOnlyFetch, + Q::Fetch: ReadOnlyFetch + for<'b> Fetch<'b, State = S>, { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict unsafe { self.world - .query_one_unchecked::(entity) + .query_one_unchecked_stateful::(entity, &self.state) .map_err(|_err| QueryError::NoSuchEntity) } } /// Gets the query result for the given `entity` - pub fn get_mut(&mut self, entity: Entity) -> Result<::Item, QueryError> { + pub fn get_mut(&mut self, entity: Entity) -> Result<::Item, QueryError> + where + Q::Fetch: for<'b> Fetch<'b, State = S>, + { // SAFE: system runs without conflicts with other systems. same-system queries have runtime borrow checks when they conflict unsafe { self.world - .query_one_unchecked::(entity) + .query_one_unchecked_stateful::(entity, &self.state) .map_err(|_err| QueryError::NoSuchEntity) } } @@ -105,7 +238,7 @@ impl<'a, Q: HecsQuery> Query<'a, Q> { entity: Entity, ) -> Result<::Item, QueryError> { self.world - .query_one_unchecked::(entity) + .query_one_unchecked_stateful::(entity, &self.state) .map_err(|_err| QueryError::NoSuchEntity) } @@ -164,10 +297,13 @@ impl<'a, Q: HecsQuery> Query<'a, Q> { pub unsafe fn get_component_unsafe( &self, entity: Entity, - ) -> Result, QueryError> { + ) -> Result, QueryError> + where + Q::Fetch: for<'b> Fetch<'b, State = S>, + { self.world .get_mut_unchecked(entity) - .map_err(QueryError::ComponentError) + .map_err(|_err| QueryError::NoSuchEntity) } pub fn removed(&self) -> &[Entity] { @@ -183,24 +319,42 @@ impl<'a, Q: HecsQuery> Query<'a, Q> { } } +/// An error that occurs when using a [Query] +#[derive(Debug)] +pub enum QueryError { + CannotReadArchetype, + CannotWriteArchetype, + ComponentError(ComponentError), + NoSuchEntity, +} + /// Parallel version of QueryIter -pub struct ParIter<'w, Q: HecsQuery> { - batched_iter: BatchedIter<'w, Q>, +pub struct ParIter<'s, 'w, Q: HecsQuery, S> { + batched_iter: BatchedIter<'s, 'w, Q, S>, } -impl<'w, Q: HecsQuery> ParIter<'w, Q> { - pub fn new(batched_iter: BatchedIter<'w, Q>) -> Self { +impl<'s, 'w, Q: HecsQuery, S> ParIter<'s, 'w, Q, S> +where + Q::Fetch: for<'a> Fetch<'a, State = S>, +{ + pub fn new(batched_iter: BatchedIter<'s, 'w, Q, S>) -> Self { Self { batched_iter } } } -unsafe impl<'w, Q: HecsQuery> Send for ParIter<'w, Q> {} +unsafe impl<'s, 'w, Q: HecsQuery, S> Send for ParIter<'s, 'w, Q, S> where + Q::Fetch: for<'a> Fetch<'a, State = S> +{ +} -impl<'w, Q: HecsQuery> ParallelIterator> for ParIter<'w, Q> { +impl<'s, 'w, Q: HecsQuery, S> ParallelIterator> for ParIter<'s, 'w, Q, S> +where + Q::Fetch: for<'a> Fetch<'a, State = S>, +{ type Item = >::Item; #[inline] - fn next_batch(&mut self) -> Option> { + fn next_batch(&mut self) -> Option> { self.batched_iter.next() } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 75d4cc370a1d45..6559bd3c8e6559 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,6 +1,11 @@ use crate::resource::Resources; -use bevy_hecs::{ArchetypeComponent, TypeAccess, World}; -use std::{any::TypeId, borrow::Cow}; +use bevy_hecs::{ArchetypeComponent, ComponentId, TypeAccess, World}; +use std::borrow::Cow; + +#[cfg(feature = "dynamic-api")] +use crate::StatefulQuery; +#[cfg(feature = "dynamic-api")] +use bevy_hecs::DynamicQuery; /// Determines the strategy used to run the `run_thread_local` function in a [System] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -25,9 +30,146 @@ pub trait System: Send + Sync { fn id(&self) -> SystemId; fn update(&mut self, world: &World); fn archetype_component_access(&self) -> &TypeAccess; - fn resource_access(&self) -> &TypeAccess; + fn resource_access(&self) -> &TypeAccess; fn thread_local_execution(&self) -> ThreadLocalExecution; fn run(&mut self, world: &World, resources: &Resources); fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources); fn initialize(&mut self, _world: &mut World, _resources: &mut Resources) {} } + +#[cfg(feature = "dynamic-api")] +pub struct DynamicSystem { + pub name: String, + pub state: S, + system_id: SystemId, + system_archetype_component_access: TypeAccess, + query_archetype_component_accesses: Vec>, + resource_access: TypeAccess, + settings: DynamicSystemSettings, +} + +#[cfg(feature = "dynamic-api")] +pub struct DynamicSystemSettings { + pub workload: fn(&mut S, &Resources, &mut [StatefulQuery]), + pub queries: Vec, + pub thread_local_execution: ThreadLocalExecution, + pub thread_local_system: fn(&mut S, &mut World, &mut Resources), + pub init_function: fn(&mut S, &mut World, &mut Resources), + pub resource_access: TypeAccess, +} + +#[cfg(feature = "dynamic-api")] +impl Default for DynamicSystemSettings { + fn default() -> Self { + Self { + workload: |_, _, _| (), + queries: Default::default(), + thread_local_execution: ThreadLocalExecution::NextFlush, + thread_local_system: |_, _, _| (), + init_function: |_, _, _| (), + resource_access: Default::default(), + } + } +} + +#[cfg(feature = "dynamic-api")] +impl DynamicSystem { + pub fn new(name: String, state: S) -> Self { + DynamicSystem { + name, + state, + system_id: SystemId::new(), + resource_access: Default::default(), + system_archetype_component_access: Default::default(), + query_archetype_component_accesses: Default::default(), + settings: Default::default(), + } + } + + pub fn settings(mut self, settings: DynamicSystemSettings) -> Self { + self.settings = settings; + self + } +} + +#[cfg(feature = "dynamic-api")] +impl System for DynamicSystem { + fn name(&self) -> std::borrow::Cow<'static, str> { + self.name.clone().into() + } + + fn id(&self) -> SystemId { + self.system_id + } + + fn update(&mut self, world: &World) { + let Self { + query_archetype_component_accesses, + system_archetype_component_access, + settings, + .. + } = self; + + // Clear previous archetype access list + system_archetype_component_access.clear(); + + for (query, component_access) in settings + .queries + .iter() + .zip(query_archetype_component_accesses.iter_mut()) + { + // Update the component access with the archetypes in the world + component_access.clear(); + query + .access + .get_world_archetype_access(world, Some(component_access)); + + // Make sure the query doesn't collide with any existing queries + if component_access + .get_conflict(system_archetype_component_access) + .is_some() + { + panic!("Dynamic system has conflicting queries."); + } + } + } + + fn archetype_component_access(&self) -> &TypeAccess { + &self.system_archetype_component_access + } + + fn resource_access(&self) -> &TypeAccess { + &self.resource_access + } + + fn thread_local_execution(&self) -> ThreadLocalExecution { + self.settings.thread_local_execution + } + + fn run(&mut self, world: &World, resources: &Resources) { + let mut queries = self + .settings + .queries + .iter() + .zip(self.query_archetype_component_accesses.iter()) + // TODO: Try to avoid cloning the query here + .map(|(query, access)| StatefulQuery::new(world, access, query.clone())) + .collect::>(); + + (self.settings.workload)(&mut self.state, resources, queries.as_mut_slice()); + } + + fn run_thread_local(&mut self, world: &mut World, resources: &mut Resources) { + (self.settings.thread_local_system)(&mut self.state, world, resources); + } + + fn initialize(&mut self, world: &mut World, resources: &mut Resources) { + // Initialize the archetype component accesses with blank accesses + for _ in &self.settings.queries { + self.query_archetype_component_accesses + .push(TypeAccess::::default()); + } + + (self.settings.init_function)(&mut self.state, world, resources); + } +} diff --git a/crates/bevy_render/src/draw.rs b/crates/bevy_render/src/draw.rs index 81804ce2a7407a..b801613ce5fdf6 100644 --- a/crates/bevy_render/src/draw.rs +++ b/crates/bevy_render/src/draw.rs @@ -11,8 +11,8 @@ use crate::{ }; use bevy_asset::{Assets, Handle}; use bevy_ecs::{ - FetchResource, Query, Res, ResMut, ResourceIndex, ResourceQuery, Resources, SystemId, - TypeAccess, UnsafeClone, + ComponentId, FetchResource, Query, Res, ResMut, ResourceIndex, ResourceQuery, Resources, + SystemId, TypeAccess, UnsafeClone, }; use bevy_property::Properties; use std::{any::TypeId, ops::Range, sync::Arc}; @@ -208,7 +208,7 @@ impl<'a> FetchResource<'a> for FetchDrawContext { } } - fn access() -> TypeAccess { + fn access() -> TypeAccess { let mut access = TypeAccess::default(); access.add_write(TypeId::of::>().into()); access.add_write(TypeId::of::>().into()); diff --git a/crates/bevy_render/src/render_graph/nodes/pass_node.rs b/crates/bevy_render/src/render_graph/nodes/pass_node.rs index 4492b305643e34..0243e285feb43f 100644 --- a/crates/bevy_render/src/render_graph/nodes/pass_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/pass_node.rs @@ -12,7 +12,7 @@ use crate::{ }, }; use bevy_asset::{Assets, Handle}; -use bevy_ecs::{HecsQuery, ReadOnlyFetch, Resources, World}; +use bevy_ecs::{Fetch, HecsQuery, ReadOnlyFetch, Resources, World}; use std::{fmt, marker::PhantomData, ops::Deref}; #[derive(Debug)] @@ -141,7 +141,7 @@ impl PassNode { impl Node for PassNode where - Q::Fetch: ReadOnlyFetch, + Q::Fetch: ReadOnlyFetch + for<'a> Fetch<'a, State = ()>, { fn input(&self) -> &[ResourceSlotInfo] { &self.inputs diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 855a9b13e9bd48..f68209bbd22bad 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -12,6 +12,9 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT" keywords = ["bevy"] +[features] +dynamic-api = ["bevy_ecs/dynamic-api"] + [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.3.0" } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 8437603a65168e..7c6cca19433135 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,6 +1,8 @@ use crate::{serde::SceneSerializer, Scene}; use anyhow::Result; -use bevy_ecs::{EntityMap, Resources, World, ComponentId}; +#[cfg(feature = "dynamic-api")] +use bevy_ecs::ComponentId; +use bevy_ecs::{EntityMap, Resources, World}; use bevy_property::{DynamicProperties, PropertyTypeRegistry}; use bevy_type_registry::{ComponentRegistry, TypeRegistry, TypeUuid}; use serde::Serialize; @@ -40,14 +42,17 @@ impl DynamicScene { }) } for type_info in archetype.types() { - if let Some(component_registration) = - component_registry.get(match &type_info.id() { - ComponentId::RustTypeId(id) => id, - ComponentId::ExternalId(_) => { - todo!("Handle external type ids in Bevy scene") - } - }) - { + #[cfg(feature = "dynamic-api")] + let id = match type_info.id() { + ComponentId::RustTypeId(id) => id, + ComponentId::ExternalId(_) => { + todo!("Handle external type ids in Bevy scene") + } + }; + #[cfg(not(feature = "dynamic-api"))] + let id = type_info.id().0; + + if let Some(component_registration) = component_registry.get(&id) { let properties = component_registration.get_component_properties(&archetype, index); diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 29088c07d3ef0c..9688a28aec63f9 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -1,7 +1,9 @@ use crate::{DynamicScene, Scene}; use bevy_app::prelude::*; use bevy_asset::{AssetEvent, Assets, Handle}; -use bevy_ecs::{ComponentId, EntityMap, Resources, World}; +#[cfg(feature = "dynamic-api")] +use bevy_ecs::ComponentId; +use bevy_ecs::{EntityMap, Resources, World}; use bevy_type_registry::TypeRegistry; use bevy_utils::HashMap; use thiserror::Error; @@ -161,14 +163,17 @@ impl SceneSpawner { .entry(*scene_entity) .or_insert_with(|| world.reserve_entity()); for type_info in archetype.types() { - if let Some(component_registration) = - component_registry.get(match &type_info.id() { - ComponentId::RustTypeId(id) => id, - ComponentId::ExternalId(_) => { - todo!("Handle external types in Bevy scene") - } - }) - { + #[cfg(feature = "dynamic-api")] + let id = match type_info.id() { + ComponentId::RustTypeId(id) => id, + ComponentId::ExternalId(_) => { + todo!("Handle external type ids in Bevy scene") + } + }; + #[cfg(not(feature = "dynamic-api"))] + let id = type_info.id().0; + + if let Some(component_registration) = component_registry.get(&id) { component_registration.component_copy( &scene.world, world, diff --git a/examples/ecs/dynamic_components.rs b/examples/ecs/dynamic_components.rs new file mode 100644 index 00000000000000..c4883844185831 --- /dev/null +++ b/examples/ecs/dynamic_components.rs @@ -0,0 +1,160 @@ +//! This example demonstrates how to create systems and queryies for those systems at runtime +//! +//! The primary use-case for doing so would be allow for integrations with scripting languages, +//! where you do no have the information about what systems exist or what queries they will make. +//! +//! In this example the components are `repr(C)` Rust structs that are spawned from Rust code. To +//! see how to also spawn entities with runtime created Components check out the + +use std::{alloc::Layout, time::Duration}; + +use bevy::prelude::*; +use bevy_app::{RunMode, ScheduleRunnerPlugin, ScheduleRunnerSettings}; +use bevy_ecs::{ + DynamicQuery, DynamicSystem, DynamicSystemSettings, EntityBuilder, QueryAccess, TypeInfo, +}; + +// Set our component IDs. These should probably be hashes of a human-friendly but unique type +// identifier, depending on your scripting implementation needs. These identifiers should have data +// in the last 7 bits. See this comment for more info: +// https://users.rust-lang.org/t/requirements-for-hashes-in-hashbrown-hashmaps/50567/2?u=zicklag. +const POS_COMPONENT_ID: u64 = 242237625853274575; +const VEL_COMPONENT_ID: u64 = 6820197023594215835; + +lazy_static::lazy_static! { + static ref POS_INFO: TypeInfo = TypeInfo::of_external( + POS_COMPONENT_ID, + Layout::from_size_align(2, 1).unwrap(), + |_| (), + ); + + static ref VEL_INFO: TypeInfo = TypeInfo::of_external( + VEL_COMPONENT_ID, + Layout::from_size_align(2, 1).unwrap(), + |_| (), + ); +} + +/// Create a system for spawning the scene +fn spawn_scene(world: &mut World, _resources: &mut Resources) { + // Here we will spawn our dynamically created components + + // For each entity we want to create, we must create a `RuntimeBundle` that contains all of that + // entity's components. We're going to create a couple entities, each with two components, one + // representing a Position and one representing a Velocity. Each of these will be made up of two + // bytes for simplicity, one representing the x and y position/velocity. + + // We create our first entity + let mut builder = EntityBuilder::new(); + // Then we add our "Position component" + let entity1 = builder + .add_dynamic( + // We must specify the component information + *POS_INFO, + // And provide the raw byte data data for the component + vec![ + 0, // X position byte + 0, // Y position byte + ] + // And cast the data to a pointer + .as_slice(), + ) + // Next we add our "Velocity component" + .add_dynamic( + *VEL_INFO, + vec![ + 0, // X position byte + 1, // Y position byte + ] + .as_slice(), + ) + .build(); + + // And let's create another entity + let mut builder = EntityBuilder::new(); + let entity2 = builder + .add_dynamic( + *POS_INFO, + vec![ + 0, // X position byte + 0, // Y position byte + ] + .as_slice(), + ) + .add_dynamic( + *VEL_INFO, + vec![ + 2, // X position byte + 0, // Y position byte + ] + .as_slice(), + ) + .build(); + + // Now we can spawn our entities + world.spawn(entity1); + world.spawn(entity2); +} + +fn main() { + // A Dynamic component query which can be constructed at runtime to represent which components + // we want a dynamic system to access. + // + // Notice that the sizes and IDs of the components can be specified at runtime and allow for + // storage of any data as an array of bytes. + let mut query = DynamicQuery::default(); + + // First we add the info for the components we'll be querying and get their component ids + let pos_id = query.register_info(*POS_INFO); + let vel_id = query.register_info(*VEL_INFO); + + // Then we structure our query based on the relationships between the components that we want to + // query + query.access = QueryAccess::union(vec![ + QueryAccess::Read(vel_id, "velocity"), + QueryAccess::Write(pos_id, "position"), + ]); + + // Create a dynamic system with the query we constructed + let pos_vel_system = + DynamicSystem::new("pos_vel_system".into(), () /* system local state */).settings( + DynamicSystemSettings { + queries: vec![query], + workload: |_state, _resources, queries| { + // Print a spacer + println!("-----"); + + // Iterate over the query + for mut components in queries[0].iter_mut() { + // Would panic if we had not set `query.entity = true` + let entity = components.entity; + let pos_bytes = components.mutable.get_mut(&POS_INFO.id()).unwrap(); + let vel_bytes = components.immutable.get(&VEL_INFO.id()).unwrap(); + + // Add the X velocity to the X position + pos_bytes[0] += vel_bytes[0]; + // And the same with the Y + pos_bytes[1] += vel_bytes[1]; + + // Print out the position and velocity + println!( + "Entity: {:?}\tPosition: {:?}\tVelocity: {:?}", + entity, pos_bytes, vel_bytes + ); + } + }, + ..Default::default() + }, + ); + + App::build() + .add_resource(ScheduleRunnerSettings { + run_mode: RunMode::Loop { + wait: Some(Duration::from_secs(1)), + }, + }) + .add_plugin(ScheduleRunnerPlugin::default()) + .add_startup_system(spawn_scene.thread_local_system()) + .add_system(Box::new(pos_vel_system)) + .run(); +} diff --git a/examples/ecs/dynamic_systems.rs b/examples/ecs/dynamic_systems.rs new file mode 100644 index 00000000000000..f26bd948f3bf6c --- /dev/null +++ b/examples/ecs/dynamic_systems.rs @@ -0,0 +1,150 @@ +//! This example demonstrates how to create systems and queries at runtime +//! +//! The primary use-case for doing so would be allow for integrations with scripting languages, +//! where you do no have the information about what systems exist, or what queries they will make, +//! at compile time. +//! +//! In this example the components are Rust structs that are spawned from Rust code. To see how to +//! also spawn entities with runtime created Components check out the `dynamic_components` example. + +use std::{any::TypeId, time::Duration}; + +use bevy::prelude::*; +use bevy_app::{RunMode, ScheduleRunnerPlugin, ScheduleRunnerSettings}; +use bevy_ecs::{ + ComponentId, DynamicQuery, DynamicSystem, DynamicSystemSettings, QueryAccess, TypeInfo, +}; + +// Define our components + +#[derive(Debug, Clone, Copy)] +struct Pos { + x: f32, + y: f32, +} + +#[derive(Debug, Clone, Copy)] +struct Vel { + x: f32, + y: f32, +} + +/// Create a system for spawning the scene +fn spawn_scene(world: &mut World, _resources: &mut Resources) { + #[rustfmt::skip] + world.spawn_batch(vec![ + ( + Pos { + x: 0., + y: 0. + }, + Vel { + x: 0., + y: -1., + } + ), + ( + Pos { + x: 0., + y: 0. + }, + Vel { + x: 0., + y: 1., + } + ), + ( + Pos { + x: 1., + y: 1. + }, + Vel { + x: -0., + y: 0., + } + ), + ]); +} + +fn main() { + // Create a DynamicQuery which can be to outline which components we want a dynamic system to + // access. + // + // Notice that the sizes and IDs of the components must be specified at runtime but this allows + // for storage of any data type as an array of bytes. + let mut query = DynamicQuery::default(); + + // First we add the info for the components we'll be querying and get their component ids + let pos_id = query.register_info(TypeInfo::of::()); + let vel_id = query.register_info(TypeInfo::of::()); + + // Then we structure our query based on the relationships between the components that we want to + // query + query.access = QueryAccess::union(vec![ + QueryAccess::Read(vel_id, "velocity"), + QueryAccess::Write(pos_id, "position"), + ]); + + // Create a dynamic system + let pos_vel_system = DynamicSystem::new( + "pos_vel_system".into(), + (), /* system local state, can be any type */ + ) + .settings( + // Specify the settings for our dynamic system + DynamicSystemSettings { + // Specify all of our queries + queries: vec![ + // In this case we only have one query, but there could be multiple + query, + ], + workload: |_state, _resources, queries| { + println!("-----"); + // Iterate over the first ( and only ) query and get the component results + for mut components in queries[0].iter_mut() { + let pos_id = ComponentId::RustTypeId(TypeId::of::()); + let vel_id = ComponentId::RustTypeId(TypeId::of::()); + // We reference the slices from our mutable and immutable components vectors. The + // indices of the components in the vectors will correspond to the indices that + // they were at in the query we created earlier. + let pos_bytes = components.mutable.get_mut(&pos_id).unwrap(); + let vel_bytes = components.immutable.get(&vel_id).unwrap(); + + // Here we have a couple of utility functions to cast the slices back to their + // original types. + unsafe fn from_slice_mut(s: &mut [u8]) -> &mut T { + debug_assert_eq!(std::mem::size_of::(), s.len()); + &mut *(s.as_mut_ptr() as *mut T) + } + unsafe fn from_slice(s: &[u8]) -> &T { + debug_assert_eq!(std::mem::size_of::(), s.len()); + &*(s.as_ptr() as *mut T) + } + + // Instead of interacting with the raw bytes of our components, we first cast them to + // their Rust structs + let mut pos: &mut Pos = unsafe { from_slice_mut(pos_bytes) }; + let vel: &Vel = unsafe { from_slice(vel_bytes) }; + + // Now we can operate on our components like we would normally in Rust + pos.x += vel.x; + pos.y += vel.y; + + println!("{:?}\t\t{:?}", pos, vel); + } + }, + ..Default::default() + }, + ); + + App::build() + .add_resource(ScheduleRunnerSettings { + run_mode: RunMode::Loop { + wait: Some(Duration::from_secs(1)), + }, + }) + .add_plugin(ScheduleRunnerPlugin::default()) + .add_startup_system(spawn_scene.thread_local_system()) + .add_system(Box::new(pos_vel_system)) + .run(); +}