summaryrefslogtreecommitdiff
path: root/ecs/src
diff options
context:
space:
mode:
Diffstat (limited to 'ecs/src')
-rw-r--r--ecs/src/actions.rs81
-rw-r--r--ecs/src/archetype.rs61
-rw-r--r--ecs/src/component.rs469
-rw-r--r--ecs/src/component/local.rs13
-rw-r--r--ecs/src/component/storage.rs1118
-rw-r--r--ecs/src/component/storage/archetype.rs387
-rw-r--r--ecs/src/component/storage/graph.rs432
-rw-r--r--ecs/src/entity.rs133
-rw-r--r--ecs/src/event.rs95
-rw-r--r--ecs/src/event/component.rs126
-rw-r--r--ecs/src/event/start.rs7
-rw-r--r--ecs/src/extension.rs16
-rw-r--r--ecs/src/lib.rs672
-rw-r--r--ecs/src/lock.rs210
-rw-r--r--ecs/src/pair.rs198
-rw-r--r--ecs/src/phase.rs13
-rw-r--r--ecs/src/query.rs622
-rw-r--r--ecs/src/query/flexible.rs84
-rw-r--r--ecs/src/query/options.rs66
-rw-r--r--ecs/src/query/term.rs115
-rw-r--r--ecs/src/relationship.rs418
-rw-r--r--ecs/src/sole.rs22
-rw-r--r--ecs/src/system.rs162
-rw-r--r--ecs/src/system/stateful.rs60
-rw-r--r--ecs/src/tuple.rs204
-rw-r--r--ecs/src/type_name.rs15
-rw-r--r--ecs/src/uid.rs216
-rw-r--r--ecs/src/util.rs285
-rw-r--r--ecs/src/util/array_vec.rs131
29 files changed, 4170 insertions, 2261 deletions
diff --git a/ecs/src/actions.rs b/ecs/src/actions.rs
index f7b00d3..3dd8755 100644
--- a/ecs/src/actions.rs
+++ b/ecs/src/actions.rs
@@ -1,12 +1,8 @@
use std::marker::PhantomData;
-use std::sync::{Arc, Weak};
-
-use crate::component::{
- Component,
- Metadata as ComponentMetadata,
- Sequence as ComponentSequence,
-};
-use crate::system::{NoInitParamFlag, Param as SystemParam, System};
+use std::rc::{Rc, Weak};
+
+use crate::component::{Parts as ComponentParts, Sequence as ComponentSequence};
+use crate::system::{Param as SystemParam, System};
use crate::uid::{Kind as UidKind, Uid};
use crate::{ActionQueue, World};
@@ -20,36 +16,61 @@ pub struct Actions<'world>
impl<'world> Actions<'world>
{
- /// Adds a spawning a new entity to the action queue.
+ /// Queues up a entity to spawn at the end of the current tick.
pub fn spawn<Comps: ComponentSequence>(&mut self, components: Comps)
{
- self.action_queue.push(Action::Spawn(components.into_vec()));
+ self.action_queue
+ .push(Action::Spawn(components.into_parts_array().into()));
}
- /// Adds component(s) to a entity.
+ /// Queues up despawning a entity at the end of the **next** tick.
+ pub fn despawn(&mut self, entity_uid: Uid)
+ {
+ debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
+
+ self.action_queue.push(Action::Despawn(entity_uid));
+ }
+
+ /// Queues up adding component(s) to a entity at the end of the current tick.
pub fn add_components<Comps>(&mut self, entity_uid: Uid, components: Comps)
where
Comps: ComponentSequence,
{
debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
- self.action_queue
- .push(Action::AddComponents(entity_uid, components.into_vec()));
+ if Comps::COUNT == 0 {
+ return;
+ }
+
+ self.action_queue.push(Action::AddComponents(
+ entity_uid,
+ components.into_parts_array().into(),
+ ));
}
- /// Removes component(s) from a entity.
- pub fn remove_components<Comps>(&mut self, entity_uid: Uid)
- where
- Comps: ComponentSequence,
+ /// Queues up removing component(s) from a entity at the end of the **next** tick.
+ pub fn remove_components(
+ &mut self,
+ entity_uid: Uid,
+ component_ids: impl IntoIterator<Item = Uid>,
+ )
{
debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
- self.action_queue
- .push(Action::RemoveComponents(entity_uid, Comps::metadata()));
+ let mut component_ids = component_ids.into_iter().peekable();
+
+ if component_ids.peek().is_none() {
+ return;
+ }
+
+ self.action_queue.push(Action::RemoveComponents(
+ entity_uid,
+ component_ids.collect(),
+ ));
}
- /// Adds stopping the loop in [`Engine::event_loop`] at the next opportune time to the
- /// action queue.
+ /// Stops the [`World`]. The world will finish the current tick and that tick will be
+ /// the last.
pub fn stop(&mut self)
{
self.action_queue.push(Action::Stop);
@@ -66,18 +87,17 @@ impl<'world> Actions<'world>
}
}
- fn new(action_queue: &'world Arc<ActionQueue>) -> Self
+ fn new(action_queue: &'world Rc<ActionQueue>) -> Self
{
Self {
action_queue,
- action_queue_weak: Arc::downgrade(action_queue),
+ action_queue_weak: Rc::downgrade(action_queue),
}
}
}
-unsafe impl<'world> SystemParam<'world> for Actions<'world>
+impl<'world> SystemParam<'world> for Actions<'world>
{
- type Flags = NoInitParamFlag;
type Input = ();
fn initialize<SystemImpl>(
@@ -122,11 +142,11 @@ impl WeakRef
#[derive(Debug, Clone)]
pub struct Ref<'weak_ref>
{
- action_queue: Arc<ActionQueue>,
+ action_queue: Rc<ActionQueue>,
_pd: PhantomData<&'weak_ref ()>,
}
-impl<'weak_ref> Ref<'weak_ref>
+impl Ref<'_>
{
#[must_use]
pub fn to_actions(&self) -> Actions<'_>
@@ -139,8 +159,9 @@ impl<'weak_ref> Ref<'weak_ref>
#[derive(Debug)]
pub(crate) enum Action
{
- Spawn(Vec<Box<dyn Component>>),
- AddComponents(Uid, Vec<Box<dyn Component>>),
- RemoveComponents(Uid, Vec<ComponentMetadata>),
+ Spawn(Vec<ComponentParts>),
+ Despawn(Uid),
+ AddComponents(Uid, Vec<ComponentParts>),
+ RemoveComponents(Uid, Vec<Uid>),
Stop,
}
diff --git a/ecs/src/archetype.rs b/ecs/src/archetype.rs
deleted file mode 100644
index d2ee36a..0000000
--- a/ecs/src/archetype.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use std::hash::{DefaultHasher, Hash, Hasher};
-
-use crate::component::{
- IsOptional as ComponentIsOptional,
- Metadata as ComponentMetadata,
-};
-use crate::uid::Uid;
-
-/// Archetype ID.
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Id
-{
- hash: u64,
-}
-
-impl Id
-{
- pub fn from_components_metadata(
- components_metadata: &impl AsRef<[ComponentMetadata]>,
- ) -> Self
- {
- debug_assert!(
- components_metadata.as_ref().len() > 0,
- "Cannot create a archetype ID from a empty component metadata list"
- );
-
- debug_assert!(
- components_metadata
- .as_ref()
- .is_sorted_by_key(|comp_metadata| comp_metadata.id),
- "Cannot create archetype ID from a unsorted component metadata list"
- );
-
- Self::new(
- components_metadata
- .as_ref()
- .iter()
- .filter_map(|component_metadata| {
- if component_metadata.is_optional == ComponentIsOptional::Yes {
- return None;
- }
-
- Some(component_metadata.id)
- }),
- )
- }
-
- /// Returns the ID of a archetype with the given components.
- fn new(component_ids: impl IntoIterator<Item = Uid>) -> Self
- {
- let mut hasher = DefaultHasher::new();
-
- for component_id in component_ids {
- component_id.hash(&mut hasher);
- }
-
- let hash = hasher.finish();
-
- Self { hash }
- }
-}
diff --git a/ecs/src/component.rs b/ecs/src/component.rs
index a9894b7..5a8cd0b 100644
--- a/ecs/src/component.rs
+++ b/ecs/src/component.rs
@@ -1,79 +1,53 @@
use std::any::{type_name, Any};
use std::fmt::Debug;
+use std::ops::{Deref, DerefMut};
+use ecs_macros::Component;
+use hashbrown::HashSet;
use seq_macro::seq;
-use crate::lock::{ReadGuard, WriteGuard};
-use crate::system::{ComponentRef, ComponentRefMut, Input as SystemInput};
-use crate::type_name::TypeName;
+use crate::lock::{
+ Error as LockError,
+ MappedReadGuard,
+ MappedWriteGuard,
+ ReadGuard,
+ WriteGuard,
+};
+use crate::system::Input as SystemInput;
use crate::uid::Uid;
-use crate::{EntityComponent, World};
+use crate::util::Array;
+use crate::EntityComponentRef;
pub mod local;
pub(crate) mod storage;
-pub trait Component: SystemInput + Any + TypeName
+pub trait Component: SystemInput + Any
{
- /// The component type in question. Will usually be `Self`
- type Component: Component
- where
- Self: Sized;
-
- type RefMut<'component>
- where
- Self: Sized;
-
- type Ref<'component>
- where
- Self: Sized;
-
/// Returns the ID of this component.
fn id() -> Uid
where
Self: Sized;
- /// The ID of the component `self`. Returns the same value as [`Component::id`].
- fn self_id(&self) -> Uid;
-
- #[doc(hidden)]
- fn as_any_mut(&mut self) -> &mut dyn Any;
-
- #[doc(hidden)]
- fn as_any(&self) -> &dyn Any;
-
- /// Whether the component `self` is optional. Returns the same value as
- /// [`Component::is_optional`].
- fn self_is_optional(&self) -> IsOptional
- {
- IsOptional::No
- }
-
- /// Returns whether this component is optional.
- #[must_use]
- fn is_optional() -> IsOptional
- where
- Self: Sized,
- {
- IsOptional::No
- }
+ /// Returns the name of this component.
+ fn name(&self) -> &'static str;
}
impl dyn Component
{
pub fn downcast_mut<Real: 'static>(&mut self) -> Option<&mut Real>
{
- self.as_any_mut().downcast_mut()
+ (self as &mut dyn Any).downcast_mut()
}
pub fn downcast_ref<Real: 'static>(&self) -> Option<&Real>
{
- self.as_any().downcast_ref()
+ (self as &dyn Any).downcast_ref()
}
pub fn is<Other: 'static>(&self) -> bool
{
- self.as_any().is::<Other>()
+ (self as &dyn Any).is::<Other>()
}
}
@@ -85,249 +59,292 @@ impl Debug for dyn Component
}
}
-impl TypeName for Box<dyn Component>
+/// A sequence of components.
+pub trait Sequence
{
- fn type_name(&self) -> &'static str
- {
- self.as_ref().type_name()
- }
+ /// The number of components in this component sequence.
+ const COUNT: usize;
+
+ type PartsArray: Array<Parts>;
+
+ fn into_parts_array(self) -> Self::PartsArray;
}
-impl<ComponentT> Component for Option<ComponentT>
-where
- ComponentT: Component,
+#[derive(Debug)]
+pub struct Handle<'a, ComponentData: 'static>
{
- type Component = ComponentT;
- type Ref<'component> = Option<ComponentRef<'component, ComponentT>>;
- type RefMut<'component> = Option<ComponentRefMut<'component, ComponentT>>;
+ inner: MappedReadGuard<'a, ComponentData>,
+}
- fn id() -> Uid
+impl<'comp, ComponentData: 'static> Handle<'comp, ComponentData>
+{
+ /// Creates a new handle instance from a [`EntityComponentRef`].
+ ///
+ /// # Errors
+ /// Will return `Err` if acquiring the component's lock fails.
+ pub fn from_entity_component_ref(
+ entity_component_ref: EntityComponentRef<'comp>,
+ ) -> Result<Self, HandleError>
{
- ComponentT::id()
+ Ok(Self::new(
+ entity_component_ref
+ .component()
+ .read_nonblock()
+ .map_err(AcquireLockError)?,
+ ))
}
- fn self_id(&self) -> Uid
+ pub(crate) fn new(inner: ReadGuard<'comp, Box<dyn Any>>) -> Self
{
- Self::id()
+ Self {
+ inner: inner.map(|component| {
+ component
+ .downcast_ref::<ComponentData>()
+ .unwrap_or_else(|| {
+ panic!(
+ "Failed to downcast component to type {}",
+ type_name::<ComponentData>()
+ );
+ })
+ }),
+ }
}
+}
+
+impl<ComponentData: 'static> Deref for Handle<'_, ComponentData>
+{
+ type Target = ComponentData;
- fn as_any_mut(&mut self) -> &mut dyn Any
+ fn deref(&self) -> &Self::Target
{
- self
+ &self.inner
}
+}
- fn as_any(&self) -> &dyn Any
+#[derive(Debug)]
+pub struct HandleMut<'a, ComponentData: 'static>
+{
+ inner: MappedWriteGuard<'a, ComponentData>,
+}
+
+impl<'comp, ComponentData: 'static> HandleMut<'comp, ComponentData>
+{
+ /// Creates a new handle instance from a [`EntityComponentRef`].
+ ///
+ /// # Errors
+ /// Will return `Err` if acquiring the component's lock fails.
+ pub fn from_entity_component_ref(
+ entity_component_ref: EntityComponentRef<'comp>,
+ ) -> Result<Self, HandleError>
{
- self
+ Ok(Self::new(
+ entity_component_ref
+ .component()
+ .write_nonblock()
+ .map_err(AcquireLockError)?,
+ ))
}
- fn self_is_optional(&self) -> IsOptional
+ pub(crate) fn new(inner: WriteGuard<'comp, Box<dyn Any>>) -> Self
{
- Self::is_optional()
+ Self {
+ inner: inner.map(|component| {
+ component
+ .downcast_mut::<ComponentData>()
+ .unwrap_or_else(|| {
+ panic!(
+ "Failed to downcast component to type {}",
+ type_name::<ComponentData>()
+ );
+ })
+ }),
+ }
}
+}
- fn is_optional() -> IsOptional
+impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData>
+{
+ type Target = ComponentData;
+
+ fn deref(&self) -> &Self::Target
{
- IsOptional::Yes
+ &self.inner
}
}
-impl<ComponentT> TypeName for Option<ComponentT>
-where
- ComponentT: Component,
+impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData>
{
- fn type_name(&self) -> &'static str
+ fn deref_mut(&mut self) -> &mut Self::Target
{
- type_name::<Self>()
+ &mut self.inner
}
}
-impl<ComponentT> SystemInput for Option<ComponentT> where ComponentT: Component {}
+#[derive(Debug, thiserror::Error)]
+pub enum HandleError
+{
+ #[error(transparent)]
+ AcquireLockFailed(#[from] AcquireLockError),
+}
-/// A sequence of components.
-pub trait Sequence
+#[derive(Debug, thiserror::Error)]
+#[error("Failed to acquire component lock")]
+pub struct AcquireLockError(#[source] LockError);
+
+macro_rules! inner {
+ ($c: tt) => {
+ seq!(I in 0..=$c {
+ impl<#(IntoCompParts~I: IntoParts,)*> Sequence for (#(IntoCompParts~I,)*)
+ {
+ const COUNT: usize = $c + 1;
+
+ type PartsArray = [Parts; $c + 1];
+
+ fn into_parts_array(self) -> Self::PartsArray
+ {
+ [#({
+ self.I.into_parts()
+ },)*]
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..=16 {
+ inner!(C);
+});
+
+impl Sequence for ()
{
- type MutRefs<'component>
- where
- Self: 'component;
+ type PartsArray = [Parts; 0];
- type Refs<'component>
- where
- Self: 'component;
-
- fn into_vec(self) -> Vec<Box<dyn Component>>;
-
- fn metadata() -> Vec<Metadata>;
-
- fn from_components_mut<'component>(
- components: impl Iterator<Item = &'component EntityComponent>,
- world: &'component World,
- lock_component: fn(
- entity_component: &EntityComponent,
- ) -> WriteGuard<'_, Box<dyn Component>>,
- ) -> Self::MutRefs<'component>;
-
- fn from_components<'component>(
- components: impl Iterator<Item = &'component EntityComponent>,
- world: &'component World,
- lock_component: fn(
- entity_component: &EntityComponent,
- ) -> ReadGuard<'_, Box<dyn Component>>,
- ) -> Self::Refs<'component>;
+ const COUNT: usize = 0;
+
+ fn into_parts_array(self) -> Self::PartsArray
+ {
+ []
+ }
+}
+
+pub trait IntoParts
+{
+ fn into_parts(self) -> Parts;
+}
+
+impl<ComponentT> IntoParts for ComponentT
+where
+ ComponentT: Component,
+{
+ fn into_parts(self) -> Parts
+ {
+ Parts::builder()
+ .name(type_name::<Self>())
+ .build(Self::id(), self)
+ }
}
-/// [`Component`] metadata.
-#[derive(Debug, Clone)]
+/// The parts of a component.
+#[derive(Debug)]
#[non_exhaustive]
-pub struct Metadata
+pub struct Parts
{
- pub id: Uid,
- pub is_optional: IsOptional,
+ id: Uid,
+ name: &'static str,
+ data: Box<dyn Any>,
}
-impl Metadata
+impl Parts
{
- pub fn get<ComponentT: Component + ?Sized>(component: &ComponentT) -> Self
+ #[must_use]
+ pub fn id(&self) -> Uid
{
- Self {
- id: component.self_id(),
- is_optional: component.self_is_optional(),
- }
+ self.id
}
- pub fn of<ComponentT: Component>() -> Self
+ #[must_use]
+ pub fn name(&self) -> &'static str
{
- Self {
- id: ComponentT::id(),
- is_optional: ComponentT::is_optional(),
- }
+ self.name
+ }
+
+ #[must_use]
+ pub fn builder() -> PartsBuilder
+ {
+ PartsBuilder::default()
+ }
+
+ pub(crate) fn into_data(self) -> Box<dyn Any>
+ {
+ self.data
}
}
-/// Whether or not a `Component` is optional.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum IsOptional
+#[derive(Debug)]
+pub struct PartsBuilder
{
- Yes,
- No,
+ name: &'static str,
}
-impl From<bool> for IsOptional
+impl PartsBuilder
{
- fn from(is_optional: bool) -> Self
+ #[must_use]
+ pub fn name(mut self, name: &'static str) -> Self
{
- if is_optional {
- return IsOptional::Yes;
- }
+ self.name = name;
+ self
+ }
- IsOptional::No
+ #[must_use]
+ pub fn build<Data: 'static>(self, id: Uid, data: Data) -> Parts
+ {
+ Parts {
+ id,
+ name: self.name,
+ data: Box::new(data),
+ }
}
}
-pub trait FromOptionalMut<'comp>
+impl Default for PartsBuilder
{
- fn from_optional_mut_component(
- optional_component: Option<WriteGuard<'comp, Box<dyn Component>>>,
- world: &'comp World,
- ) -> Self;
+ fn default() -> Self
+ {
+ Self { name: "(unspecified)" }
+ }
}
-pub trait FromOptional<'comp>
+/// Pending component removals for a entity.
+#[derive(Debug, Clone, Component)]
+pub struct Removals
{
- fn from_optional_component(
- optional_component: Option<ReadGuard<'comp, Box<dyn Component>>>,
- world: &'comp World,
- ) -> Self;
+ component_ids: HashSet<Uid>,
}
-macro_rules! inner {
- ($c: tt) => {
- seq!(I in 0..=$c {
- impl<#(Comp~I: Component,)*> Sequence for (#(Comp~I,)*)
- where
- #(for<'comp> Comp~I::RefMut<'comp>: FromOptionalMut<'comp>,)*
- #(for<'comp> Comp~I::Ref<'comp>: FromOptional<'comp>,)*
- {
- type MutRefs<'component> = (#(Comp~I::RefMut<'component>,)*)
- where Self: 'component;
-
- type Refs<'component> = (#(Comp~I::Ref<'component>,)*)
- where Self: 'component;
-
- fn into_vec(self) -> Vec<Box<dyn Component>>
- {
- Vec::from_iter([#(Box::new(self.I) as Box<dyn Component>,)*])
- }
-
- fn metadata() -> Vec<Metadata>
- {
- vec![
- #(
- Metadata {
- id: Comp~I::id(),
- is_optional: Comp~I::is_optional()
- },
- )*
- ]
- }
-
- fn from_components_mut<'component>(
- components: impl Iterator<Item = &'component EntityComponent>,
- world: &'component World,
- lock_component: fn(
- entity_component: &EntityComponent,
- ) -> WriteGuard<'_, Box<dyn Component>>,
- ) -> Self::MutRefs<'component>
- {
- #(
- let mut comp_~I: Option<WriteGuard<Box<dyn Component>>> = None;
- )*
-
- for comp in components {
- #(
- if comp.id == Comp~I::Component::id() {
- comp_~I = Some(lock_component(comp));
- continue;
- }
- )*
- }
-
- (#(
- Comp~I::RefMut::from_optional_mut_component(comp_~I, world),
- )*)
- }
+impl Removals
+{
+ pub fn contains<ComponentT: Component>(&self) -> bool
+ {
+ self.contains_id(ComponentT::id())
+ }
- fn from_components<'component>(
- components: impl Iterator<Item = &'component EntityComponent>,
- world: &'component World,
- lock_component: fn(
- entity_component: &EntityComponent,
- ) -> ReadGuard<'_, Box<dyn Component>>,
- ) -> Self::Refs<'component>
- {
+ pub fn contains_id(&self, component_id: Uid) -> bool
+ {
+ self.component_ids.contains(&component_id)
+ }
- #(
- let mut comp_~I: Option<ReadGuard<Box<dyn Component>>> = None;
- )*
-
- for comp in components {
- #(
- if comp.id == Comp~I::Component::id() {
- comp_~I = Some(lock_component(comp));
- continue;
- }
- )*
- }
-
- (#(
- Comp~I::Ref::from_optional_component(comp_~I, world),
- )*)
- }
- }
- });
- };
+ pub(crate) fn add_ids(&mut self, ids: impl IntoIterator<Item = Uid>)
+ {
+ self.component_ids.extend(ids)
+ }
}
-seq!(C in 0..=64 {
- inner!(C);
-});
+impl FromIterator<Uid> for Removals
+{
+ fn from_iter<T: IntoIterator<Item = Uid>>(iter: T) -> Self
+ {
+ Self {
+ component_ids: iter.into_iter().collect(),
+ }
+ }
+}
diff --git a/ecs/src/component/local.rs b/ecs/src/component/local.rs
index 20627bf..0f6f641 100644
--- a/ecs/src/component/local.rs
+++ b/ecs/src/component/local.rs
@@ -1,21 +1,20 @@
use std::ops::{Deref, DerefMut};
-use crate::component::Component;
-use crate::system::{ComponentRefMut, Param as SystemParam, System};
+use crate::component::{Component, HandleMut as ComponentHandleMut};
+use crate::system::{Param as SystemParam, System};
use crate::World;
/// Holds a component which is local to a single system.
#[derive(Debug)]
pub struct Local<'world, LocalComponent: Component>
{
- local_component: ComponentRefMut<'world, LocalComponent>,
+ local_component: ComponentHandleMut<'world, LocalComponent>,
}
-unsafe impl<'world, LocalComponent> SystemParam<'world> for Local<'world, LocalComponent>
+impl<'world, LocalComponent> SystemParam<'world> for Local<'world, LocalComponent>
where
LocalComponent: Component,
{
- type Flags = ();
type Input = LocalComponent;
fn initialize<SystemImpl>(
@@ -39,7 +38,7 @@ where
}
}
-impl<'world, LocalComponent> Deref for Local<'world, LocalComponent>
+impl<LocalComponent> Deref for Local<'_, LocalComponent>
where
LocalComponent: Component,
{
@@ -51,7 +50,7 @@ where
}
}
-impl<'world, LocalComponent> DerefMut for Local<'world, LocalComponent>
+impl<LocalComponent> DerefMut for Local<'_, LocalComponent>
where
LocalComponent: Component,
{
diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs
index ffd682e..b27b552 100644
--- a/ecs/src/component/storage.rs
+++ b/ecs/src/component/storage.rs
@@ -1,621 +1,787 @@
-use std::any::type_name;
-use std::borrow::Borrow;
+use std::any::Any;
+use std::array::IntoIter as ArrayIter;
use std::cell::RefCell;
-use std::collections::{HashMap, HashSet};
-use std::slice::Iter as SliceIter;
-use std::vec::IntoIter as OwnedVecIter;
-
-use crate::archetype::Id as ArchetypeId;
-use crate::component::{
- Component,
- IsOptional as ComponentIsOptional,
- Metadata as ComponentMetadata,
+use std::vec::IntoIter as VecIntoIter;
+
+use hashbrown::HashMap;
+
+use crate::component::storage::archetype::{
+ Archetype,
+ Entity as ArchetypeEntity,
+ EntityComponent as ArchetypeEntityComponent,
+ Id as ArchetypeId,
};
-use crate::type_name::TypeName;
-use crate::uid::Uid;
-use crate::util::Sortable;
-use crate::EntityComponent;
+use crate::component::storage::graph::{
+ ArchetypeAddEdgeDfsIter,
+ ArchetypeAddEdgeDfsIterResult,
+ ArchetypeEdges,
+ Graph,
+};
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::{BorrowedOrOwned, Either, StreamingIterator, VecExt};
-#[derive(Debug, Default)]
-pub struct Storage
+pub mod archetype;
+
+mod graph;
+
+#[derive(Debug)]
+pub struct ArchetypeSearchTerms<'a>
{
- archetypes: Vec<Archetype>,
- archetype_lookup: RefCell<HashMap<ArchetypeId, ArchetypeLookupEntry>>,
- entity_archetype_lookup: HashMap<Uid, ArchetypeId>,
+ pub required_components: &'a [Uid],
+ pub excluded_components: &'a [Uid],
}
-impl Storage
+impl ArchetypeSearchTerms<'_>
{
- pub fn find_entities<CompMetadataList>(
- &self,
- mut components_metadata: CompMetadataList,
- ) -> ArchetypeRefIter<'_>
- where
- CompMetadataList: Sortable<Item = ComponentMetadata>,
- CompMetadataList: AsRef<[ComponentMetadata]>,
+ fn excluded_contains(&self, comp_id: Uid) -> bool
{
- components_metadata.sort_by_key_b(|component_metadata| component_metadata.id);
+ let comp_id_kind = comp_id.kind();
- let archetype_id = ArchetypeId::from_components_metadata(&components_metadata);
+ debug_assert!(
+ comp_id_kind == UidKind::Component
+ || (comp_id_kind == UidKind::Pair
+ && comp_id.target_component() != Uid::wildcard())
+ );
- // This looks stupid but the borrow checker complains otherwise
- if self.archetype_lookup.borrow().contains_key(&archetype_id) {
- return self.iter_archetypes_by_lookup(archetype_id);
- }
+ let is_found = self.excluded_components.binary_search(&comp_id).is_ok();
- let comp_ids_set = create_non_opt_component_id_set(components_metadata.as_ref());
+ if !is_found && comp_id_kind == UidKind::Pair {
+ return self.excluded_components.iter().any(|excluded_comp_id| {
+ excluded_comp_id.kind() == UidKind::Pair
+ && excluded_comp_id.has_same_relation_as(comp_id)
+ && excluded_comp_id.target_component() == Uid::wildcard()
+ });
+ }
- let matching_archetype_indices = self
- .archetypes
- .iter()
- .enumerate()
- .filter_map(|(index, archetype)| {
- if archetype.component_ids_is_superset(&comp_ids_set) {
- return Some(index);
- }
+ is_found
+ }
- None
- })
- .collect();
-
- self.archetype_lookup.borrow_mut().insert(
- archetype_id,
- ArchetypeLookupEntry {
- component_ids: comp_ids_set,
- archetype_indices: matching_archetype_indices,
- },
- );
+ fn contains_conflicting(&self) -> bool
+ {
+ self.excluded_components.iter().any(|excluded_comp_id| {
+ self.required_components
+ .binary_search(excluded_comp_id)
+ .is_ok()
+ })
+ }
- self.iter_archetypes_by_lookup(archetype_id)
+ fn archetype_contains_all_required(&self, archetype: &Archetype) -> bool
+ {
+ self.required_components
+ .iter()
+ .all(|comp_id| archetype.contains_matching_component(*comp_id))
}
+}
- pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype>
+#[derive(Debug, Default)]
+pub struct Storage
+{
+ graph: Graph,
+ entity_archetype_lookup: HashMap<Uid, ArchetypeId>,
+ imaginary_archetypes: RefCell<Vec<ImaginaryArchetype>>,
+}
+
+impl Storage
+{
+ pub fn search_archetypes<'search_terms>(
+ &self,
+ search_terms: ArchetypeSearchTerms<'search_terms>,
+ ) -> ArchetypeRefIter<'_, 'search_terms>
{
- let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+ let archetype_id = ArchetypeId::new(search_terms.required_components);
+
+ if search_terms.contains_conflicting() {
+ return ArchetypeRefIter {
+ storage: self,
+ pre_iter: Either::B(Vec::new().into_iter()),
+ dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &[]),
+ search_terms,
+ };
+ }
- let archetype_index =
- self.find_archetype_index_with_entity(*archetype_id, entity_uid)?;
+ let Some(add_edge_recursive_iter) =
+ self.graph.dfs_archetype_add_edges(archetype_id)
+ else {
+ self.imaginary_archetypes
+ .borrow_mut()
+ .push(ImaginaryArchetype {
+ id: ArchetypeId::new(search_terms.required_components.iter().filter(
+ |required_comp_id| {
+ required_comp_id.kind() != UidKind::Pair
+ || required_comp_id.target_component() != Uid::wildcard()
+ },
+ )),
+ component_ids: search_terms
+ .required_components
+ .iter()
+ .copied()
+ .filter(|required_comp_id| {
+ required_comp_id.kind() != UidKind::Pair
+ || required_comp_id.target_component() != Uid::wildcard()
+ })
+ .collect(),
+ });
+
+ let found_archetypes = self.find_all_archetype_with_comps(&search_terms);
+
+ return ArchetypeRefIter {
+ storage: self,
+ pre_iter: Either::B(found_archetypes.clone().into_iter()),
+ dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &found_archetypes),
+ search_terms,
+ };
+ };
- self.archetypes.get(archetype_index)
+ ArchetypeRefIter {
+ storage: self,
+ pre_iter: Either::A([archetype_id].into_iter()),
+ dfs_iter: add_edge_recursive_iter,
+ search_terms,
+ }
}
- #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
- pub fn push_entity(
- &mut self,
- entity_uid: Uid,
- mut components: Vec<Box<dyn Component>>,
- ) -> Result<(ArchetypeId, Uid), Error>
+ pub fn get_archetype_by_id(&self, id: ArchetypeId) -> Option<&Archetype>
{
- if self.entity_archetype_lookup.contains_key(&entity_uid) {
- return Err(Error::EntityAlreadyExists(entity_uid));
+ Some(self.graph.get_node_by_id(id)?.archetype())
+ }
+
+ pub fn create_entity(&mut self, uid: Uid) -> Result<(), Error>
+ {
+ debug_assert_eq!(uid.kind(), UidKind::Entity);
+
+ if self.entity_archetype_lookup.contains_key(&uid) {
+ return Err(Error::EntityAlreadyExists(uid));
}
- components.sort_by_key(|component| component.self_id());
+ let empty_archetype_id = ArchetypeId::new_empty();
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Pushing entity with components: ({})",
- components
- .iter()
- .map(|component| component.type_name())
- .collect::<Vec<_>>()
- .join(", ")
- );
+ let archetype_node = self.graph.get_or_create_node(empty_archetype_id, &[]);
- let archetype_id = ArchetypeId::from_components_metadata(
- &components
- .iter()
- .map(|component| ComponentMetadata::get(&**component))
- .collect::<Vec<_>>(),
- );
+ archetype_node
+ .archetype_mut()
+ .push_entity(ArchetypeEntity::new(uid, []));
- let comp_ids_set = create_non_opt_component_id_set(
- components
- .iter()
- .map(|component| ComponentMetadata::get(&**component)),
- );
+ self.entity_archetype_lookup.insert(uid, empty_archetype_id);
- let archetype_index =
- self.get_or_create_archetype(archetype_id, &comp_ids_set, &components);
+ Ok(())
+ }
- self.populate_matching_archetype_lookup_entries(&comp_ids_set, archetype_index);
+ pub fn remove_entity(&mut self, entity_uid: Uid) -> Result<ArchetypeEntity, Error>
+ {
+ let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else {
+ return Err(Error::EntityDoesNotExist(entity_uid));
+ };
- let archetype = self
- .archetypes
- .get_mut(archetype_index)
- .expect("Archetype is gone");
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(*archetype_id)
+ .expect("Archetype should exist");
- archetype.push_entity(entity_uid, components);
+ let entity = archetype_node
+ .archetype_mut()
+ .remove_entity(entity_uid)
+ .expect("Entity should exist in archetype");
- archetype
- .entity_lookup
- .insert(entity_uid, archetype.entities.len() - 1);
+ self.entity_archetype_lookup.remove(&entity_uid);
- self.entity_archetype_lookup
- .insert(entity_uid, archetype_id);
+ Ok(entity)
+ }
- Ok((archetype_id, entity_uid))
+ pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype>
+ {
+ let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+
+ self.get_archetype_by_id(*archetype_id)
}
- pub fn add_components_to_entity(
+ pub fn add_entity_component(
&mut self,
entity_uid: Uid,
- components: Vec<Box<dyn Component>>,
- ) -> Option<()>
+ (component_id, component_name, component): (Uid, &'static str, Box<dyn Any>),
+ ) -> Result<(), Error>
{
- let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+ let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else {
+ return Err(Error::EntityDoesNotExist(entity_uid));
+ };
+
+ let archetype_id = *archetype_id;
+
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
+
+ if archetype_node
+ .archetype()
+ .contains_component_with_exact_id(component_id)
+ {
+ return Err(Error::ComponentAlreadyInEntity {
+ entity: entity_uid,
+ component: component_id,
+ });
+ }
- let archetype_index =
- self.find_archetype_index_with_entity(*archetype_id, entity_uid)?;
+ let add_edge_archetype_id = if let Some(add_edge_id) = archetype_node
+ .get_or_insert_edges(component_id, ArchetypeEdges::default)
+ .add
+ {
+ if !self.graph.contains_archetype(add_edge_id) {
+ let (_, add_edge_comp_ids) = self
+ .graph
+ .get_node_by_id(archetype_id)
+ .expect("Archetype should exist")
+ .make_add_edge(component_id);
+
+ self.graph.create_node(add_edge_id, &add_edge_comp_ids);
+ }
- let archetype = self.archetypes.get_mut(archetype_index)?;
+ add_edge_id
+ } else {
+ let archetype_node = self
+ .graph
+ .get_node_by_id(archetype_id)
+ .expect("Archetype should exist");
- let contains_component_already = components
- .iter()
- .any(|component| archetype.component_ids.contains_key(&component.self_id()));
-
- if contains_component_already {
- let component_cnt = components.len();
-
- // TODO: return error
- panic!(
- "Entity with UID {:?} already contains one or more component(s) ({})",
- entity_uid,
- components
- .iter()
- .flat_map(|component| [component.type_name(), ", "])
- .enumerate()
- .take_while(|(index, _)| { *index < (component_cnt * 2) - 1 })
- .map(|(_, component)| component)
- .collect::<String>()
- );
- }
+ let (add_edge_id, add_edge_comp_ids) =
+ archetype_node.make_add_edge(component_id);
- let entity = archetype.take_entity(entity_uid)?;
+ if !self.graph.contains_archetype(add_edge_id) {
+ self.graph.create_node(add_edge_id, &add_edge_comp_ids);
+ }
- self.entity_archetype_lookup.remove(&entity_uid);
+ add_edge_id
+ };
+
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
+
+ let mut entity = archetype_node
+ .archetype_mut()
+ .remove_entity(entity_uid)
+ .expect("Entity should exist in archetype");
+
+ let add_edge_archetype = self
+ .graph
+ .get_node_by_id_mut(add_edge_archetype_id)
+ .expect("Add edge archetype should exist")
+ .archetype_mut();
+
+ entity.insert_component(
+ component_id,
+ ArchetypeEntityComponent::new(component, component_id, component_name),
+ add_edge_archetype,
+ );
- self.push_entity(
- entity_uid,
- entity
- .components
- .into_iter()
- .map(|component| component.component.into_inner())
- .chain(components)
- .collect(),
- )
- .expect("Not supposed to return Err since the entity is removed");
+ add_edge_archetype.push_entity(entity);
- Some(())
+ self.entity_archetype_lookup
+ .insert(entity_uid, add_edge_archetype_id);
+
+ Ok(())
}
- pub fn remove_components_from_entity(
+ pub fn remove_entity_component(
&mut self,
entity_uid: Uid,
- component_ids: impl IntoIterator<Item = Uid>,
- ) -> Option<()>
+ component_id: Uid,
+ ) -> Result<(), Error>
{
- let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+ let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else {
+ return Err(Error::EntityDoesNotExist(entity_uid));
+ };
+
+ let archetype_id = *archetype_id;
+
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
+
+ if !archetype_node
+ .archetype()
+ .contains_component_with_exact_id(component_id)
+ {
+ return Err(Error::ComponentNotFoundInEntity {
+ entity: entity_uid,
+ component: component_id,
+ });
+ }
- let archetype_index =
- self.find_archetype_index_with_entity(*archetype_id, entity_uid)?;
+ let remove_edge_id = archetype_node
+ .get_or_insert_edges(component_id, ArchetypeEdges::default)
+ .remove
+ .unwrap_or_else(|| {
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
+
+ let (remove_edge_id, remove_edge_comp_ids) =
+ archetype_node.make_remove_edge(component_id);
+
+ if !self.graph.contains_archetype(remove_edge_id) {
+ self.graph
+ .create_node(remove_edge_id, &remove_edge_comp_ids);
+ }
- let archetype = self.archetypes.get_mut(archetype_index)?;
+ remove_edge_id
+ });
- let entity = archetype.take_entity(entity_uid)?;
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
- let component_ids_set = component_ids.into_iter().collect::<HashSet<_>>();
+ let mut entity = archetype_node
+ .archetype_mut()
+ .remove_entity(entity_uid)
+ .expect("Entity should exist in archetype");
- self.entity_archetype_lookup.remove(&entity_uid);
+ entity.remove_component(component_id, archetype_node.archetype());
- self.push_entity(
- entity_uid,
- entity
- .components
- .into_iter()
- .map(|component| component.component.into_inner())
- .filter(|component| !component_ids_set.contains(&component.self_id()))
- .collect(),
- )
- .expect("Not supposed to return Err since the entity is removed");
+ self.graph
+ .get_node_by_id_mut(remove_edge_id)
+ .expect("Remove edge archetype should exist")
+ .archetype_mut()
+ .push_entity(entity);
- Some(())
+ self.entity_archetype_lookup
+ .insert(entity_uid, remove_edge_id);
+
+ Ok(())
}
- fn populate_matching_archetype_lookup_entries(
- &mut self,
- comp_ids_set: &HashSet<Uid>,
- archetype_index: usize,
- )
+ pub fn create_imaginary_archetypes(&mut self)
{
- let mut archetype_lookup = self.archetype_lookup.borrow_mut();
-
- for (_, lookup_entry) in archetype_lookup.iter_mut() {
- if &lookup_entry.component_ids == comp_ids_set {
+ for imaginary_archetype in self.imaginary_archetypes.get_mut().drain(..) {
+ if self.graph.contains_archetype(imaginary_archetype.id) {
continue;
}
- // There shouldn't be duplicate archetype indices in the lookup entry
- if lookup_entry.archetype_indices.contains(&archetype_index) {
- continue;
- }
-
- if lookup_entry.component_ids.is_subset(comp_ids_set) {
- lookup_entry.archetype_indices.push(archetype_index);
- }
+ self.graph
+ .create_node(imaginary_archetype.id, &imaginary_archetype.component_ids);
}
}
- fn get_or_create_archetype(
- &mut self,
- archetype_id: ArchetypeId,
- comp_ids_set: &HashSet<Uid>,
- components: &[Box<dyn Component>],
- ) -> usize
+ fn find_all_archetype_with_comps(
+ &self,
+ search_terms: &ArchetypeSearchTerms<'_>,
+ ) -> Vec<ArchetypeId>
{
- let mut archetype_lookup = self.archetype_lookup.borrow_mut();
+ let Some(mut search_iter) =
+ self.graph.dfs_archetype_add_edges(ArchetypeId::new_empty())
+ else {
+ // If the root archetype doesn't exist, no other archetype can exist either
+ //
+ // TODO: The above comment is not true. Cases where imaginary archetypes have
+ // been created should be handled as well
+ return Vec::new();
+ };
+
+ let mut found = Vec::<ArchetypeId>::new();
+
+ while let Some(node_id) = search_iter.streaming_next() {
+ let ArchetypeAddEdgeDfsIterResult::AddEdge {
+ add_edge_archetype_id: node_id,
+ add_edge_component_id,
+ } = node_id
+ else {
+ continue;
+ };
- let lookup_entry = archetype_lookup.entry(archetype_id).or_insert_with(|| {
- ArchetypeLookupEntry {
- component_ids: comp_ids_set.clone(),
- archetype_indices: Vec::with_capacity(1),
+ if search_terms.excluded_contains(add_edge_component_id) {
+ search_iter.pop();
+ continue;
+ }
+
+ let node = self
+ .graph
+ .get_node_by_id(node_id)
+ .expect("Graph node found through DFS doesn't exist");
+
+ if node.archetype().component_cnt() < search_terms.required_components.len() {
+ continue;
+ }
+
+ if !search_terms.archetype_contains_all_required(node.archetype()) {
+ continue;
}
- });
- if lookup_entry.archetype_indices.is_empty() {
- self.archetypes.push(Archetype::new(
- components.iter().map(|component| component.self_id()),
- ));
+ found.push(node.archetype().id());
- lookup_entry
- .archetype_indices
- .push(self.archetypes.len() - 1);
+ search_iter.pop();
}
- // SAFETY: Above, we push a archetype index if archetype_indices is empty so this
- // cannot fail
- unsafe { *lookup_entry.archetype_indices.first().unwrap_unchecked() }
+ found
}
+}
- fn find_archetype_index_with_entity(
+#[cfg(feature = "vizoxide")]
+impl Storage
+{
+ pub fn create_vizoxide_archetype_graph(
&self,
- archetype_id: ArchetypeId,
- entity_uid: Uid,
- ) -> Option<usize>
+ graph_name: impl AsRef<str>,
+ params: VizoxideArchetypeGraphParams,
+ ) -> Result<vizoxide::Graph, vizoxide::GraphvizError>
{
- let archetype_lookup = self.archetype_lookup.borrow_mut();
+ let viz_graph = vizoxide::Graph::builder(graph_name.as_ref())
+ .strict(true)
+ .directed(true)
+ .build()?;
+
+ let mut viz_node_lookup = HashMap::new();
+
+ for node in self.graph.iter_nodes() {
+ let id = node.archetype().id();
+
+ if !viz_node_lookup.contains_key(&id) {
+ let node = self.graph.get_node_by_id(id).unwrap();
+
+ let viz_node = (params.create_node_cb)(
+ node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ viz_graph.create_node(&(params.create_node_name)(
+ node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ )),
+ )
+ .build()?;
+
+ viz_node_lookup.insert(id, viz_node);
+ }
- let archetype_lookup_entry = archetype_lookup.get(&archetype_id)?;
+ for (edge_comp_id, edges) in node.iter_edges() {
+ if let Some(add_edge) = edges.add {
+ if !viz_node_lookup.contains_key(&add_edge) {
+ let viz_node = self.create_vizoxide_archetype_graph_edge_node(
+ &viz_graph,
+ node,
+ add_edge,
+ *edge_comp_id,
+ &params,
+ )?;
+
+ viz_node_lookup.insert(add_edge, viz_node);
+ }
+
+ (params.create_edge_cb)(
+ node.archetype(),
+ *edge_comp_id,
+ VizoxideArchetypeGraphEdgeKind::Add,
+ viz_graph.create_edge(
+ viz_node_lookup.get(&id).unwrap(),
+ viz_node_lookup.get(&add_edge).unwrap(),
+ Some(&format!("Add {}", edge_comp_id.id())),
+ ),
+ )
+ .build()?;
+ }
- // TODO: Also have a hashmap for precise archetype ID -> archetype index lookup.
- // This way is slow
- archetype_lookup_entry
- .archetype_indices
- .iter()
- .find(|archetype_index| {
- let Some(archetype) = self.archetypes.get(**archetype_index) else {
- return false;
- };
+ if let Some(remove_edge) = edges.remove {
+ if !viz_node_lookup.contains_key(&remove_edge) {
+ let viz_node = self.create_vizoxide_archetype_graph_edge_node(
+ &viz_graph,
+ node,
+ remove_edge,
+ *edge_comp_id,
+ &params,
+ )?;
+
+ viz_node_lookup.insert(remove_edge, viz_node);
+ }
+
+ (params.create_edge_cb)(
+ node.archetype(),
+ *edge_comp_id,
+ VizoxideArchetypeGraphEdgeKind::Remove,
+ viz_graph.create_edge(
+ viz_node_lookup.get(&id).unwrap(),
+ viz_node_lookup.get(&remove_edge).unwrap(),
+ Some(&format!("Remove {}", edge_comp_id.id())),
+ ),
+ )
+ .build()?;
+ }
+ }
+ }
- archetype.has_entity(entity_uid)
- })
- .copied()
+ drop(viz_node_lookup);
+
+ Ok(viz_graph)
}
- fn iter_archetypes_by_lookup(&self, archetype_id: ArchetypeId)
- -> ArchetypeRefIter<'_>
+ fn create_vizoxide_archetype_graph_edge_node<'vizoxide_graph>(
+ &self,
+ viz_graph: &'vizoxide_graph vizoxide::Graph,
+ node: &graph::ArchetypeNode,
+ edge_id: ArchetypeId,
+ edge_comp_id: Uid,
+ params: &VizoxideArchetypeGraphParams,
+ ) -> Result<vizoxide::Node<'vizoxide_graph>, vizoxide::GraphvizError>
{
- let archetype_lookup = self.archetype_lookup.borrow();
-
- // The archetype indices have to be cloned to prevent a panic when query
- // iterations are nested. The panic happens because the archetype_lookup RefCell
- // is borrowed and it tries to mutably borrow it
- let archetype_indices = archetype_lookup
- .get(&archetype_id)
- .unwrap()
- .archetype_indices
- .clone();
-
- ArchetypeRefIter {
- indices: archetype_indices.into_iter(),
- archetypes: &self.archetypes,
+ match self.graph.get_node_by_id(edge_id) {
+ Some(edge_node) => (params.create_node_cb)(
+ edge_node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ viz_graph.create_node(&(params.create_node_name)(
+ edge_node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ )),
+ )
+ .build(),
+ None => {
+ let mut comp_ids =
+ node.archetype().component_ids_sorted().collect::<Vec<_>>();
+
+ let insert_index = comp_ids.partition_point(|cid| *cid <= edge_comp_id);
+
+ comp_ids.insert(insert_index, edge_comp_id);
+
+ let imaginary_edge_archetype = Archetype::new(edge_id, comp_ids);
+
+ (params.create_node_cb)(
+ &imaginary_edge_archetype,
+ ArchetypeMetadata { is_imaginary: true },
+ viz_graph.create_node(&(params.create_node_name)(
+ &imaginary_edge_archetype,
+ ArchetypeMetadata { is_imaginary: true },
+ )),
+ )
+ .build()
+ }
}
}
}
-/// Component storage error
-#[derive(Debug, Clone, thiserror::Error)]
-pub enum Error
+#[cfg(feature = "vizoxide")]
+pub struct VizoxideArchetypeGraphParams
{
- #[error("Entity already exists")]
- EntityAlreadyExists(Uid),
+ pub create_node_name: fn(&Archetype, ArchetypeMetadata) -> std::borrow::Cow<'_, str>,
+ pub create_node_cb: for<'storage, 'graph> fn(
+ &'storage Archetype,
+ ArchetypeMetadata,
+ vizoxide::NodeBuilder<'graph>,
+ ) -> vizoxide::NodeBuilder<'graph>,
+ pub create_edge_cb: for<'storage, 'graph> fn(
+ &'storage Archetype,
+ Uid,
+ VizoxideArchetypeGraphEdgeKind,
+ vizoxide::EdgeBuilder<'graph>,
+ ) -> vizoxide::EdgeBuilder<'graph>,
}
-impl TypeName for Storage
+#[cfg(feature = "vizoxide")]
+#[derive(Debug, Clone)]
+pub struct ArchetypeMetadata
{
- fn type_name(&self) -> &'static str
- {
- type_name::<Self>()
- }
+ pub is_imaginary: bool,
}
-#[derive(Debug)]
-struct ArchetypeLookupEntry
+#[cfg(feature = "vizoxide")]
+#[derive(Debug, Clone, Copy)]
+pub enum VizoxideArchetypeGraphEdgeKind
{
- component_ids: HashSet<Uid>,
- archetype_indices: Vec<usize>,
+ Add,
+ Remove,
}
#[derive(Debug)]
-pub struct Archetype
+pub struct ArchetypeRefIter<'storage, 'search_terms>
{
- component_ids: HashMap<Uid, usize>,
- entity_lookup: HashMap<Uid, usize>,
- entities: Vec<ArchetypeEntity>,
+ storage: &'storage Storage,
+ pre_iter: Either<ArrayIter<ArchetypeId, 1>, VecIntoIter<ArchetypeId>>,
+ dfs_iter: ArchetypeAddEdgeDfsIter<'storage>,
+ search_terms: ArchetypeSearchTerms<'search_terms>,
}
-impl Archetype
+impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage, '_>
{
- fn new(component_ids: impl IntoIterator<Item = Uid>) -> Self
- {
- Self {
- component_ids: component_ids
- .into_iter()
- .enumerate()
- .map(|(index, component_id)| (component_id, index))
- .collect(),
- entity_lookup: HashMap::new(),
- entities: Vec::new(),
- }
- }
+ type Item = &'component_storage Archetype;
- pub fn component_ids_is_superset(&self, other_component_ids: &HashSet<Uid>) -> bool
+ fn next(&mut self) -> Option<Self::Item>
{
- if other_component_ids.len() <= self.component_ids.len() {
- other_component_ids
- .iter()
- .all(|v| self.component_ids.contains_key(v))
- } else {
- false
+ if let Some(pre_iter_archetype_id) = self.pre_iter.next() {
+ return Some(
+ self.storage
+ .get_archetype_by_id(pre_iter_archetype_id)
+ .expect("Archetype should exist"),
+ );
}
- }
-
- pub fn get_entity(&self, entity_uid: Uid) -> Option<&ArchetypeEntity>
- {
- let entity_index = *self.entity_lookup.get(&entity_uid)?;
-
- self.entities.get(entity_index)
- }
-
- pub fn entities(&self) -> EntityIter<'_>
- {
- EntityIter { iter: self.entities.iter() }
- }
-
- pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize>
- {
- self.component_ids.get(&component_id).copied()
- }
-
- fn push_entity(
- &mut self,
- entity_uid: Uid,
- components: impl IntoIterator<Item = Box<dyn Component>>,
- )
- {
- self.entities.push(ArchetypeEntity {
- uid: entity_uid,
- components: components.into_iter().map(Into::into).collect(),
- });
- }
-
- pub fn take_entity(&mut self, entity_uid: Uid) -> Option<ArchetypeEntity>
- {
- let entity_index = self.entity_lookup.remove(&entity_uid)?;
-
- let entity = self.entities.remove(entity_index);
- for index in self.entity_lookup.values_mut() {
- if *index > entity_index {
- *index -= 1;
+ let archetype_id = loop {
+ match self.dfs_iter.streaming_find(|res| {
+ matches!(
+ res,
+ ArchetypeAddEdgeDfsIterResult::AddEdge { .. }
+ | ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { .. }
+ )
+ })? {
+ ArchetypeAddEdgeDfsIterResult::AddEdge {
+ add_edge_archetype_id,
+ add_edge_component_id,
+ } => {
+ if self.search_terms.excluded_contains(add_edge_component_id) {
+ self.dfs_iter.pop();
+ continue;
+ }
+
+ break add_edge_archetype_id;
+ }
+ ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound {
+ archetype,
+ add_edge_archetype_id,
+ add_edge_component_id,
+ } => {
+ if self.search_terms.excluded_contains(add_edge_component_id) {
+ continue;
+ }
+
+ let mut add_edge_archetype_comps =
+ archetype.component_ids_sorted().collect::<Vec<_>>();
+
+ add_edge_archetype_comps.insert_at_partition_point_by_key(
+ add_edge_component_id,
+ |comp_id| *comp_id,
+ );
+
+ self.storage.imaginary_archetypes.borrow_mut().push(
+ ImaginaryArchetype {
+ id: add_edge_archetype_id,
+ component_ids: add_edge_archetype_comps.clone(),
+ },
+ );
+
+ let found =
+ self.find_edges_of_imaginary_archetype(&add_edge_archetype_comps);
+
+ self.dfs_iter.push((
+ BorrowedOrOwned::Owned(Archetype::new(
+ add_edge_archetype_id,
+ add_edge_archetype_comps.clone(),
+ )),
+ found.into_iter(),
+ ));
+ }
+ _ => {
+ unreachable!();
+ }
}
- }
+ };
- Some(entity)
- }
-
- fn has_entity(&self, entity_uid: Uid) -> bool
- {
- self.entity_lookup.contains_key(&entity_uid)
+ Some(
+ self.storage
+ .get_archetype_by_id(archetype_id)
+ .expect("Archetype should exist"),
+ )
}
}
-#[derive(Debug)]
-pub struct ArchetypeEntity
+impl ArchetypeRefIter<'_, '_>
{
- uid: Uid,
- components: Vec<EntityComponent>,
-}
-
-impl ArchetypeEntity
-{
- pub fn uid(&self) -> Uid
+ fn find_edges_of_imaginary_archetype(
+ &self,
+ imaginary_archetype_comps: &[Uid],
+ ) -> Vec<(Uid, ArchetypeEdges)>
{
- self.uid
- }
+ self.storage
+ .find_all_archetype_with_comps(&ArchetypeSearchTerms {
+ required_components: imaginary_archetype_comps,
+ excluded_components: &[],
+ })
+ .into_iter()
+ .filter_map(|found_id| {
+ let found_archetype = self.storage.get_archetype_by_id(found_id).unwrap();
- pub fn components(&self) -> &[EntityComponent]
- {
- &self.components
- }
+ if found_archetype.component_cnt() < imaginary_archetype_comps.len() + 1 {
+ return None;
+ }
- pub fn get_component(&self, index: usize) -> Option<&EntityComponent>
- {
- self.components.get(index)
- }
-}
+ let unique_comp_id = found_archetype
+ .component_ids_sorted()
+ .find(|found_archetype_comp_id| {
+ !imaginary_archetype_comps.iter().any(
+ |imaginary_archetype_comp_id| {
+ *imaginary_archetype_comp_id == *found_archetype_comp_id
+ },
+ )
+ })
+ .expect("Oh noooo");
-#[derive(Debug)]
-pub struct ArchetypeRefIter<'component_storage>
-{
- indices: OwnedVecIter<usize>,
- archetypes: &'component_storage [Archetype],
-}
+ let mut add_edge_comp_ids = imaginary_archetype_comps.to_vec();
-impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage>
-{
- type Item = &'component_storage Archetype;
+ add_edge_comp_ids
+ .insert_at_partition_point_by_key(unique_comp_id, |id| *id);
- fn next(&mut self) -> Option<Self::Item>
- {
- let archetype_index = self.indices.next()?;
+ let add_edge = ArchetypeId::new(&add_edge_comp_ids);
- Some(
- self.archetypes
- .get(archetype_index)
- .expect("Archetype index in archetype lookup entry was not found"),
- )
+ Some((
+ unique_comp_id,
+ ArchetypeEdges { add: Some(add_edge), remove: None },
+ ))
+ })
+ .collect::<Vec<_>>()
}
}
-#[derive(Debug)]
-pub struct EntityIter<'archetype>
+#[derive(Debug, thiserror::Error)]
+pub enum Error
{
- iter: SliceIter<'archetype, ArchetypeEntity>,
-}
+ #[error("Entity with ID {0:?} already exists")]
+ EntityAlreadyExists(Uid),
-impl<'archetype> Iterator for EntityIter<'archetype>
-{
- type Item = &'archetype ArchetypeEntity;
+ #[error("Entity with ID {0:?} does not exist")]
+ EntityDoesNotExist(Uid),
- fn next(&mut self) -> Option<Self::Item>
+ #[error("Entity with ID {entity:?} already has component with ID {component:?}")]
+ ComponentAlreadyInEntity
{
- self.iter.next()
- }
+ entity: Uid, component: Uid
+ },
+
+ #[error("Entity with ID {entity:?} does not have component with ID {component:?}")]
+ ComponentNotFoundInEntity
+ {
+ entity: Uid, component: Uid
+ },
}
-fn create_non_opt_component_id_set<Item>(
- component_metadata_iter: impl IntoIterator<Item = Item>,
-) -> HashSet<Uid>
-where
- Item: Borrow<ComponentMetadata>,
+#[derive(Debug)]
+struct ImaginaryArchetype
{
- component_metadata_iter
- .into_iter()
- .filter_map(|item| {
- let component_metadata = item.borrow();
-
- if component_metadata.is_optional == ComponentIsOptional::Yes {
- return None;
- }
-
- Some(component_metadata.id)
- })
- .collect::<HashSet<_>>()
+ id: ArchetypeId,
+ component_ids: Vec<Uid>,
}
#[cfg(test)]
mod tests
{
-
- use ecs_macros::Component;
-
- use super::Storage;
- use crate::archetype::Id as ArchetypeId;
- use crate::component::{
- Component,
- IsOptional as ComponentIsOptional,
- Metadata as ComponentMetadata,
- };
+ use crate::component::storage::archetype::Id as ArchetypeId;
+ use crate::component::storage::Storage;
use crate::uid::{Kind as UidKind, Uid};
- #[derive(Debug, Component)]
- struct HealthPotion
- {
- _hp_restoration: u32,
- }
-
- #[derive(Debug, Component)]
- struct Hookshot
- {
- _range: u32,
- }
-
- #[derive(Debug, Component)]
- struct DekuNut
- {
- _throwing_damage: u32,
- }
-
- #[derive(Debug, Component)]
- struct Bow
- {
- _damage: u32,
- }
-
- #[derive(Debug, Component)]
- struct IronBoots;
-
#[test]
- fn push_entity_works()
+ fn create_entity_works()
{
- let mut component_storage = Storage::default();
-
- component_storage
- .push_entity(
- Uid::new_unique(UidKind::Entity),
- vec![
- Box::new(HealthPotion { _hp_restoration: 12 }),
- Box::new(Hookshot { _range: 50 }),
- ],
- )
- .expect("Expected Ok");
-
- assert_eq!(component_storage.archetypes.len(), 1);
-
- let archetype = component_storage
- .archetypes
- .first()
- .expect("Expected a archetype in archetypes Vec");
-
- assert_eq!(archetype.component_ids.len(), 2);
+ let mut new_storage = Storage::default();
- // One entity
- assert_eq!(archetype.entities.len(), 1);
+ let uid = Uid::new_unique(UidKind::Entity);
- let entity_components = archetype
- .entities
- .first()
- .expect("Expected a entity in archetype");
+ new_storage.create_entity(uid).expect("Expected Ok");
- assert_eq!(entity_components.components.len(), 2);
+ let archetype_node = new_storage
+ .graph
+ .get_node_by_id(ArchetypeId::new_empty())
+ .expect("Archetype for entities with no component doesn't exist");
- assert_eq!(component_storage.archetype_lookup.borrow().len(), 1);
+ assert_eq!(archetype_node.archetype().component_cnt(), 0);
+ assert_eq!(archetype_node.archetype().entity_cnt(), 1);
- let mut components_metadata = [
- ComponentMetadata {
- id: HealthPotion::id(),
- is_optional: ComponentIsOptional::No,
- },
- ComponentMetadata {
- id: Hookshot::id(),
- is_optional: ComponentIsOptional::No,
- },
- ];
-
- components_metadata.sort_by_key(|comp_metadata| comp_metadata.id);
-
- let archetype_lookup = component_storage.archetype_lookup.borrow();
-
- let lookup_entry = archetype_lookup
- .get(&ArchetypeId::from_components_metadata(&components_metadata))
- .expect("Expected entry in archetype lookup map");
-
- let first_archetype_index = lookup_entry
- .archetype_indices
- .first()
- .expect("Expected archetype lookup to contain a archetype reference");
-
- assert_eq!(*first_archetype_index, 0);
+ assert_eq!(
+ new_storage.entity_archetype_lookup.get(&uid).copied(),
+ Some(ArchetypeId::new_empty())
+ );
}
}
diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs
new file mode 100644
index 0000000..bb29701
--- /dev/null
+++ b/ecs/src/component/storage/archetype.rs
@@ -0,0 +1,387 @@
+use std::any::Any;
+use std::array::IntoIter as ArrayIntoIter;
+use std::hash::{DefaultHasher, Hash, Hasher};
+use std::iter::{Enumerate, Filter, Map, RepeatN, Zip};
+use std::option::IntoIter as OptionIntoIter;
+use std::slice::Iter as SliceIter;
+
+use hashbrown::HashMap;
+
+use crate::lock::Lock;
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::{Either, HashMapExt};
+
+#[derive(Debug)]
+pub struct Archetype
+{
+ id: Id,
+ entities: Vec<Entity>,
+ entity_index_lookup: HashMap<Uid, usize>,
+ component_index_lookup: HashMap<Uid, usize>,
+ component_ids: Vec<Uid>,
+}
+
+impl Archetype
+{
+ pub fn new(id: Id, component_ids: impl AsRef<[Uid]>) -> Self
+ {
+ Self {
+ id,
+ entities: Vec::new(),
+ entity_index_lookup: HashMap::new(),
+ component_index_lookup: component_ids
+ .as_ref()
+ .iter()
+ .enumerate()
+ .map(|(index, id)| (*id, index))
+ .collect(),
+ component_ids: component_ids.as_ref().to_vec(),
+ }
+ }
+
+ pub fn id(&self) -> Id
+ {
+ self.id
+ }
+
+ pub fn is_superset(&self, other: &Self) -> bool
+ {
+ self.component_index_lookup
+ .keys_is_superset(&other.component_index_lookup)
+ }
+
+ pub fn is_subset(&self, other: &Self) -> bool
+ {
+ self.component_index_lookup
+ .keys_is_subset(&other.component_index_lookup)
+ }
+
+ pub fn get_entity_by_id(&self, entity_uid: Uid) -> Option<&Entity>
+ {
+ let index = *self.entity_index_lookup.get(&entity_uid)?;
+
+ Some(self.entities.get(index).unwrap_or_else(|| {
+ panic!(
+ "In invalid state! Index of entity with ID {entity_uid:?} is out of bounds"
+ );
+ }))
+ }
+
+ pub fn push_entity(&mut self, entity: Entity)
+ {
+ self.entity_index_lookup
+ .insert(entity.uid, self.entities.len());
+
+ self.entities.push(entity);
+ }
+
+ pub fn remove_entity(&mut self, entity_uid: Uid) -> Option<Entity>
+ {
+ //debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
+
+ let entity_index = self.entity_index_lookup.remove(&entity_uid)?;
+
+ if self.entities.len() == 1 {
+ return Some(self.entities.remove(entity_index));
+ }
+
+ let last_entity_uid = self
+ .entities
+ .last()
+ .expect(concat!(
+ "Invalid state. No entities in archetype but entry was ",
+ "removed successfully from entity index lookup"
+ ))
+ .uid;
+
+ // By using swap_remove, no memory reallocation occurs and only one index in the
+ // entity lookup needs to be updated
+ let removed_entity = self.entities.swap_remove(entity_index);
+
+ self.entity_index_lookup
+ .insert(last_entity_uid, entity_index);
+
+ Some(removed_entity)
+ }
+
+ pub fn entities(&self) -> EntityIter<'_>
+ {
+ EntityIter { iter: self.entities.iter() }
+ }
+
+ pub fn entity_cnt(&self) -> usize
+ {
+ self.entities.len()
+ }
+
+ pub fn component_cnt(&self) -> usize
+ {
+ self.component_index_lookup.len()
+ }
+
+ pub fn get_matching_component_indices(
+ &self,
+ component_id: Uid,
+ ) -> MatchingComponentIter
+ {
+ assert!(
+ component_id.kind() == UidKind::Component
+ || component_id.kind() == UidKind::Pair
+ );
+
+ if component_id.kind() == UidKind::Pair
+ && component_id.target_component() == Uid::wildcard()
+ {
+ return MatchingComponentIter {
+ inner: Either::A(
+ self.component_ids
+ .iter()
+ .enumerate()
+ .zip(std::iter::repeat_n(component_id, self.component_ids.len()))
+ .filter(
+ (|((_, other_comp_id), component_id)| {
+ other_comp_id.kind() == UidKind::Pair
+ && other_comp_id.has_same_relation_as(*component_id)
+ })
+ as MatchingComponentIterFilterFn,
+ )
+ .map(|((index, other_comp_id), _)| (*other_comp_id, index)),
+ ),
+ };
+ }
+
+ MatchingComponentIter {
+ inner: Either::B(
+ [component_id]
+ .into_iter()
+ .zip(self.get_index_for_component(component_id)),
+ ),
+ }
+ }
+
+ pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize>
+ {
+ assert!(
+ component_id.kind() == UidKind::Component
+ || (component_id.kind() == UidKind::Pair
+ && component_id.target_component() != Uid::wildcard())
+ );
+
+ self.component_index_lookup.get(&component_id).copied()
+ }
+
+ pub fn component_ids_unsorted(&self) -> impl Iterator<Item = Uid> + '_
+ {
+ self.component_index_lookup.keys().copied()
+ }
+
+ pub fn component_ids_sorted(&self) -> impl Iterator<Item = Uid> + '_
+ {
+ self.component_ids.iter().copied()
+ }
+
+ pub fn contains_matching_component(&self, component_id: Uid) -> bool
+ {
+ let component_id_kind = component_id.kind();
+
+ debug_assert!(
+ component_id_kind == UidKind::Component || component_id_kind == UidKind::Pair
+ );
+
+ if component_id.kind() == UidKind::Pair
+ && component_id.target_component() == Uid::wildcard()
+ {
+ return self.component_ids.iter().any(|other_comp_id| {
+ other_comp_id.kind() == UidKind::Pair
+ && other_comp_id.has_same_relation_as(component_id)
+ });
+ }
+
+ self.contains_component_with_exact_id(component_id)
+ }
+
+ pub fn contains_component_with_exact_id(&self, component_id: Uid) -> bool
+ {
+ let component_id_kind = component_id.kind();
+
+ debug_assert!(
+ component_id_kind == UidKind::Component
+ || (component_id_kind == UidKind::Pair
+ && component_id.target_component() != Uid::wildcard())
+ );
+
+ self.component_index_lookup.contains_key(&component_id)
+ }
+}
+
+type MatchingComponentIterFilterFn = fn(&((usize, &Uid), Uid)) -> bool;
+
+type MatchingComponentIterMapFn = fn(((usize, &Uid), Uid)) -> (Uid, usize);
+
+type InnerMatchingComponentIterA<'archetype> = Map<
+ Filter<
+ Zip<Enumerate<SliceIter<'archetype, Uid>>, RepeatN<Uid>>,
+ MatchingComponentIterFilterFn,
+ >,
+ MatchingComponentIterMapFn,
+>;
+
+type InnerMatchingComponentIterB = Zip<ArrayIntoIter<Uid, 1>, OptionIntoIter<usize>>;
+
+#[derive(Debug)]
+pub struct MatchingComponentIter<'archetype>
+{
+ inner: Either<InnerMatchingComponentIterA<'archetype>, InnerMatchingComponentIterB>,
+}
+
+impl Iterator for MatchingComponentIter<'_>
+{
+ type Item = (Uid, usize);
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ self.inner.next()
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityIter<'archetype>
+{
+ iter: SliceIter<'archetype, Entity>,
+}
+
+impl<'archetype> Iterator for EntityIter<'archetype>
+{
+ type Item = &'archetype Entity;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ self.iter.next()
+ }
+}
+
+#[derive(Debug)]
+pub struct Entity
+{
+ uid: Uid,
+ components: Vec<EntityComponent>,
+}
+
+impl Entity
+{
+ pub fn new(uid: Uid, components: impl IntoIterator<Item = EntityComponent>) -> Self
+ {
+ Self {
+ uid,
+ components: components.into_iter().collect(),
+ }
+ }
+
+ pub fn uid(&self) -> Uid
+ {
+ self.uid
+ }
+
+ pub fn components(&self) -> &[EntityComponent]
+ {
+ &self.components
+ }
+
+ pub fn remove_component(&mut self, component_id: Uid, archetype: &Archetype)
+ {
+ let index = archetype
+ .get_index_for_component(component_id)
+ .expect("Archetype should contain component");
+
+ self.components.remove(index);
+ }
+
+ pub fn insert_component(
+ &mut self,
+ component_id: Uid,
+ component: EntityComponent,
+ archetype: &Archetype,
+ )
+ {
+ let index = archetype
+ .get_index_for_component(component_id)
+ .expect("Archetype should contain component");
+
+ self.components.insert(index, component);
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityComponent
+{
+ id: Uid,
+ component: Lock<Box<dyn Any>>,
+}
+
+impl EntityComponent
+{
+ pub fn new(
+ component: Box<dyn Any>,
+ component_id: Uid,
+ component_name: &'static str,
+ ) -> Self
+ {
+ Self {
+ id: component_id,
+ component: Lock::new(component, component_name),
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn id(&self) -> Uid
+ {
+ self.id
+ }
+
+ pub fn component(&self) -> &Lock<Box<dyn Any>>
+ {
+ &self.component
+ }
+}
+
+/// Archetype ID.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Id
+{
+ hash: u64,
+}
+
+impl Id
+{
+ pub fn new_empty() -> Self
+ {
+ Self { hash: 0 }
+ }
+
+ pub fn new<'a>(component_ids: impl IntoIterator<Item = &'a Uid>) -> Self
+ {
+ let mut hasher = DefaultHasher::new();
+
+ let mut prev_component_id: Option<Uid> = None;
+
+ let mut component_id_iter = component_ids.into_iter().peekable();
+
+ if component_id_iter.peek().is_none() {
+ return Self::new_empty();
+ }
+
+ for comp_id in component_id_iter {
+ if prev_component_id.is_some_and(|prev_comp_id| *comp_id < prev_comp_id) {
+ panic!(
+ "Cannot create archetype ID from a unsorted component metadata list"
+ );
+ }
+
+ prev_component_id = Some(*comp_id);
+
+ comp_id.hash(&mut hasher);
+ }
+
+ Self { hash: hasher.finish() }
+ }
+}
diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs
new file mode 100644
index 0000000..29fa937
--- /dev/null
+++ b/ecs/src/component/storage/graph.rs
@@ -0,0 +1,432 @@
+use std::vec::IntoIter as VecIntoIter;
+
+use hashbrown::{HashMap, HashSet};
+
+use crate::component::storage::archetype::{Archetype, Id as ArchetypeId};
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::{BorrowedOrOwned, StreamingIterator};
+
+#[derive(Debug, Default)]
+pub struct Graph
+{
+ nodes: Vec<ArchetypeNode>,
+ archetype_index_lookup: HashMap<ArchetypeId, usize>,
+}
+
+impl Graph
+{
+ pub fn create_node(&mut self, id: ArchetypeId, component_ids: &impl AsRef<[Uid]>)
+ {
+ debug_assert!(!self.contains_archetype(id));
+
+ let _ = self.get_or_create_node(id, component_ids);
+ }
+
+ pub fn get_or_create_node(
+ &mut self,
+ id: ArchetypeId,
+ component_ids: &impl AsRef<[Uid]>,
+ ) -> &mut ArchetypeNode
+ {
+ let exists_before = self.archetype_index_lookup.contains_key(&id);
+
+ let index = *self.archetype_index_lookup.entry(id).or_insert_with(|| {
+ self.nodes.push(ArchetypeNode {
+ archetype: Archetype::new(id, component_ids.as_ref()),
+ edges: HashMap::new(),
+ });
+
+ self.nodes.len() - 1
+ });
+
+ if !exists_before {
+ self.create_missing_edges(id);
+ }
+
+ self.nodes
+ .get_mut(index)
+ .expect("Archetype index from lookup is out of bounds")
+ }
+
+ pub fn contains_archetype(&self, id: ArchetypeId) -> bool
+ {
+ self.archetype_index_lookup.contains_key(&id)
+ }
+
+ pub fn get_node_by_id(&self, id: ArchetypeId) -> Option<&ArchetypeNode>
+ {
+ let index = self.archetype_index_lookup.get(&id)?;
+
+ Some(self.nodes.get(*index).unwrap_or_else(|| {
+ panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds")
+ }))
+ }
+
+ pub fn get_node_by_id_mut(&mut self, id: ArchetypeId) -> Option<&mut ArchetypeNode>
+ {
+ let index = self.archetype_index_lookup.get(&id)?;
+
+ Some(self.nodes.get_mut(*index).unwrap_or_else(|| {
+ panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds")
+ }))
+ }
+
+ #[cfg(feature = "vizoxide")]
+ pub fn iter_nodes(&self) -> impl Iterator<Item = &ArchetypeNode>
+ {
+ self.nodes.iter()
+ }
+
+ pub fn dfs_archetype_add_edges(
+ &self,
+ archetype_id: ArchetypeId,
+ ) -> Option<ArchetypeAddEdgeDfsIter>
+ {
+ let node = self.get_node_by_id(archetype_id)?;
+
+ Some(ArchetypeAddEdgeDfsIter {
+ graph: self,
+ stack: vec![(
+ BorrowedOrOwned::Borrowned(node.archetype()),
+ node.edges
+ .iter()
+ .map(|(comp_id, edges)| (*comp_id, edges.clone()))
+ .collect::<Vec<_>>()
+ .into_iter(),
+ )],
+ visited: HashSet::new(),
+ })
+ }
+
+ fn create_missing_edges(&mut self, archetype_id: ArchetypeId)
+ {
+ let archetype_node_index = *self
+ .archetype_index_lookup
+ .get(&archetype_id)
+ .expect("Archetype should exist");
+
+ let (nodes_before, nodes_rest) = self.nodes.split_at_mut(archetype_node_index);
+
+ let ([archetype_node], nodes_after) = nodes_rest.split_at_mut(1) else {
+ unreachable!();
+ };
+
+ for other_archetype_node in nodes_before.iter_mut().chain(nodes_after.iter_mut())
+ {
+ if archetype_node.archetype().component_cnt()
+ > other_archetype_node.archetype().component_cnt()
+ && other_archetype_node
+ .archetype()
+ .is_subset(archetype_node.archetype())
+ {
+ Self::create_missing_subset_node_edges(
+ archetype_node,
+ other_archetype_node,
+ );
+
+ continue;
+ }
+
+ if other_archetype_node
+ .archetype()
+ .is_superset(archetype_node.archetype())
+ {
+ Self::create_missing_superset_node_edges(
+ archetype_node,
+ other_archetype_node,
+ );
+ }
+ }
+ }
+
+ fn create_missing_subset_node_edges(
+ target_node: &mut ArchetypeNode,
+ subset_node: &mut ArchetypeNode,
+ )
+ {
+ let uniq_comp_id = target_node
+ .archetype()
+ .component_ids_sorted()
+ .find(|id| {
+ !subset_node
+ .archetype()
+ .contains_component_with_exact_id(*id)
+ })
+ .unwrap();
+
+ subset_node
+ .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default)
+ .add = Some(subset_node.make_add_edge(uniq_comp_id).0);
+
+ if target_node.archetype().component_cnt()
+ == subset_node.archetype().component_cnt() + 1
+ {
+ target_node
+ .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default)
+ .remove = Some(subset_node.archetype().id());
+ }
+ }
+
+ fn create_missing_superset_node_edges(
+ target_node: &mut ArchetypeNode,
+ superset_node: &mut ArchetypeNode,
+ )
+ {
+ if superset_node.archetype().component_cnt()
+ > target_node.archetype().component_cnt() + 1
+ {
+ let first_unique_comp_id = superset_node
+ .archetype()
+ .component_ids_sorted()
+ .find(|other_archetype_comp_id| {
+ !target_node
+ .archetype()
+ .contains_component_with_exact_id(*other_archetype_comp_id)
+ })
+ .or_else(|| {
+ if target_node.archetype().component_cnt() != 0 {
+ return None;
+ }
+
+ superset_node.archetype().component_ids_sorted().next()
+ })
+ .expect("Not possible");
+
+ target_node
+ .get_or_insert_edges(first_unique_comp_id, ArchetypeEdges::default)
+ .add = Some(target_node.make_add_edge(first_unique_comp_id).0);
+
+ return;
+ }
+
+ if superset_node.archetype().component_cnt()
+ != target_node.archetype().component_cnt() + 1
+ {
+ return;
+ }
+
+ let extra_comp_id = superset_node
+ .archetype()
+ .component_ids_unsorted()
+ .find(|comp_id| {
+ !target_node
+ .archetype()
+ .contains_component_with_exact_id(*comp_id)
+ })
+ .expect("Archetype should contain one extra component ID");
+
+ superset_node
+ .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default)
+ .remove = Some(target_node.archetype().id());
+
+ target_node
+ .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default)
+ .add = Some(superset_node.archetype().id());
+ }
+}
+
+#[derive(Debug)]
+pub struct ArchetypeNode
+{
+ archetype: Archetype,
+ edges: HashMap<Uid, ArchetypeEdges>,
+}
+
+impl ArchetypeNode
+{
+ pub fn archetype(&self) -> &Archetype
+ {
+ &self.archetype
+ }
+
+ pub fn archetype_mut(&mut self) -> &mut Archetype
+ {
+ &mut self.archetype
+ }
+
+ pub fn get_or_insert_edges(
+ &mut self,
+ component_id: Uid,
+ insert_fn: impl FnOnce() -> ArchetypeEdges,
+ ) -> &mut ArchetypeEdges
+ {
+ debug_assert!(matches!(
+ component_id.kind(),
+ UidKind::Component | UidKind::Pair
+ ));
+
+ self.edges.entry(component_id).or_insert_with(insert_fn)
+ }
+
+ #[cfg(feature = "vizoxide")]
+ pub fn iter_edges(&self) -> impl Iterator<Item = (&Uid, &ArchetypeEdges)>
+ {
+ self.edges.iter()
+ }
+
+ pub fn make_add_edge(&self, component_id: Uid) -> (ArchetypeId, Vec<Uid>)
+ {
+ let mut edge_comp_ids = self
+ .archetype()
+ .component_ids_unsorted()
+ .chain([component_id])
+ .collect::<Vec<_>>();
+
+ edge_comp_ids.sort();
+
+ let add_edge_id = ArchetypeId::new(&edge_comp_ids);
+
+ (add_edge_id, edge_comp_ids)
+ }
+
+ pub fn make_remove_edge(&self, component_id: Uid) -> (ArchetypeId, Vec<Uid>)
+ {
+ let mut edge_comp_ids = self
+ .archetype()
+ .component_ids_unsorted()
+ .filter(|id| *id != component_id)
+ .collect::<Vec<_>>();
+
+ edge_comp_ids.sort();
+
+ let remove_edge_id = ArchetypeId::new(&edge_comp_ids);
+
+ (remove_edge_id, edge_comp_ids)
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct ArchetypeEdges
+{
+ pub add: Option<ArchetypeId>,
+ pub remove: Option<ArchetypeId>,
+}
+
+type ArchetypeAddEdgeDfsIterStackElem<'graph> = (
+ BorrowedOrOwned<'graph, Archetype>,
+ VecIntoIter<(Uid, ArchetypeEdges)>,
+);
+
+#[derive(Debug)]
+pub struct ArchetypeAddEdgeDfsIter<'graph>
+{
+ graph: &'graph Graph,
+ stack: Vec<ArchetypeAddEdgeDfsIterStackElem<'graph>>,
+ visited: HashSet<ArchetypeId>,
+}
+
+impl<'graph> ArchetypeAddEdgeDfsIter<'graph>
+{
+ pub fn new(graph: &'graph Graph, start_nodes: &[ArchetypeId]) -> Self
+ {
+ Self {
+ graph,
+ stack: start_nodes
+ .iter()
+ .map(|start_node_id| {
+ let start_node = graph
+ .get_node_by_id(*start_node_id)
+ .expect("Start node does not exist");
+
+ (
+ BorrowedOrOwned::Borrowned(start_node.archetype()),
+ start_node
+ .edges
+ .iter()
+ .map(|(comp_id, edges)| (*comp_id, edges.clone()))
+ .collect::<Vec<_>>()
+ .into_iter(),
+ )
+ })
+ .collect(),
+ visited: start_nodes.iter().copied().collect::<HashSet<_>>(),
+ }
+ }
+
+ pub fn push(
+ &mut self,
+ item: (
+ BorrowedOrOwned<'graph, Archetype>,
+ VecIntoIter<(Uid, ArchetypeEdges)>,
+ ),
+ )
+ {
+ self.stack.push(item);
+ }
+
+ pub fn pop(&mut self)
+ {
+ self.stack.pop();
+ }
+}
+
+impl<'graph> StreamingIterator for ArchetypeAddEdgeDfsIter<'graph>
+{
+ type Item<'a>
+ = ArchetypeAddEdgeDfsIterResult<'graph, 'a>
+ where
+ Self: 'a;
+
+ fn streaming_next(&mut self) -> Option<Self::Item<'_>>
+ {
+ let (_, edges_iter) = self.stack.last_mut()?;
+
+ let Some((component_id, edges)) = edges_iter.next() else {
+ self.stack.pop();
+
+ return Some(ArchetypeAddEdgeDfsIterResult::NoEdgesLeftForArchetype);
+ };
+
+ let Some(add_edge) = edges.add else {
+ return Some(ArchetypeAddEdgeDfsIterResult::NoAddEdge);
+ };
+
+ if self.visited.contains(&add_edge) {
+ return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeAlreadyVisited);
+ }
+
+ self.visited.insert(add_edge);
+
+ let Some(add_edge_archetype) = self.graph.get_node_by_id(add_edge) else {
+ return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound {
+ archetype: &self.stack.last().unwrap().0,
+ add_edge_archetype_id: add_edge,
+ add_edge_component_id: component_id,
+ });
+ };
+
+ self.stack.push((
+ BorrowedOrOwned::Borrowned(add_edge_archetype.archetype()),
+ add_edge_archetype
+ .edges
+ .iter()
+ .map(|(comp_id, edges)| (*comp_id, edges.clone()))
+ .collect::<Vec<_>>()
+ .into_iter(),
+ ));
+
+ Some(ArchetypeAddEdgeDfsIterResult::AddEdge {
+ add_edge_archetype_id: add_edge,
+ add_edge_component_id: component_id,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub enum ArchetypeAddEdgeDfsIterResult<'graph, 'iter>
+{
+ AddEdge
+ {
+ add_edge_archetype_id: ArchetypeId,
+ add_edge_component_id: Uid,
+ },
+ NoEdgesLeftForArchetype,
+ NoAddEdge,
+ AddEdgeAlreadyVisited,
+ AddEdgeArchetypeNotFound
+ {
+ archetype: &'iter BorrowedOrOwned<'graph, Archetype>,
+ add_edge_archetype_id: ArchetypeId,
+ add_edge_component_id: Uid,
+ },
+}
diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs
index 3de9cd5..bab3d61 100644
--- a/ecs/src/entity.rs
+++ b/ecs/src/entity.rs
@@ -1,6 +1,133 @@
+use std::any::type_name;
+
use linkme::distributed_slice;
-use crate::World;
+use crate::component::storage::archetype::{
+ Archetype,
+ Entity as ArchetypeEntity,
+ MatchingComponentIter as ArchetypeMatchingComponentIter,
+};
+use crate::component::{
+ Component,
+ Handle as ComponentHandle,
+ HandleMut as ComponentHandleMut,
+};
+use crate::uid::{Kind as UidKind, Uid};
+use crate::{EntityComponentRef, World};
+
+/// A handle to a entity.
+#[derive(Debug)]
+pub struct Handle<'a>
+{
+ archetype: &'a Archetype,
+ entity: &'a ArchetypeEntity,
+}
+
+impl<'a> Handle<'a>
+{
+ /// Returns the [`Uid`] of this entity.
+ #[inline]
+ #[must_use]
+ pub fn uid(&self) -> Uid
+ {
+ self.entity.uid()
+ }
+
+ /// Returns a reference to the specified component in this entity. `None` is
+ /// returned if the component isn't found in the entity.
+ ///
+ /// # Panics
+ /// Will panic if:
+ /// - The component's ID is not a component ID
+ /// - The component is mutably borrowed elsewhere
+ #[must_use]
+ pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'_, ComponentT>>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let component = self.get_matching_components(ComponentT::id()).next()?;
+
+ Some(
+ ComponentHandle::from_entity_component_ref(component).unwrap_or_else(|err| {
+ panic!(
+ "Taking component {} lock failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
+ )
+ }
+
+ /// Returns a mutable reference to the specified component in this entity. `None` is
+ /// returned if the component isn't found in the entity.
+ ///
+ /// # Panics
+ /// Will panic if:
+ /// - The component's ID is not a component ID
+ /// - The component is borrowed elsewhere
+ #[must_use]
+ pub fn get_mut<ComponentT: Component>(
+ &self,
+ ) -> Option<ComponentHandleMut<'_, ComponentT>>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let component = self.get_matching_components(ComponentT::id()).next()?;
+
+ Some(
+ ComponentHandleMut::from_entity_component_ref(component).unwrap_or_else(
+ |err| {
+ panic!(
+ "Taking component {} lock failed: {err}",
+ type_name::<ComponentT>()
+ );
+ },
+ ),
+ )
+ }
+
+ #[inline]
+ #[must_use]
+ pub fn get_matching_components(&self, component_uid: Uid)
+ -> MatchingComponentIter<'a>
+ {
+ MatchingComponentIter {
+ inner: self.archetype.get_matching_component_indices(component_uid),
+ entity: self.entity,
+ }
+ }
+
+ pub fn component_ids(&self) -> impl Iterator<Item = Uid> + '_
+ {
+ self.archetype.component_ids_sorted()
+ }
+
+ pub(crate) fn new(archetype: &'a Archetype, entity: &'a ArchetypeEntity) -> Self
+ {
+ Self { archetype, entity }
+ }
+}
+
+#[derive(Debug)]
+pub struct MatchingComponentIter<'a>
+{
+ inner: ArchetypeMatchingComponentIter<'a>,
+ entity: &'a ArchetypeEntity,
+}
+
+impl<'a> Iterator for MatchingComponentIter<'a>
+{
+ type Item = EntityComponentRef<'a>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let (matching_component_id, index) = self.inner.next()?;
+
+ Some(EntityComponentRef::new(
+ matching_component_id,
+ self.entity.components().get(index).unwrap(),
+ ))
+ }
+}
#[allow(clippy::module_name_repetitions)]
#[macro_export]
@@ -19,7 +146,7 @@ macro_rules! static_entity {
$crate::entity::CREATE_STATIC_ENTITIES
)]
#[linkme(crate=$crate::private::linkme)]
- static CREATE_STATIC_ENTITY: fn(&$crate::World) = |world| {
+ static CREATE_STATIC_ENTITY: fn(&mut $crate::World) = |world| {
world.create_entity_with_uid($components, *$ident);
};
}
@@ -29,4 +156,4 @@ macro_rules! static_entity {
#[distributed_slice]
#[doc(hidden)]
-pub static CREATE_STATIC_ENTITIES: [fn(&World)];
+pub static CREATE_STATIC_ENTITIES: [fn(&mut World)];
diff --git a/ecs/src/event.rs b/ecs/src/event.rs
index 1a4edcc..9cea807 100644
--- a/ecs/src/event.rs
+++ b/ecs/src/event.rs
@@ -1,96 +1 @@
-use std::any::TypeId;
-use std::fmt::Debug;
-use std::hash::{DefaultHasher, Hash, Hasher};
-
-use seq_macro::seq;
-
pub mod component;
-
-pub trait Event: Debug + 'static
-{
- /// Returns the ID of this event.
- #[must_use]
- fn id() -> Id
- where
- Self: Sized,
- {
- Id::new::<Self, ()>(None)
- }
-}
-
-pub mod start;
-
-/// The ID of a [`Event`].
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct Id
-{
- inner: TypeId,
- extra: Option<u64>,
-}
-
-impl Id
-{
- #[must_use]
- pub fn new<EventT, Extra>(extra: Option<Extra>) -> Self
- where
- EventT: Event,
- Extra: Hash,
- {
- Self {
- inner: TypeId::of::<EventT>(),
- extra: extra.map(|extra| {
- let mut hasher = DefaultHasher::new();
-
- extra.hash(&mut hasher);
-
- hasher.finish()
- }),
- }
- }
-}
-
-pub trait Ids
-{
- type Iter<'a>: Iterator<Item = &'a Id>
- where
- Self: 'a;
-
- fn iter(&self) -> Self::Iter<'_>;
-}
-
-/// A sequence of events.
-pub trait Sequence
-{
- type Ids: Ids;
-
- fn ids() -> Self::Ids;
-}
-
-macro_rules! impl_sequence {
- ($c: tt) => {
- seq!(I in 0..=$c {
- impl Ids for [Id; $c + 1]
- {
- type Iter<'a> = std::slice::Iter<'a, Id>;
-
- fn iter(&self) -> Self::Iter<'_> {
- self.into_iter()
- }
- }
-
- impl<#(Event~I: Event,)*> Sequence for (#(Event~I,)*) {
- type Ids = [Id; $c + 1];
-
- fn ids() -> Self::Ids {
- [#(
- Event~I::id(),
- )*]
- }
- }
- });
- };
-}
-
-seq!(C in 0..=64 {
- impl_sequence!(C);
-});
diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs
index 8b066a7..72a78a3 100644
--- a/ecs/src/event/component.rs
+++ b/ecs/src/event/component.rs
@@ -1,124 +1,12 @@
//! Component events.
-use std::fmt::{Debug, Formatter};
-use std::marker::PhantomData;
+use std::convert::Infallible;
+use std::fmt::Debug;
-use ecs_macros::Component;
-
-use crate::component::Component;
-use crate::uid::Uid;
-use crate::event::{Event, Id};
-use crate::tuple::{ReduceElement as TupleReduceElement, With as TupleWith};
-
-/// Event emitted when:
-/// a) A entity with component `ComponentT` is spawned.
-/// b) A component `ComponentT` is added to a entity.
-pub struct Added<ComponentT>
-where
- ComponentT: Component,
-{
- _pd: PhantomData<ComponentT>,
-}
-
-impl<ComponentT> Debug for Added<ComponentT>
-where
- ComponentT: Component,
-{
- fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
- {
- formatter
- .debug_struct("Added")
- .field("_pd", &self._pd)
- .finish()
- }
-}
-
-impl<ComponentT> Default for Added<ComponentT>
-where
- ComponentT: Component,
-{
- fn default() -> Self
- {
- Self { _pd: PhantomData }
- }
-}
-
-impl<ComponentT> Event for Added<ComponentT>
-where
- ComponentT: Component,
-{
- fn id() -> Id
- where
- Self: Sized,
- {
- Id::new::<Added<ComponentForId>, _>(Some(ComponentT::id()))
- }
-}
-
-/// Event emitted when a `ComponentT` component is removed from a entity.
-pub struct Removed<ComponentT>
-where
- ComponentT: Component,
-{
- _pd: PhantomData<ComponentT>,
-}
-
-impl<ComponentT> Debug for Removed<ComponentT>
-where
- ComponentT: Component,
-{
- fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
- {
- formatter
- .debug_struct("Removed")
- .field("_pd", &self._pd)
- .finish()
- }
-}
-
-impl<ComponentT> Default for Removed<ComponentT>
-where
- ComponentT: Component,
-{
- fn default() -> Self
- {
- Self { _pd: PhantomData }
- }
-}
-
-impl<ComponentT> Event for Removed<ComponentT>
-where
- ComponentT: Component,
-{
- fn id() -> Id
- where
- Self: Sized,
- {
- Id::new::<Removed<ComponentForId>, _>(Some(ComponentT::id()))
- }
-}
-
-#[must_use]
-pub fn create_added_id(component_id: Uid) -> Id
-{
- Id::new::<Added<ComponentForId>, _>(Some(component_id))
-}
-
-#[must_use]
-pub fn create_removed_id(component_id: Uid) -> Id
-{
- Id::new::<Removed<ComponentForId>, _>(Some(component_id))
-}
-
-pub struct TypeTransformComponentsToAddedEvents;
-
-impl<ComponentT: Component, Accumulator>
- TupleReduceElement<Accumulator, TypeTransformComponentsToAddedEvents> for ComponentT
-where
- Accumulator: TupleWith<Added<Self>>,
-{
- type Return = Accumulator::With;
-}
+use crate::Component;
+/// Pair relation for events emitted when:
+/// a) A entity with the target component is spawned.
+/// b) The target component is added to a entity.
#[derive(Debug, Component)]
-struct ComponentForId;
+pub struct Added(Infallible);
diff --git a/ecs/src/event/start.rs b/ecs/src/event/start.rs
deleted file mode 100644
index 248dd50..0000000
--- a/ecs/src/event/start.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use crate::event::Event;
-
-/// Start event.
-#[derive(Debug, Clone, Copy)]
-pub struct Start;
-
-impl Event for Start {}
diff --git a/ecs/src/extension.rs b/ecs/src/extension.rs
index a945d89..42ebef9 100644
--- a/ecs/src/extension.rs
+++ b/ecs/src/extension.rs
@@ -1,9 +1,7 @@
use crate::component::Sequence as ComponentSequence;
-use crate::event::component::TypeTransformComponentsToAddedEvents;
-use crate::event::{Event, Sequence as EventSequence};
use crate::sole::Sole;
use crate::system::System;
-use crate::tuple::Reduce as TupleReduce;
+use crate::uid::Uid;
use crate::{SoleAlreadyExistsError, World};
/// A collection of systems, entities & soles that can be added to a [`World`].
@@ -27,21 +25,19 @@ impl<'world> Collector<'world>
}
/// Adds a system to the [`World`].
- pub fn add_system<'this, EventT, SystemImpl>(
+ pub fn add_system<'this, SystemImpl>(
&'this mut self,
- event: EventT,
+ phase_euid: Uid,
system: impl System<'this, SystemImpl>,
- ) where
- EventT: Event,
+ )
{
- self.world.register_system(event, system);
+ self.world.register_system(phase_euid, system);
}
/// Adds a entity to the [`World`].
pub fn add_entity<Comps>(&mut self, components: Comps)
where
- Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>,
- Comps::Out: EventSequence,
+ Comps: ComponentSequence,
{
self.world.create_entity(components);
}
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
index 78e526f..07b1cba 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -1,65 +1,75 @@
#![deny(clippy::all, clippy::pedantic)]
-use std::any::{type_name, TypeId};
+use std::any::{type_name, Any, TypeId};
use std::cell::RefCell;
-use std::collections::HashMap;
use std::fmt::Debug;
use std::mem::ManuallyDrop;
+use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
+use hashbrown::HashMap;
+
use crate::actions::Action;
+use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComponent;
use crate::component::storage::Storage as ComponentStorage;
-use crate::component::{Component, Sequence as ComponentSequence};
-use crate::entity::CREATE_STATIC_ENTITIES;
-use crate::event::component::{
- create_added_id as create_component_added_event_id,
- create_removed_id as create_component_removed_event_id,
- TypeTransformComponentsToAddedEvents,
+use crate::component::{
+ Component,
+ IntoParts,
+ Parts as ComponentParts,
+ Removals as ComponentRemovals,
+ Sequence as ComponentSequence,
};
-use crate::event::start::Start as StartEvent;
-use crate::event::{Event, Id as EventId, Ids, Sequence as EventSequence};
+use crate::entity::{Handle as EntityHandle, CREATE_STATIC_ENTITIES};
+use crate::event::component::Added as ComponentAddedEvent;
use crate::extension::{Collector as ExtensionCollector, Extension};
-use crate::lock::{Lock, WriteGuard};
-use crate::query::options::Options as QueryOptions;
+use crate::lock::Lock;
+use crate::pair::{ChildOf, DependsOn, Pair};
+use crate::phase::{Phase, START as START_PHASE};
+use crate::query::flexible::Query as FlexibleQuery;
+use crate::query::term::Without;
+use crate::query::{
+ Iter as QueryIter,
+ TermWithFieldTuple as QueryTermWithFieldTuple,
+ TermWithoutFieldTuple as QueryTermWithoutFieldTuple,
+ Terms as QueryTerms,
+ TermsBuilderInterface,
+};
use crate::sole::Sole;
use crate::stats::Stats;
-use crate::system::{System, TypeErased as TypeErasedSystem};
-use crate::tuple::Reduce as TupleReduce;
-use crate::type_name::TypeName;
-use crate::uid::{Kind as UidKind, Uid};
+use crate::system::{System, SystemComponent};
+use crate::uid::{Kind as UidKind, Uid, Wildcard};
pub mod actions;
pub mod component;
pub mod entity;
pub mod event;
pub mod extension;
-pub mod lock;
+pub mod pair;
+pub mod phase;
pub mod query;
-pub mod relationship;
pub mod sole;
pub mod stats;
pub mod system;
pub mod tuple;
-pub mod type_name;
pub mod uid;
+pub mod util;
#[doc(hidden)]
pub mod private;
-mod archetype;
-mod util;
+mod lock;
pub use ecs_macros::{Component, Sole};
pub use crate::query::Query;
-#[derive(Debug, Default)]
+#[derive(Debug)]
pub struct World
{
- systems: Vec<TypeErasedSystem>,
data: WorldData,
stop: AtomicBool,
+ is_first_tick: AtomicBool,
}
impl World
@@ -67,21 +77,25 @@ impl World
#[must_use]
pub fn new() -> Self
{
- let mut world = Self::default();
+ let mut world = Self {
+ data: WorldData::default(),
+ stop: AtomicBool::new(false),
+ is_first_tick: AtomicBool::new(false),
+ };
world.add_sole(Stats::default()).ok();
+ for create_static_entity in CREATE_STATIC_ENTITIES {
+ create_static_entity(&mut world);
+ }
+
world
}
/// Creates a new entity with the given components.
- ///
- /// # Panics
- /// Will panic if mutable internal lock cannot be acquired.
pub fn create_entity<Comps>(&mut self, components: Comps) -> Uid
where
- Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>,
- Comps::Out: EventSequence,
+ Comps: ComponentSequence,
{
let entity_uid = Uid::new_unique(UidKind::Entity);
@@ -90,31 +104,27 @@ impl World
entity_uid
}
- #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
+ #[tracing::instrument(skip_all)]
#[doc(hidden)]
- pub fn create_entity_with_uid<Comps>(&self, components: Comps, entity_uid: Uid)
+ pub fn create_entity_with_uid<Comps>(&mut self, components: Comps, entity_uid: Uid)
where
- Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>,
- Comps::Out: EventSequence,
+ Comps: ComponentSequence,
{
debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
- #[allow(unused_variables)]
- if let Err(err) = self
- .data
- .component_storage
- .write_nonblock()
- .expect("Failed to acquire read-write component storage lock")
- .push_entity(entity_uid, components.into_vec())
- {
- #[cfg(feature = "debug")]
- tracing::error!("Failed to create entity: {err}");
-
+ if let Err(err) = self.data.component_storage.create_entity(entity_uid) {
+ tracing::warn!("Failed to create entity: {err}");
return;
- };
+ }
- for component_added_event_id in <Comps::Out as EventSequence>::ids().iter() {
- self.emit_event_by_id(*component_added_event_id);
+ let added_component_ids = Self::add_entity_components(
+ entity_uid,
+ components.into_parts_array(),
+ &mut self.data.component_storage,
+ );
+
+ for comp_id in added_component_ids {
+ self.emit_event_by_id::<ComponentAddedEvent>(comp_id);
}
}
@@ -129,28 +139,31 @@ impl World
self.data.sole_storage.insert(sole)
}
- pub fn register_system<'this, EventT, SystemImpl>(
+ pub fn register_system<'this, SystemImpl>(
&'this mut self,
- event: EventT,
+ phase_euid: Uid,
system: impl System<'this, SystemImpl>,
- ) where
- EventT: Event,
+ )
{
- self.systems.push(system.into_type_erased());
-
- self.data
- .events
- .entry(EventT::id())
- .or_default()
- .push(self.systems.len() - 1);
+ self.create_entity((
+ SystemComponent { system: system.into_type_erased() },
+ Pair::new::<DependsOn>(phase_euid),
+ ));
+ }
- drop(event);
+ pub fn register_observer_system<'this, SystemImpl>(
+ &'this mut self,
+ system: impl System<'this, SystemImpl>,
+ event: Pair<Uid, Uid>,
+ )
+ {
+ self.create_entity((
+ SystemComponent { system: system.into_type_erased() },
+ event,
+ ));
}
/// Adds a extensions.
- ///
- /// # Panics
- /// Will panic if mutable internal lock cannot be acquired.
pub fn add_extension(&mut self, extension: impl Extension)
{
let extension_collector = ExtensionCollector::new(self);
@@ -158,34 +171,177 @@ impl World
extension.collect(extension_collector);
}
- /// Emits a event, running all systems listening to the event for each compatible
- /// entity.
- ///
- /// # Panics
- /// Will panic if a system has dissapeared.
- pub fn emit<EventT>(&self, event: EventT)
+ pub fn query<FieldTerms, FieldlessTerms>(&self) -> Query<FieldTerms, FieldlessTerms>
where
- EventT: Event,
+ FieldTerms: QueryTermWithFieldTuple,
+ FieldlessTerms: QueryTermWithoutFieldTuple,
{
- self.emit_event_by_id(EventT::id());
-
- drop(event);
+ Query::new(self)
}
- pub fn query<Comps, OptionsT>(&self) -> Query<Comps, OptionsT>
- where
- Comps: ComponentSequence,
- OptionsT: QueryOptions,
+ pub fn flexible_query<const MAX_TERM_CNT: usize>(
+ &self,
+ terms: QueryTerms<MAX_TERM_CNT>,
+ ) -> FlexibleQuery<'_, MAX_TERM_CNT>
{
- Query::new(self)
+ FlexibleQuery::new(self, terms)
}
- /// Peforms the actions that have been queued up using [`Actions`].
- ///
+ /// Performs a single tick.
/// # Panics
- /// Will panic if a mutable internal lock cannot be acquired.
- #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
- pub fn perform_queued_actions(&self)
+ /// Will panic if mutable internal lock cannot be acquired.
+ pub fn step(&mut self) -> StepResult
+ {
+ if self.stop.load(Ordering::Relaxed) {
+ return StepResult::Stop;
+ }
+
+ if self
+ .is_first_tick
+ .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
+ .is_ok()
+ {
+ self.query_and_run_systems(*START_PHASE);
+ }
+
+ self.perform_phases();
+
+ self.data.component_storage.create_imaginary_archetypes();
+
+ let prev_pending_removals = std::mem::take(&mut self.data.pending_removals);
+
+ self.perform_queued_actions();
+
+ self.perform_removals(prev_pending_removals);
+
+ if self.stop.load(Ordering::Relaxed) {
+ return StepResult::Stop;
+ }
+
+ let mut stats_lock = self
+ .data
+ .sole_storage
+ .get::<Stats>()
+ .expect("No stats sole found")
+ .write_nonblock()
+ .expect("Failed to aquire read-write stats sole lock");
+
+ let stats = stats_lock
+ .downcast_mut::<Stats>()
+ .expect("Casting stats sole to Stats type failed");
+
+ stats.current_tick += 1;
+
+ StepResult::Continue
+ }
+
+ /// Starts a loop which calls [`Self::step`] until the world is stopped.
+ pub fn start_loop(&mut self)
+ {
+ while let StepResult::Continue = self.step() {}
+ }
+
+ #[cfg(feature = "vizoxide")]
+ pub fn create_vizoxide_archetype_graph(
+ &self,
+ name: impl AsRef<str>,
+ ) -> Result<vizoxide::Graph, vizoxide::GraphvizError>
+ {
+ use std::borrow::Cow;
+
+ use crate::component::storage::{
+ VizoxideArchetypeGraphEdgeKind,
+ VizoxideArchetypeGraphParams,
+ };
+
+ self.data.component_storage.create_vizoxide_archetype_graph(
+ name,
+ VizoxideArchetypeGraphParams {
+ create_node_name: |archetype, _| {
+ Cow::Owned(format!(
+ "[{}]",
+ archetype
+ .component_ids_sorted()
+ .into_iter()
+ .map(|comp_id| comp_id.to_string())
+ .collect::<Vec<_>>()
+ .join(", ")
+ ))
+ },
+ create_node_cb: |_archetype, archetype_metadata, node_builder| {
+ if archetype_metadata.is_imaginary {
+ return node_builder.attribute("shape", "ellipse");
+ }
+
+ node_builder.attribute("shape", "box")
+ },
+ create_edge_cb: |_, _, edge_kind, edge_builder| {
+ edge_builder.attribute(
+ "color",
+ match edge_kind {
+ VizoxideArchetypeGraphEdgeKind::Add => "green",
+ VizoxideArchetypeGraphEdgeKind::Remove => "red",
+ },
+ )
+ },
+ },
+ )
+ }
+
+ fn query_and_run_systems(&self, phase_euid: Uid)
+ {
+ let system_query = self.flexible_query(
+ QueryTerms::<2>::builder()
+ .with_required([
+ SystemComponent::id(),
+ Pair::new::<DependsOn>(phase_euid).id(),
+ ])
+ .build(),
+ );
+
+ for (system_component,) in
+ QueryIter::<(&SystemComponent,), _>::new(self, system_query.iter())
+ {
+ // SAFETY: The world lives long enough
+ unsafe {
+ system_component.system.run(self);
+ }
+ }
+ }
+
+ fn perform_child_phases(&self, parent_phase_euid: Uid)
+ {
+ let phase_query = self.flexible_query(
+ QueryTerms::<2>::builder()
+ .with_required([
+ Phase::id(),
+ Pair::new::<ChildOf>(parent_phase_euid).id(),
+ ])
+ .build(),
+ );
+
+ for child_phase_entity in &phase_query {
+ self.query_and_run_systems(child_phase_entity.uid());
+ self.perform_child_phases(child_phase_entity.uid());
+ }
+ }
+
+ fn perform_phases(&self)
+ {
+ let phase_query = self.query::<(&Phase,), (Without<Pair<ChildOf, Wildcard>>,)>();
+
+ for (phase_entity_id, _) in phase_query.iter_with_euids() {
+ if phase_entity_id == *START_PHASE {
+ continue;
+ }
+
+ self.query_and_run_systems(phase_entity_id);
+ self.perform_child_phases(phase_entity_id);
+ }
+ }
+
+ #[tracing::instrument(skip_all)]
+ fn perform_queued_actions(&mut self)
{
let mut active_action_queue = match *self.data.action_queue.active_queue.borrow()
{
@@ -202,82 +358,64 @@ impl World
let mut has_swapped_active_queue = false;
+ // TODO: Figure out a good way to handle situations where there are multiple
+ // AddComponents/RemoveComponents actions that affect the same entity.
for action in active_action_queue.drain(..) {
match action {
Action::Spawn(components) => {
- let mut component_storage_lock = self.lock_component_storage_rw();
-
- let component_ids = components
- .iter()
- .map(|component| component.self_id())
- .collect::<Vec<_>>();
+ let new_entity_uid = Uid::new_unique(UidKind::Entity);
- #[allow(unused_variables)]
- if let Err(err) = component_storage_lock
- .push_entity(Uid::new_unique(UidKind::Entity), components)
+ if let Err(err) =
+ self.data.component_storage.create_entity(new_entity_uid)
{
- #[cfg(feature = "debug")]
- tracing::error!("Failed to create entity: {err}");
-
+ tracing::warn!("Failed to create entity: {err}");
continue;
}
- drop(component_storage_lock);
+ let added_component_ids = Self::add_entity_components(
+ new_entity_uid,
+ components,
+ &mut self.data.component_storage,
+ );
if !has_swapped_active_queue {
self.swap_event_queue(&mut has_swapped_active_queue);
}
- for component_id in component_ids {
- self.emit_event_by_id(create_component_added_event_id(
- component_id,
- ));
+ for comp_id in added_component_ids {
+ self.emit_event_by_id::<ComponentAddedEvent>(comp_id);
}
}
+ Action::Despawn(entity_uid) => {
+ Self::schedule_removal(
+ &mut self.data.component_storage,
+ &mut self.data.pending_removals,
+ entity_uid,
+ PendingRemoval::Entity,
+ );
+ }
Action::AddComponents(entity_uid, components) => {
- let mut component_storage_lock = self.lock_component_storage_rw();
-
- let component_ids = components
- .iter()
- .map(|component| component.self_id())
- .collect::<Vec<_>>();
-
- component_storage_lock
- .add_components_to_entity(entity_uid, components);
-
- drop(component_storage_lock);
+ let added_component_ids = Self::add_entity_components(
+ entity_uid,
+ components,
+ &mut self.data.component_storage,
+ );
if !has_swapped_active_queue {
self.swap_event_queue(&mut has_swapped_active_queue);
}
- for component_id in component_ids {
- self.emit_event_by_id(create_component_added_event_id(
- component_id,
- ));
+ for comp_id in added_component_ids {
+ self.emit_event_by_id::<ComponentAddedEvent>(comp_id);
}
}
- Action::RemoveComponents(entity_uid, components_metadata) => {
- let mut component_storage_lock = self.lock_component_storage_rw();
-
- component_storage_lock.remove_components_from_entity(
+ Action::RemoveComponents(entity_uid, component_ids) => {
+ Self::schedule_removal(
+ &mut self.data.component_storage,
+ &mut self.data.pending_removals,
entity_uid,
- components_metadata
- .iter()
- .map(|component_metadata| component_metadata.id),
+ PendingRemoval::Components(component_ids),
);
-
- drop(component_storage_lock);
-
- if !has_swapped_active_queue {
- self.swap_event_queue(&mut has_swapped_active_queue);
- }
-
- for component_metadata in components_metadata {
- self.emit_event_by_id(create_component_removed_event_id(
- component_metadata.id,
- ));
- }
}
Action::Stop => {
self.stop.store(true, Ordering::Relaxed);
@@ -286,60 +424,134 @@ impl World
}
}
- /// A event loop which runs until a stop is issued with [`Flags::stop`]. Before the
- /// loop begins, [`StartEvent`] is emitted.
- ///
- /// # Panics
- /// Will panic if a internal lock cannot be acquired.
- pub fn event_loop<EventSeq: EventSequence>(&self)
+ fn perform_removals(&mut self, removals: Vec<(Uid, PendingRemoval)>)
{
- for create_static_entity in CREATE_STATIC_ENTITIES {
- create_static_entity(self);
+ for (entity_id, removal) in removals {
+ match removal {
+ PendingRemoval::Components(component_ids) => {
+ Self::remove_entity_components(
+ entity_id,
+ component_ids.into_iter().chain([ComponentRemovals::id()]),
+ &mut self.data.component_storage,
+ );
+ }
+ PendingRemoval::Entity => {
+ if let Err(err) = self.data.component_storage.remove_entity(entity_id)
+ {
+ tracing::error!("Failed to remove entity {entity_id}: {err}");
+ }
+ }
+ }
}
+ }
- self.emit(StartEvent);
+ #[tracing::instrument(skip(component_storage, pending_removals))]
+ fn schedule_removal(
+ component_storage: &mut ComponentStorage,
+ pending_removals: &mut Vec<(Uid, PendingRemoval)>,
+ entity_uid: Uid,
+ removal: PendingRemoval,
+ )
+ {
+ let Some(ent_handle) = Self::get_entity(component_storage, entity_uid) else {
+ tracing::warn!("Cannot schedule removal. Entity does not exist");
+ return;
+ };
- let event_seq = EventSeq::ids();
+ let component_ids = match removal {
+ PendingRemoval::Components(ref component_ids) => component_ids,
+ PendingRemoval::Entity => &ent_handle.component_ids().collect::<Vec<_>>(),
+ };
- loop {
- for event_id in event_seq.iter() {
- self.emit_event_by_id(*event_id);
- }
+ let Some(mut component_removals) = ent_handle.get_mut::<ComponentRemovals>()
+ else {
+ Self::add_entity_components(
+ entity_uid,
+ [ComponentRemovals::from_iter(component_ids.iter().copied())
+ .into_parts()],
+ component_storage,
+ );
+
+ pending_removals.push((entity_uid, removal));
+
+ return;
+ };
+
+ component_removals.add_ids(component_ids.iter().copied());
- self.perform_queued_actions();
+ drop(component_removals);
- if self.stop.load(Ordering::Relaxed) {
- break;
+ pending_removals.push((entity_uid, removal));
+ }
+
+ fn add_entity_components(
+ entity_uid: Uid,
+ components: impl IntoIterator<Item = ComponentParts>,
+ component_storage: &mut ComponentStorage,
+ ) -> Vec<Uid>
+ {
+ let component_iter = components.into_iter();
+
+ let mut added_component_ids =
+ Vec::<Uid>::with_capacity(component_iter.size_hint().0);
+
+ for component_parts in component_iter {
+ let comp_id = component_parts.id();
+
+ if let Err(err) = component_storage.add_entity_component(
+ entity_uid,
+ (comp_id, component_parts.name(), component_parts.into_data()),
+ ) {
+ tracing::error!("Failed to add component to entity: {err}");
+ continue;
}
- let mut stats_lock = self
- .data
- .sole_storage
- .get::<Stats>()
- .expect("No stats sole found")
- .write_nonblock()
- .expect("Failed to aquire read-write stats sole lock");
+ added_component_ids.push(comp_id);
+ }
+
+ added_component_ids
+ }
+
+ fn remove_entity_components(
+ entity_uid: Uid,
+ component_ids: impl IntoIterator<Item = Uid>,
+ component_storage: &mut ComponentStorage,
+ ) -> Vec<Uid>
+ {
+ let component_id_iter = component_ids.into_iter();
+
+ let mut removed_component_ids =
+ Vec::<Uid>::with_capacity(component_id_iter.size_hint().0);
- let stats = stats_lock
- .downcast_mut::<Stats>()
- .expect("Casting stats sole to Stats type failed");
+ for component_id in component_id_iter {
+ if let Err(err) =
+ component_storage.remove_entity_component(entity_uid, component_id)
+ {
+ tracing::error!("Failed to remove component to entity: {err}");
+ continue;
+ }
- stats.current_tick += 1;
+ removed_component_ids.push(component_id);
}
+
+ removed_component_ids
}
- fn emit_event_by_id(&self, event_id: EventId)
+ fn emit_event_by_id<Event: Component>(&self, target: Uid)
{
- let Some(system_indices) = self.data.events.get(&event_id) else {
+ if target.kind() == UidKind::Pair {
return;
- };
+ }
- for system_index in system_indices {
- let system = self.systems.get(*system_index).unwrap();
+ let query = self.flexible_query(
+ QueryTerms::<2>::builder()
+ .with_required([SystemComponent::id(), Pair::new::<Event>(target).id()])
+ .build(),
+ );
- // SAFETY: The world lives long enough
+ for (system,) in QueryIter::<(&SystemComponent,), _>::new(self, query.iter()) {
unsafe {
- system.run(self);
+ system.system.run(self);
}
}
}
@@ -356,45 +568,95 @@ impl World
*has_swapped_active_queue = true;
}
- fn lock_component_storage_rw(&self) -> WriteGuard<'_, ComponentStorage>
+ fn get_entity(
+ component_storage: &mut ComponentStorage,
+ entity_uid: Uid,
+ ) -> Option<EntityHandle<'_>>
{
- self.data
- .component_storage
- .write_nonblock()
- .expect("Failed to acquire read-write component storage lock")
+ let archetype = component_storage.get_entity_archetype(entity_uid)?;
+
+ Some(EntityHandle::new(
+ archetype,
+ archetype
+ .get_entity_by_id(entity_uid)
+ .expect("Not possible"),
+ ))
}
}
-#[derive(Debug, Default)]
-pub struct WorldData
+impl Default for World
{
- events: HashMap<EventId, Vec<usize>>,
- component_storage: Arc<Lock<ComponentStorage>>,
- sole_storage: SoleStorage,
- action_queue: Arc<ActionQueue>,
+ fn default() -> Self
+ {
+ Self::new()
+ }
+}
+
+/// The result of calling [`World::step`].
+pub enum StepResult
+{
+ /// Another step can be made.
+ Continue,
+
+ /// The world have been stopped so no step can be made again.
+ Stop,
}
#[derive(Debug)]
-#[non_exhaustive]
-pub struct EntityComponent
+struct WorldData
{
- pub id: Uid,
- pub name: &'static str,
- pub component: Lock<Box<dyn Component>>,
+ component_storage: ComponentStorage,
+ sole_storage: SoleStorage,
+ action_queue: Rc<ActionQueue>,
+ pending_removals: Vec<(Uid, PendingRemoval)>,
}
-impl From<Box<dyn Component>> for EntityComponent
+impl Default for WorldData
{
- fn from(component: Box<dyn Component>) -> Self
+ fn default() -> Self
{
Self {
- id: component.self_id(),
- name: component.type_name(),
- component: Lock::new(component),
+ component_storage: ComponentStorage::default(),
+ sole_storage: SoleStorage::default(),
+ action_queue: Rc::new(ActionQueue::default()),
+ pending_removals: Vec::new(),
}
}
}
+#[derive(Debug)]
+enum PendingRemoval
+{
+ Components(Vec<Uid>),
+ Entity,
+}
+
+#[derive(Debug)]
+pub struct EntityComponentRef<'a>
+{
+ component_id: Uid,
+ component: &'a ArchetypeEntityComponent,
+}
+
+impl<'a> EntityComponentRef<'a>
+{
+ fn component(&self) -> &'a Lock<Box<dyn Any>>
+ {
+ self.component.component()
+ }
+
+ #[must_use]
+ pub fn id(&self) -> Uid
+ {
+ self.component_id
+ }
+
+ fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent) -> Self
+ {
+ Self { component_id, component: comp }
+ }
+}
+
#[derive(Debug, Default, Clone, Copy)]
enum ActiveActionQueue
{
@@ -403,7 +665,7 @@ enum ActiveActionQueue
B,
}
-#[derive(Debug, Default)]
+#[derive(Debug)]
struct ActionQueue
{
queue_a: Lock<Vec<Action>>,
@@ -430,11 +692,15 @@ impl ActionQueue
}
}
-impl TypeName for ActionQueue
+impl Default for ActionQueue
{
- fn type_name(&self) -> &'static str
+ fn default() -> Self
{
- type_name::<Self>()
+ Self {
+ queue_a: Lock::new(Vec::new(), type_name::<Vec<Action>>()),
+ queue_b: Lock::new(Vec::new(), type_name::<Vec<Action>>()),
+ active_queue: RefCell::new(ActiveActionQueue::default()),
+ }
}
}
@@ -479,7 +745,7 @@ impl SoleStorage
self.storage.insert(
sole_type_id,
ManuallyDrop::new(StoredSole {
- sole: Arc::new(Lock::new(Box::new(sole))),
+ sole: Arc::new(Lock::new(Box::new(sole), type_name::<SoleT>())),
drop_last,
}),
);
@@ -496,34 +762,16 @@ impl Drop for SoleStorage
for sole in self.storage.values_mut() {
if sole.drop_last {
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Sole {} pushed to dropping last queue",
- sole.sole.read_nonblock().unwrap().type_name()
- );
-
soles_to_drop_last.push(sole);
continue;
}
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Dropping sole {}",
- sole.sole.read_nonblock().unwrap().type_name()
- );
-
unsafe {
ManuallyDrop::drop(sole);
}
}
for sole in &mut soles_to_drop_last {
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Dropping sole {} last",
- sole.sole.read_nonblock().unwrap().type_name()
- );
-
unsafe {
ManuallyDrop::drop(sole);
}
diff --git a/ecs/src/lock.rs b/ecs/src/lock.rs
index fbc6842..0b36922 100644
--- a/ecs/src/lock.rs
+++ b/ecs/src/lock.rs
@@ -1,24 +1,29 @@
-use std::mem::transmute;
+use std::mem::forget;
use std::ops::{Deref, DerefMut};
-use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError};
-use crate::type_name::TypeName;
+use parking_lot::{
+ MappedRwLockReadGuard,
+ MappedRwLockWriteGuard,
+ RwLock,
+ RwLockReadGuard,
+ RwLockWriteGuard,
+};
-#[derive(Debug, Default)]
+#[derive(Debug)]
pub struct Lock<Value>
-where
- Value: TypeName,
{
inner: RwLock<Value>,
+ value_type_name: &'static str,
}
impl<Value> Lock<Value>
-where
- Value: TypeName,
{
- pub fn new(value: Value) -> Self
+ pub fn new(value: Value, value_type_name: &'static str) -> Self
{
- Self { inner: RwLock::new(value) }
+ Self {
+ inner: RwLock::new(value),
+ value_type_name,
+ }
}
/// Tries to a acquire a handle to the resource with read access.
@@ -27,15 +32,14 @@ where
/// Returns `Err` if unavailable (A mutable handle is hold).
pub fn read_nonblock(&self) -> Result<ReadGuard<Value>, Error>
{
- let guard = self.inner.try_read().or_else(|err| match err {
- TryLockError::WouldBlock => Err(Error::Unavailable),
- TryLockError::Poisoned(poison_err) => Ok(poison_err.into_inner()),
- })?;
+ let guard = self.inner.try_read().ok_or(Error::ReadUnavailable)?;
- #[cfg(feature = "debug")]
- tracing::trace!("Acquired lock to value of type {}", guard.type_name());
+ tracing::trace!("Acquired lock to value of type {}", self.value_type_name);
- Ok(ReadGuard { inner: guard })
+ Ok(ReadGuard {
+ inner: guard,
+ value_type_name: self.value_type_name,
+ })
}
/// Tries to a acquire a handle to the resource with mutable access.
@@ -44,62 +48,59 @@ where
/// Returns `Err` if unavailable (A mutable or immutable handle is hold).
pub fn write_nonblock(&self) -> Result<WriteGuard<Value>, Error>
{
- let guard = self.inner.try_write().or_else(|err| match err {
- TryLockError::WouldBlock => Err(Error::Unavailable),
- TryLockError::Poisoned(poison_err) => Ok(poison_err.into_inner()),
- })?;
+ let guard = self.inner.try_write().ok_or(Error::WriteUnavailable)?;
- #[cfg(feature = "debug")]
tracing::trace!(
"Acquired mutable lock to value of type {}",
- guard.type_name()
+ self.value_type_name
);
- Ok(WriteGuard { inner: guard })
- }
-
- pub fn into_inner(self) -> Value
- {
- self.inner
- .into_inner()
- .unwrap_or_else(PoisonError::into_inner)
+ Ok(WriteGuard {
+ inner: guard,
+ value_type_name: self.value_type_name,
+ })
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error
{
- #[error("Lock is unavailable")]
- Unavailable,
+ #[error("Lock is unavailable for reading")]
+ ReadUnavailable,
+
+ #[error("Lock is unavailable for writing")]
+ WriteUnavailable,
}
#[derive(Debug)]
pub struct ReadGuard<'guard, Value>
-where
- Value: TypeName,
{
inner: RwLockReadGuard<'guard, Value>,
+ value_type_name: &'static str,
}
impl<'guard, Value> ReadGuard<'guard, Value>
-where
- Value: TypeName,
{
- /// Converts the `ReadGuard` to a `ReadGuard` with a possibly longer lifetime.
- ///
- /// # Safety
- /// The returned `ReadGuard` must **NOT** be used for longer than the original
- /// lifetime.
- #[must_use]
- pub unsafe fn upgrade_lifetime<'new>(self) -> ReadGuard<'new, Value>
+ pub fn map<NewValue>(
+ self,
+ func: impl FnOnce(&Value) -> &NewValue,
+ ) -> MappedReadGuard<'guard, NewValue>
{
- unsafe { transmute(self) }
+ let value_type_name = self.value_type_name;
+
+ // The 'inner' field cannot be moved out of ReadGuard in a normal way since
+ // ReadGuard implements Drop
+ let inner = unsafe { std::ptr::read(&self.inner) };
+ forget(self);
+
+ MappedReadGuard {
+ inner: RwLockReadGuard::map(inner, func),
+ value_type_name,
+ }
}
}
-impl<'guard, Value> Deref for ReadGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Deref for ReadGuard<'_, Value>
{
type Target = Value;
@@ -109,28 +110,71 @@ where
}
}
-impl<'guard, Value> Drop for ReadGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Drop for ReadGuard<'_, Value>
{
fn drop(&mut self)
{
- #[cfg(feature = "debug")]
- tracing::trace!("Dropped lock to value of type {}", self.type_name());
+ tracing::trace!("Dropped lock to value of type {}", self.value_type_name);
+ }
+}
+
+#[derive(Debug)]
+pub struct MappedReadGuard<'guard, Value>
+{
+ inner: MappedRwLockReadGuard<'guard, Value>,
+ value_type_name: &'static str,
+}
+
+impl<Value> Deref for MappedReadGuard<'_, Value>
+{
+ type Target = Value;
+
+ fn deref(&self) -> &Self::Target
+ {
+ &self.inner
+ }
+}
+
+impl<Value> Drop for MappedReadGuard<'_, Value>
+{
+ fn drop(&mut self)
+ {
+ tracing::trace!(
+ "Dropped mapped lock to value of type {}",
+ self.value_type_name
+ );
}
}
#[derive(Debug)]
pub struct WriteGuard<'guard, Value>
-where
- Value: TypeName,
{
inner: RwLockWriteGuard<'guard, Value>,
+ value_type_name: &'static str,
+}
+
+impl<'guard, Value> WriteGuard<'guard, Value>
+{
+ pub fn map<NewValue>(
+ self,
+ func: impl FnOnce(&mut Value) -> &mut NewValue,
+ ) -> MappedWriteGuard<'guard, NewValue>
+ {
+ let value_type_name = self.value_type_name;
+
+ // The 'inner' field cannot be moved out of ReadGuard in a normal way since
+ // ReadGuard implements Drop
+ let inner = unsafe { std::ptr::read(&self.inner) };
+ forget(self);
+
+ MappedWriteGuard {
+ inner: RwLockWriteGuard::map(inner, func),
+ value_type_name,
+ }
+ }
}
-impl<'guard, Value> Deref for WriteGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Deref for WriteGuard<'_, Value>
{
type Target = Value;
@@ -140,9 +184,7 @@ where
}
}
-impl<'guard, Value> DerefMut for WriteGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> DerefMut for WriteGuard<'_, Value>
{
fn deref_mut(&mut self) -> &mut Self::Target
{
@@ -150,13 +192,49 @@ where
}
}
-impl<'guard, Value> Drop for WriteGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Drop for WriteGuard<'_, Value>
{
fn drop(&mut self)
{
- #[cfg(feature = "debug")]
- tracing::trace!("Dropped mutable lock to value of type {}", self.type_name());
+ tracing::trace!(
+ "Dropped mutable lock to value of type {}",
+ self.value_type_name
+ );
+ }
+}
+
+#[derive(Debug)]
+pub struct MappedWriteGuard<'guard, Value>
+{
+ inner: MappedRwLockWriteGuard<'guard, Value>,
+ value_type_name: &'static str,
+}
+
+impl<Value> Deref for MappedWriteGuard<'_, Value>
+{
+ type Target = Value;
+
+ fn deref(&self) -> &Self::Target
+ {
+ &self.inner
+ }
+}
+
+impl<Value> DerefMut for MappedWriteGuard<'_, Value>
+{
+ fn deref_mut(&mut self) -> &mut Self::Target
+ {
+ &mut self.inner
+ }
+}
+
+impl<Value> Drop for MappedWriteGuard<'_, Value>
+{
+ fn drop(&mut self)
+ {
+ tracing::trace!(
+ "Dropped mapped mutable lock to value of type {}",
+ self.value_type_name
+ );
}
}
diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs
new file mode 100644
index 0000000..4ff4995
--- /dev/null
+++ b/ecs/src/pair.rs
@@ -0,0 +1,198 @@
+use std::convert::Infallible;
+
+use crate::component::{IntoParts as IntoComponentParts, Parts as ComponentParts};
+use crate::entity::{
+ Handle as EntityHandle,
+ MatchingComponentIter as EntityMatchingComponentIter,
+};
+use crate::query::{
+ TermWithField as QueryTermWithField,
+ TermsBuilder as QueryTermsBuilder,
+ TermsBuilderInterface,
+};
+use crate::uid::{PairParams as UidPairParams, Uid, Wildcard, With as WithUid};
+use crate::{Component, World};
+
+#[derive(Debug)]
+pub struct Pair<RelationElem: Element, TargetElem: Element>
+{
+ relation: RelationElem,
+ target: TargetElem,
+}
+
+impl Pair<Uid, Uid>
+{
+ #[must_use]
+ pub fn new<Relation: WithUid>(target: Uid) -> Self
+ {
+ Self { relation: Relation::uid(), target }
+ }
+
+ #[must_use]
+ pub fn id(&self) -> Uid
+ {
+ Uid::new_pair(&UidPairParams {
+ relation: self.relation,
+ target: self.target,
+ })
+ }
+}
+
+impl IntoComponentParts for Pair<Uid, Uid>
+{
+ fn into_parts(self) -> ComponentParts
+ {
+ ComponentParts::builder().name("Pair").build(self.id(), ())
+ }
+}
+
+impl<Relation, Target> QueryTermWithField for Pair<Relation, Target>
+where
+ Relation: WithUid,
+ Target: WithUid,
+{
+ type Field<'a> = Handle<'a>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with_required([Self::uid()]);
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ let first_matching_comp = entity_handle
+ .get_matching_components(Self::uid())
+ .next()
+ .expect("Not possible");
+
+ Handle {
+ world,
+ pair_uid: first_matching_comp.id(),
+ }
+ }
+}
+
+impl<Relation, Target> WithUid for Pair<Relation, Target>
+where
+ Relation: WithUid,
+ Target: WithUid,
+{
+ fn uid() -> Uid
+ {
+ Uid::new_pair(&UidPairParams {
+ relation: Relation::uid(),
+ target: Target::uid(),
+ })
+ }
+}
+
+impl<Relation> QueryTermWithField for &'static [Pair<Relation, Wildcard>]
+where
+ Relation: WithUid,
+{
+ type Field<'a> = HandleIter<'a>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with_required([Pair::<Relation, Wildcard>::uid()]);
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ HandleIter {
+ inner: entity_handle
+ .get_matching_components(Pair::<Relation, Wildcard>::uid()),
+ world,
+ }
+ }
+}
+
+pub struct Handle<'world>
+{
+ world: &'world World,
+ pair_uid: Uid,
+}
+
+impl Handle<'_>
+{
+ #[must_use]
+ pub fn get_target_entity(&self) -> Option<EntityHandle<'_>>
+ {
+ let archetype = self
+ .world
+ .data
+ .component_storage
+ .get_entity_archetype(self.pair_uid.target_entity())?;
+
+ let Some(archetype_entity) =
+ archetype.get_entity_by_id(self.pair_uid.target_entity())
+ else {
+ unreachable!();
+ };
+
+ Some(EntityHandle::new(archetype, archetype_entity))
+ }
+}
+
+pub struct HandleIter<'a>
+{
+ inner: EntityMatchingComponentIter<'a>,
+ world: &'a World,
+}
+
+impl<'a> Iterator for HandleIter<'a>
+{
+ type Item = Handle<'a>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let matching_comp = self.inner.next()?;
+
+ Some(Handle {
+ world: self.world,
+ pair_uid: matching_comp.id(),
+ })
+ }
+}
+
+pub trait Element: sealed::Sealed
+{
+ type Value;
+}
+
+impl Element for Uid
+{
+ type Value = Uid;
+}
+
+impl sealed::Sealed for Uid {}
+
+impl<WithUidT: WithUid> Element for WithUidT
+{
+ type Value = Infallible;
+}
+
+impl<WithUidT: WithUid> sealed::Sealed for WithUidT {}
+
+/// Relation denoting a dependency to another entity
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct DependsOn;
+
+/// Relation denoting being the child of another entity.
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct ChildOf;
+
+mod sealed
+{
+ pub trait Sealed {}
+}
diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs
new file mode 100644
index 0000000..9f47fb8
--- /dev/null
+++ b/ecs/src/phase.rs
@@ -0,0 +1,13 @@
+use ecs_macros::Component;
+
+use crate::pair::{ChildOf, Pair};
+use crate::static_entity;
+
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct Phase;
+
+static_entity!(pub START, (Phase,));
+
+static_entity!(pub PRE_UPDATE, (Phase,));
+
+static_entity!(pub UPDATE, (Phase, Pair::new::<ChildOf>(*PRE_UPDATE)));
diff --git a/ecs/src/query.rs b/ecs/src/query.rs
index f3318bd..ccb7add 100644
--- a/ecs/src/query.rs
+++ b/ecs/src/query.rs
@@ -1,115 +1,91 @@
-use std::iter::{Filter, Flatten, Map};
+use std::any::type_name;
use std::marker::PhantomData;
-use crate::component::storage::{
- Archetype,
- ArchetypeEntity,
- ArchetypeRefIter,
- EntityIter,
- Storage as ComponentStorage,
-};
+use seq_macro::seq;
+
use crate::component::{
Component,
- Metadata as ComponentMetadata,
- Sequence as ComponentSequence,
-};
-use crate::lock::{ReadGuard, WriteGuard};
-use crate::query::options::Options;
-use crate::system::{
- NoInitParamFlag as NoInitSystemParamFlag,
- Param as SystemParam,
- System,
+ Handle as ComponentHandle,
+ HandleMut as ComponentHandleMut,
};
-use crate::uid::Uid;
-use crate::{EntityComponent, World};
+use crate::entity::Handle as EntityHandle;
+use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery};
+use crate::system::{Param as SystemParam, System};
+use crate::uid::{Kind as UidKind, Uid, With as WithUid};
+use crate::util::array_vec::ArrayVec;
+use crate::util::Array;
+use crate::World;
-pub mod options;
+pub mod flexible;
+pub mod term;
#[derive(Debug)]
-pub struct Query<'world, Comps, OptionsT = ()>
+pub struct Query<'world, FieldTerms, FieldlessTerms = ()>
where
- Comps: ComponentSequence,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
world: &'world World,
- component_storage: ReadGuard<'world, ComponentStorage>,
- _pd: PhantomData<(Comps, OptionsT)>,
+ // A term tuple type can have a maximum of 17 elements
+ inner: FlexibleQuery<'world, 17>,
+ _pd: PhantomData<(FieldTerms, FieldlessTerms)>,
}
-impl<'world, Comps, OptionsT> Query<'world, Comps, OptionsT>
+impl<'world, FieldTerms, FieldlessTerms> Query<'world, FieldTerms, FieldlessTerms>
where
- Comps: ComponentSequence,
- OptionsT: Options,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
- /// Iterates over the entities matching this query.
+ /// Iterates over the entities matching this query, the iterator item being the entity
+ /// components.
#[must_use]
- pub fn iter_mut(
- &'world self,
- ) -> ComponentIterMut<'world, Comps, QueryEntityIter<'world>>
+ pub fn iter<'query>(
+ &'query self,
+ ) -> Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>
{
- #[cfg(feature = "debug")]
- tracing::debug!("Searching for {}", std::any::type_name::<Comps>());
+ tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>());
- #[allow(clippy::map_flatten)]
- ComponentIterMut {
+ Iter {
world: self.world,
- entities: self
- .component_storage
- .find_entities(Comps::metadata())
- .map(Archetype::entities as ComponentIterMapFn)
- .flatten()
- .filter(|entity| OptionsT::entity_filter(entity.components())),
+ iter: self.inner.iter(),
comps_pd: PhantomData,
}
}
- /// Iterates over the entities matching this query.
+ /// Iterates over the entities matching this query, the iterator item being the entity
+ /// [`Uid`] and the matching entity components.
#[must_use]
- pub fn iter(&'world self) -> ComponentIter<'world, Comps, QueryEntityIter<'world>>
+ pub fn iter_with_euids<'query>(
+ &'query self,
+ ) -> ComponentAndEuidIter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>
{
- #[cfg(feature = "debug")]
- tracing::debug!("Searching for {}", std::any::type_name::<Comps>());
+ tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>());
- #[allow(clippy::map_flatten)]
- ComponentIter {
+ ComponentAndEuidIter {
world: self.world,
- entities: self
- .component_storage
- .find_entities(Comps::metadata())
- .map(Archetype::entities as ComponentIterMapFn)
- .flatten()
- .filter(|entity| OptionsT::entity_filter(entity.components())),
+ iter: self.inner.iter(),
comps_pd: PhantomData,
}
}
- /// Iterates over the entities matching this query and has the provided extra
- /// component.
+ /// Iterates over the entities matching this query using the iterator returned by
+ /// `func`.
+ ///
+ /// This function exists so that a custom [`EntityHandle`] iterator can be given to
+ /// [`Iter`] without giving the user access to a reference to the [`World`].
#[must_use]
- pub fn iter_with_extra_comps(
- &'world self,
- extra_components: impl IntoIterator<Item = ComponentMetadata>,
- ) -> ComponentIter<'world, Comps, QueryEntityIter<'world>>
+ pub fn iter_with<'query, OutIter>(
+ &'query self,
+ func: impl FnOnce(FlexibleQueryIter<'query>) -> OutIter,
+ ) -> Iter<'query, 'world, FieldTerms, OutIter>
+ where
+ OutIter: Iterator<Item = EntityHandle<'query>>,
{
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Searching for {} + extra components",
- std::any::type_name::<Comps>()
- );
-
- #[allow(clippy::map_flatten)]
- ComponentIter {
+ tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>());
+
+ Iter {
world: self.world,
- entities: self
- .component_storage
- .find_entities(
- Comps::metadata()
- .into_iter()
- .chain(extra_components)
- .collect::<Vec<_>>(),
- )
- .map(Archetype::entities as ComponentIterMapFn)
- .flatten()
- .filter(|entity| OptionsT::entity_filter(entity.components())),
+ iter: func(self.inner.iter()),
comps_pd: PhantomData,
}
}
@@ -118,51 +94,45 @@ where
#[must_use]
pub fn get_entity_uid(&self, entity_index: usize) -> Option<Uid>
{
- Some(
- self.component_storage
- .find_entities(Comps::metadata())
- .flat_map(|archetype| archetype.entities())
- .filter(|entity| OptionsT::entity_filter(entity.components()))
- .nth(entity_index)?
- .uid(),
- )
+ Some(self.inner.iter().nth(entity_index)?.uid())
}
pub(crate) fn new(world: &'world World) -> Self
{
+ let mut terms_builder = Terms::builder();
+
+ FieldTerms::apply_terms_to_builder(&mut terms_builder);
+ FieldlessTerms::apply_terms_to_builder(&mut terms_builder);
+
Self {
world,
- component_storage: world
- .data
- .component_storage
- .read_nonblock()
- .expect("Failed to acquire read-only component storage lock"),
+ inner: world.flexible_query(terms_builder.build()),
_pd: PhantomData,
}
}
}
-impl<'world, Comps, OptionsT> IntoIterator for &'world Query<'world, Comps, OptionsT>
+impl<'query, 'world, FieldTerms, FieldlessTerms> IntoIterator
+ for &'query Query<'world, FieldTerms, FieldlessTerms>
where
- Comps: ComponentSequence,
- OptionsT: Options,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
- type IntoIter = ComponentIterMut<'world, Comps, QueryEntityIter<'world>>;
- type Item = Comps::MutRefs<'world>;
+ type IntoIter = Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>;
+ type Item = FieldTerms::Fields<'query>;
fn into_iter(self) -> Self::IntoIter
{
- self.iter_mut()
+ self.iter()
}
}
-unsafe impl<'world, Comps, OptionsT> SystemParam<'world>
- for Query<'world, Comps, OptionsT>
+impl<'world, FieldTerms, FieldlessTerms> SystemParam<'world>
+ for Query<'world, FieldTerms, FieldlessTerms>
where
- Comps: ComponentSequence,
- OptionsT: Options,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
- type Flags = NoInitSystemParamFlag;
type Input = ();
fn initialize<SystemImpl>(
@@ -181,93 +151,429 @@ where
}
}
-type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> EntityIter<'a>;
+#[derive(Debug)]
+pub struct Terms<const MAX_TERM_CNT: usize>
+{
+ required_components: ArrayVec<Uid, MAX_TERM_CNT>,
+ excluded_components: ArrayVec<Uid, MAX_TERM_CNT>,
+}
-type ComponentIterFilterFn = for<'a, 'b> fn(&'a &'b ArchetypeEntity) -> bool;
+impl<const MAX_TERM_CNT: usize> Terms<MAX_TERM_CNT>
+{
+ pub fn builder() -> TermsBuilder<MAX_TERM_CNT>
+ {
+ TermsBuilder::default()
+ }
+}
-type QueryEntityIter<'world> = Filter<
- Flatten<Map<ArchetypeRefIter<'world>, ComponentIterMapFn>>,
- ComponentIterFilterFn,
->;
+#[derive(Debug, Default)]
+#[must_use]
+pub struct TermsBuilder<const MAX_TERM_CNT: usize>
+{
+ required_components: ArrayVec<Uid, MAX_TERM_CNT>,
+ excluded_components: ArrayVec<Uid, MAX_TERM_CNT>,
+}
-pub struct ComponentIterMut<'world, Comps, EntityIter>
-where
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+#[allow(clippy::return_self_not_must_use)]
+pub trait TermsBuilderInterface
{
- world: &'world World,
- entities: EntityIter,
- comps_pd: PhantomData<Comps>,
+ fn with<WithUidT: WithUid>(self) -> Self;
+
+ fn without<WithUidT: WithUid>(self) -> Self;
+
+ fn with_required(self, ids: impl Array<Uid>) -> Self;
+
+ fn without_ids(self, ids: impl Array<Uid>) -> Self;
}
-impl<'world, Comps, EntityIter> Iterator for ComponentIterMut<'world, Comps, EntityIter>
-where
- Comps: ComponentSequence + 'world,
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+macro_rules! impl_terms_builder {
+ ($($impl_content: tt)*) => {
+ impl<const MAX_TERM_CNT: usize>
+ TermsBuilderInterface for TermsBuilder<MAX_TERM_CNT>
+ {
+ $($impl_content)*
+ }
+
+ impl<const MAX_TERM_CNT: usize>
+ TermsBuilderInterface for &mut TermsBuilder<MAX_TERM_CNT>
+ {
+ $($impl_content)*
+ }
+ };
+}
+
+impl_terms_builder! {
+ #[allow(unused_mut)]
+ fn with<WithUidT: WithUid>(mut self) -> Self
+ {
+ let insert_index = self.required_components
+ .partition_point(|id| *id <= WithUidT::uid());
+
+ self.required_components
+ .insert(insert_index, WithUidT::uid());
+
+ self
+ }
+
+ #[allow(unused_mut)]
+ fn without<WithUidT: WithUid>(mut self) -> Self
+ {
+ let insert_index = self.excluded_components
+ .partition_point(|id| *id <= WithUidT::uid());
+
+ self.excluded_components
+ .insert(insert_index, WithUidT::uid());
+
+ self
+ }
+
+ #[allow(unused_mut)]
+ fn with_required(mut self, mut ids: impl Array<Uid>) -> Self
+ {
+ if !ids.as_ref().is_sorted() {
+ ids.as_mut().sort();
+ }
+
+ if self.required_components.is_empty() {
+ self.required_components.extend(ids);
+ return self;
+ }
+
+ let mut id_iter = ids.into_iter();
+
+ while let Some(id) = id_iter.next() {
+ let insert_index = self.required_components
+ .partition_point(|other_id| *other_id <= id);
+
+ if insert_index == self.required_components.len() {
+ self.required_components.extend([id].into_iter().chain(id_iter));
+
+ return self;
+ }
+
+ self.required_components
+ .insert(insert_index, id);
+
+ }
+
+ self
+ }
+
+ #[allow(unused_mut)]
+ fn without_ids(mut self, mut ids: impl Array<Uid>) -> Self
+ {
+ if !ids.as_ref().is_sorted() {
+ ids.as_mut().sort();
+ }
+
+ if self.excluded_components.is_empty() {
+ self.excluded_components.extend(ids);
+ return self;
+ }
+
+ let mut id_iter = ids.into_iter();
+
+ while let Some(id) = id_iter.next() {
+ let insert_index = self.excluded_components
+ .partition_point(|other_id| *other_id <= id);
+
+ if insert_index == self.excluded_components.len() {
+ self.excluded_components.extend([id].into_iter().chain(id_iter));
+
+ return self;
+ }
+
+ self.excluded_components
+ .insert(insert_index, id);
+
+ }
+
+ self
+ }
+}
+
+impl<const MAX_TERM_CNT: usize> TermsBuilder<MAX_TERM_CNT>
{
- type Item = Comps::MutRefs<'world>;
+ #[must_use]
+ pub fn build(self) -> Terms<MAX_TERM_CNT>
+ {
+ debug_assert!(self.required_components.is_sorted());
+ debug_assert!(self.excluded_components.is_sorted());
- fn next(&mut self) -> Option<Self::Item>
+ Terms {
+ required_components: self.required_components,
+ excluded_components: self.excluded_components,
+ }
+ }
+}
+
+pub trait TermWithoutField
+{
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+}
+
+pub trait TermWithField
+{
+ type Field<'a>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ world: &'world World,
+ ) -> Self::Field<'world>;
+}
+
+impl<ComponentT: Component> TermWithField for &ComponentT
+{
+ type Field<'a> = ComponentHandle<'a, ComponentT>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
{
- Some(Comps::from_components_mut(
- self.entities.next()?.components().iter(),
- self.world,
- lock_component_rw,
- ))
+ terms_builder.with::<ComponentT>();
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ _world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let Some(component) = entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()
+ else {
+ panic!(
+ concat!(
+ "Component {} was not found in entity {}. There ",
+ "is most likely a bug in the entity querying"
+ ),
+ type_name::<ComponentT>(),
+ entity_handle.uid()
+ );
+ };
+
+ Self::Field::from_entity_component_ref(component).unwrap_or_else(|err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
+ );
+ })
}
}
-fn lock_component_rw(
- entity_component: &EntityComponent,
-) -> WriteGuard<'_, Box<dyn Component>>
+impl<ComponentT: Component> TermWithField for &mut ComponentT
{
- entity_component
- .component
- .write_nonblock()
- .unwrap_or_else(|_| {
+ type Field<'a> = ComponentHandleMut<'a, ComponentT>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with::<ComponentT>();
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ _world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let Some(component) = entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()
+ else {
+ panic!(
+ concat!(
+ "Component {} was not found in entity {}. There ",
+ "is most likely a bug in the entity querying"
+ ),
+ type_name::<ComponentT>(),
+ entity_handle.uid()
+ );
+ };
+
+ Self::Field::from_entity_component_ref(component).unwrap_or_else(|err| {
panic!(
- "Failed to acquire read-write lock to component {}",
- entity_component.name
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
);
})
+ }
+}
+
+pub trait TermWithoutFieldTuple
+{
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+}
+
+pub trait TermWithFieldTuple
+{
+ type Fields<'component>;
+
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+
+ fn get_fields<'component>(
+ entity_handle: &EntityHandle<'component>,
+ world: &'component World,
+ ) -> Self::Fields<'component>;
+}
+
+pub struct Iter<'query, 'world, FieldTerms, EntityHandleIter>
+where
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+{
+ world: &'world World,
+ iter: EntityHandleIter,
+ comps_pd: PhantomData<FieldTerms>,
}
-pub struct ComponentIter<'world, Comps, EntityIter>
+impl<'query, 'world, FieldTerms, EntityHandleIter>
+ Iter<'query, 'world, FieldTerms, EntityHandleIter>
where
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+ 'world: 'query,
+{
+ /// Creates a new iterator from the given entity handle iterator.
+ ///
+ /// # Important
+ /// All of the yielded entities of the entity handle iterator should match the
+ /// terms `Terms`. The [`Self::next`] function will panic if it encounters a
+ /// entity that does not match the terms `Terms`.
+ pub fn new(world: &'world World, iter: EntityHandleIter) -> Self
+ {
+ Self { world, iter, comps_pd: PhantomData }
+ }
+}
+
+impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator
+ for Iter<'query, 'world, FieldTerms, EntityHandleIter>
+where
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+ 'world: 'query,
+{
+ type Item = FieldTerms::Fields<'query>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let entity_handle = self.iter.next()?;
+
+ Some(FieldTerms::get_fields(&entity_handle, self.world))
+ }
+}
+
+pub struct ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter>
+where
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
{
world: &'world World,
- entities: EntityIter,
- comps_pd: PhantomData<Comps>,
+ iter: EntityHandleIter,
+ comps_pd: PhantomData<FieldTerms>,
}
-impl<'world, Comps, EntityIter> Iterator for ComponentIter<'world, Comps, EntityIter>
+impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator
+ for ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter>
where
- Comps: ComponentSequence + 'world,
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+ 'world: 'query,
{
- type Item = Comps::Refs<'world>;
+ type Item = (Uid, FieldTerms::Fields<'query>);
fn next(&mut self) -> Option<Self::Item>
{
- Some(Comps::from_components(
- self.entities.next()?.components().iter(),
- self.world,
- lock_component_ro,
+ let entity_handle = self.iter.next()?;
+
+ Some((
+ entity_handle.uid(),
+ FieldTerms::get_fields(&entity_handle, self.world),
))
}
}
-fn lock_component_ro(
- entity_component: &EntityComponent,
-) -> ReadGuard<'_, Box<dyn Component>>
+macro_rules! impl_term_sequence {
+ ($c: tt) => {
+ seq!(I in 0..=$c {
+ impl<#(Term~I: TermWithoutField,)*> TermWithoutFieldTuple for (#(Term~I,)*)
+ {
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>
+ )
+ {
+ #(
+ Term~I::apply_to_terms_builder(terms_builder);
+ )*
+ }
+ }
+
+ impl<#(Term~I: TermWithField,)*> TermWithFieldTuple for (#(Term~I,)*)
+ {
+ type Fields<'component> = (#(Term~I::Field<'component>,)*);
+
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>
+ )
+ {
+ #(
+ Term~I::apply_to_terms_builder(terms_builder);
+ )*
+ }
+
+ fn get_fields<'component>(
+ entity_handle: &EntityHandle<'component>,
+ world: &'component World,
+ ) -> Self::Fields<'component>
+ {
+ (#(Term~I::get_field(entity_handle, world),)*)
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..=16 {
+ impl_term_sequence!(C);
+});
+
+impl TermWithoutFieldTuple for ()
+{
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+}
+
+impl TermWithFieldTuple for ()
{
- entity_component
- .component
- .read_nonblock()
- .unwrap_or_else(|_| {
- panic!(
- "Failed to acquire read-write lock to component {}",
- entity_component.name
- );
- })
+ type Fields<'component> = ();
+
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+
+ fn get_fields<'component>(
+ _entity_handle: &EntityHandle<'_>,
+ _world: &'component World,
+ ) -> Self::Fields<'component>
+ {
+ }
}
diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs
new file mode 100644
index 0000000..add30b0
--- /dev/null
+++ b/ecs/src/query/flexible.rs
@@ -0,0 +1,84 @@
+//! Low-level querying.
+use std::iter::{repeat_n, FlatMap, RepeatN, Zip};
+
+use crate::component::storage::archetype::{Archetype, EntityIter};
+use crate::component::storage::{ArchetypeRefIter, ArchetypeSearchTerms};
+use crate::entity::Handle as EntityHandle;
+use crate::query::Terms;
+use crate::World;
+
+/// Low-level entity query structure.
+#[derive(Debug)]
+pub struct Query<'world, const MAX_TERM_CNT: usize>
+{
+ world: &'world World,
+ terms: Terms<MAX_TERM_CNT>,
+}
+
+impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
+{
+ /// Iterates over the entities matching this query.
+ #[must_use]
+ pub fn iter(&self) -> Iter<'_>
+ {
+ Iter {
+ iter: self
+ .world
+ .data
+ .component_storage
+ .search_archetypes(ArchetypeSearchTerms {
+ required_components: &self.terms.required_components,
+ excluded_components: &self.terms.excluded_components,
+ })
+ .flat_map(
+ (|archetype| {
+ repeat_n(archetype, archetype.entity_cnt())
+ .zip(archetype.entities())
+ }) as ComponentIterMapFn,
+ ),
+ }
+ }
+
+ pub(crate) fn new(world: &'world World, terms: Terms<MAX_TERM_CNT>) -> Self
+ {
+ Self { world, terms }
+ }
+}
+
+impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_TERM_CNT>
+{
+ type IntoIter = Iter<'query>;
+ type Item = EntityHandle<'query>;
+
+ fn into_iter(self) -> Self::IntoIter
+ {
+ self.iter()
+ }
+}
+
+pub struct Iter<'query>
+{
+ iter: QueryEntityIter<'query>,
+}
+
+impl<'query> Iterator for Iter<'query>
+{
+ type Item = EntityHandle<'query>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let (archetype, entity) = self.iter.next()?;
+
+ Some(EntityHandle::new(archetype, entity))
+ }
+}
+
+type ComponentIterMapFnOutput<'a> = Zip<RepeatN<&'a Archetype>, EntityIter<'a>>;
+
+type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> ComponentIterMapFnOutput<'a>;
+
+type QueryEntityIter<'query> = FlatMap<
+ ArchetypeRefIter<'query, 'query>,
+ ComponentIterMapFnOutput<'query>,
+ ComponentIterMapFn,
+>;
diff --git a/ecs/src/query/options.rs b/ecs/src/query/options.rs
deleted file mode 100644
index bbbe0a8..0000000
--- a/ecs/src/query/options.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use std::collections::HashSet;
-use std::marker::PhantomData;
-
-use crate::component::Component;
-use crate::EntityComponent;
-
-/// Query options.
-pub trait Options
-{
- fn entity_filter<'component>(
- components: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool;
-}
-
-impl Options for ()
-{
- fn entity_filter<'component>(
- _: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool
- {
- true
- }
-}
-
-pub struct With<ComponentT>
-where
- ComponentT: Component,
-{
- _pd: PhantomData<ComponentT>,
-}
-
-impl<ComponentT> Options for With<ComponentT>
-where
- ComponentT: Component,
-{
- fn entity_filter<'component>(
- components: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool
- {
- let ids_set = components
- .into_iter()
- .map(|component| component.id)
- .collect::<HashSet<_>>();
-
- ids_set.contains(&ComponentT::id())
- }
-}
-
-pub struct Not<OptionsT>
-where
- OptionsT: Options,
-{
- _pd: PhantomData<OptionsT>,
-}
-
-impl<OptionsT> Options for Not<OptionsT>
-where
- OptionsT: Options,
-{
- fn entity_filter<'component>(
- components: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool
- {
- !OptionsT::entity_filter(components)
- }
-}
diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs
new file mode 100644
index 0000000..9c772da
--- /dev/null
+++ b/ecs/src/query/term.rs
@@ -0,0 +1,115 @@
+use std::any::type_name;
+use std::marker::PhantomData;
+
+use crate::component::{
+ Component,
+ Handle as ComponentHandle,
+ HandleMut as ComponentHandleMut,
+};
+use crate::query::{
+ TermWithField,
+ TermWithoutField,
+ TermsBuilder,
+ TermsBuilderInterface,
+};
+use crate::uid::With as WithUid;
+
+pub struct With<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ _pd: PhantomData<WithUidT>,
+}
+
+impl<WithUidT> TermWithoutField for With<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with::<WithUidT>();
+ }
+}
+
+pub struct Without<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ _pd: PhantomData<WithUidT>,
+}
+
+impl<WithUidT> TermWithoutField for Without<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.without::<WithUidT>();
+ }
+}
+
+impl<ComponentT: Component> TermWithField for Option<&ComponentT>
+{
+ type Field<'a> = Option<ComponentHandle<'a, ComponentT>>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+
+ fn get_field<'world>(
+ entity_handle: &crate::entity::Handle<'world>,
+ _world: &'world crate::World,
+ ) -> Self::Field<'world>
+ {
+ Some(
+ ComponentHandle::<'world, ComponentT>::from_entity_component_ref(
+ entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()?,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
+ )
+ }
+}
+
+impl<ComponentT: Component> TermWithField for Option<&mut ComponentT>
+{
+ type Field<'a> = Option<ComponentHandleMut<'a, ComponentT>>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+
+ fn get_field<'world>(
+ entity_handle: &crate::entity::Handle<'world>,
+ _world: &'world crate::World,
+ ) -> Self::Field<'world>
+ {
+ Some(
+ ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref(
+ entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()?,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
+ )
+ }
+}
diff --git a/ecs/src/relationship.rs b/ecs/src/relationship.rs
deleted file mode 100644
index 0ebd9c2..0000000
--- a/ecs/src/relationship.rs
+++ /dev/null
@@ -1,418 +0,0 @@
-use std::any::type_name;
-use std::marker::PhantomData;
-
-use ecs_macros::Component;
-
-use crate::component::storage::Storage as ComponentStorage;
-use crate::component::{
- Component,
- FromOptional as FromOptionalComponent,
- FromOptionalMut as FromOptionalMutComponent,
-};
-use crate::lock::ReadGuard;
-use crate::system::{ComponentRef, ComponentRefMut};
-use crate::uid::{Kind as UidKind, Uid};
-use crate::World;
-
-/// A relationship to one or more targets.
-#[derive(Debug, Component)]
-#[component(
- ref_type = Relation<'component, Kind, ComponentT>,
- ref_mut_type = RelationMut<'component, Kind, ComponentT>,
-)]
-pub struct Relationship<Kind, ComponentT: Component>
-where
- Kind: 'static,
-{
- entity_uid: SingleOrMultiple<Uid>,
- _pd: PhantomData<(Kind, ComponentT)>,
-}
-
-impl<Kind, ComponentT> Relationship<Kind, ComponentT>
-where
- ComponentT: Component,
-{
- /// Creates a new `Relationship` with a single target.
- #[must_use]
- pub fn new(entity_uid: Uid) -> Self
- {
- debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
- Self {
- entity_uid: SingleOrMultiple::Single(entity_uid),
- _pd: PhantomData,
- }
- }
-
- /// Creates a new `Relationship` with multiple targets.
- #[must_use]
- pub fn new_multiple(entity_uids: impl IntoIterator<Item = Uid>) -> Self
- {
- let uids = entity_uids.into_iter().collect::<Vec<_>>();
-
- for euid in &uids {
- debug_assert_eq!(euid.kind(), UidKind::Entity);
- }
-
- Self {
- entity_uid: SingleOrMultiple::Multiple(uids),
- _pd: PhantomData,
- }
- }
-}
-
-pub struct RelationMut<'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- component_storage_lock: ReadGuard<'static, ComponentStorage>,
- relationship_comp: ComponentRefMut<'rel_comp, Relationship<Kind, ComponentT>>,
-}
-
-impl<'rel_comp, Kind, ComponentT> FromOptionalMutComponent<'rel_comp>
- for RelationMut<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- fn from_optional_mut_component(
- optional_component: Option<
- crate::lock::WriteGuard<'rel_comp, Box<dyn Component>>,
- >,
- world: &'rel_comp World,
- ) -> Self
- {
- let relationship_comp =
- ComponentRefMut::<Relationship<Kind, ComponentT>>::from_optional_mut_component(
- optional_component,
- world,
- );
-
- let component_storage_lock = world
- .data
- .component_storage
- .read_nonblock()
- .expect("Failed to aquire read-only component storage lock");
-
- Self {
- relationship_comp,
- // SAFETY: The component lock is not used for longer than the original
- // lifetime
- component_storage_lock: unsafe { component_storage_lock.upgrade_lifetime() },
- }
- }
-}
-
-impl<'rel_comp, Kind, ComponentT> RelationMut<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- /// Returns the component of the target at the specified index.
- ///
- /// # Panics
- /// Will panic if the entity does not exist in the archetype it belongs to. This
- /// should hopefully never happend.
- #[must_use]
- pub fn get(&self, index: usize) -> Option<ComponentRefMut<'_, ComponentT>>
- {
- let target = self.get_target(index)?;
-
- let archetype = self.component_storage_lock.get_entity_archetype(*target)?;
-
- let entity = archetype
- .get_entity(*target)
- .expect("Target entity is gone from archetype");
-
- let component_index = archetype.get_index_for_component(ComponentT::id())?;
-
- let component = ComponentRefMut::new(
- entity
- .get_component(component_index)?
- .component
- .write_nonblock()
- .unwrap_or_else(|_| {
- panic!(
- "Failed to aquire read-write lock of component {}",
- type_name::<ComponentT>()
- )
- }),
- );
-
- Some(component)
- }
-
- /// Returns a reference to the target at the specified index.
- #[must_use]
- pub fn get_target(&self, index: usize) -> Option<&Uid>
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.get(index),
- SingleOrMultiple::Single(_) => None,
- }
- }
-
- /// Returns a mutable reference to the target at the specified index.
- #[must_use]
- pub fn get_target_mut(&mut self, index: usize) -> Option<&mut Uid>
- {
- match &mut self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.get_mut(index),
- SingleOrMultiple::Single(_) => None,
- }
- }
-
- /// Adds a target to the relationship.
- pub fn add_target(&mut self, entity_uid: Uid)
- {
- debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
- match &mut self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(prev_entity_uid) => {
- self.relationship_comp.entity_uid =
- SingleOrMultiple::Multiple(vec![*prev_entity_uid, entity_uid]);
- }
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.push(entity_uid),
- }
- }
-
- /// Removes a target to the relationship, returning it.
- pub fn remove_target(&mut self, index: usize) -> Option<Uid>
- {
- match &mut self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) => {
- let prev_entity_uid = *entity_uid;
-
- self.relationship_comp.entity_uid =
- SingleOrMultiple::Multiple(Vec::new());
-
- Some(prev_entity_uid)
- }
- SingleOrMultiple::Multiple(entity_uids) => {
- if index >= entity_uids.len() {
- return None;
- }
-
- Some(entity_uids.remove(index))
- }
- }
- }
-
- #[must_use]
- pub fn target_count(&self) -> usize
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(_) => 1,
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.len(),
- }
- }
-
- /// Returns a iterator of the components of the targets of this relationship.
- #[must_use]
- pub fn iter(&self) -> TargetComponentIterMut<'_, 'rel_comp, Kind, ComponentT>
- {
- TargetComponentIterMut { relation: self, index: 0 }
- }
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> IntoIterator
- for &'relationship RelationMut<'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- ComponentT: Component,
-{
- type IntoIter = TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>;
- type Item = ComponentRefMut<'rel_comp, ComponentT>;
-
- fn into_iter(self) -> Self::IntoIter
- {
- self.iter()
- }
-}
-
-/// Iterator of the components of the targets of a relationship.
-pub struct TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- relation: &'relationship RelationMut<'rel_comp, Kind, ComponentT>,
- index: usize,
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> Iterator
- for TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- Kind: 'static,
- ComponentT: Component,
-{
- type Item = ComponentRefMut<'rel_comp, ComponentT>;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- let index = self.index;
-
- self.index += 1;
-
- self.relation.get(index)
- }
-}
-
-#[derive(Debug)]
-enum SingleOrMultiple<Value>
-{
- Single(Value),
- Multiple(Vec<Value>),
-}
-
-pub struct Relation<'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- component_storage_lock: ReadGuard<'static, ComponentStorage>,
- relationship_comp: ComponentRef<'rel_comp, Relationship<Kind, ComponentT>>,
-}
-
-impl<'rel_comp, Kind, ComponentT> FromOptionalComponent<'rel_comp>
- for Relation<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- fn from_optional_component(
- optional_component: Option<ReadGuard<'rel_comp, Box<dyn Component>>>,
- world: &'rel_comp World,
- ) -> Self
- {
- let relationship_comp =
- ComponentRef::<Relationship<Kind, ComponentT>>::from_optional_component(
- optional_component,
- world,
- );
-
- let component_storage_lock = world
- .data
- .component_storage
- .read_nonblock()
- .expect("Failed to aquire read-only component storage lock");
-
- Self {
- relationship_comp,
- // SAFETY: The component lock is not used for longer than the original
- // lifetime
- component_storage_lock: unsafe { component_storage_lock.upgrade_lifetime() },
- }
- }
-}
-
-impl<'rel_comp, Kind, ComponentT> Relation<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- /// Returns the component of the target at the specified index.
- ///
- /// # Panics
- /// Will panic if the entity does not exist in the archetype it belongs to. This
- /// should hopefully never happend.
- #[must_use]
- pub fn get(&self, index: usize) -> Option<ComponentRef<'_, ComponentT>>
- {
- let target = self.get_target(index)?;
-
- let archetype = self.component_storage_lock.get_entity_archetype(*target)?;
-
- let entity = archetype
- .get_entity(*target)
- .expect("Target entity is gone from archetype");
-
- let component_index = archetype.get_index_for_component(ComponentT::id())?;
-
- let component = ComponentRef::new(
- entity
- .get_component(component_index)?
- .component
- .read_nonblock()
- .unwrap_or_else(|_| {
- panic!(
- "Failed to aquire read-write lock of component {}",
- type_name::<ComponentT>()
- )
- }),
- );
-
- Some(component)
- }
-
- /// Returns a reference to the target at the specified index.
- #[must_use]
- pub fn get_target(&self, index: usize) -> Option<&Uid>
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.get(index),
- SingleOrMultiple::Single(_) => None,
- }
- }
-
- #[must_use]
- pub fn target_count(&self) -> usize
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(_) => 1,
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.len(),
- }
- }
-
- /// Returns a iterator of the components of the targets of this relationship.
- #[must_use]
- pub fn iter(&self) -> TargetComponentIter<'_, 'rel_comp, Kind, ComponentT>
- {
- TargetComponentIter { relation: self, index: 0 }
- }
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> IntoIterator
- for &'relationship Relation<'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- ComponentT: Component,
-{
- type IntoIter = TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>;
- type Item = ComponentRef<'rel_comp, ComponentT>;
-
- fn into_iter(self) -> Self::IntoIter
- {
- self.iter()
- }
-}
-
-/// Iterator of the components of the targets of a relationship.
-pub struct TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- relation: &'relationship Relation<'rel_comp, Kind, ComponentT>,
- index: usize,
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> Iterator
- for TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- Kind: 'static,
- ComponentT: Component,
-{
- type Item = ComponentRef<'rel_comp, ComponentT>;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- let index = self.index;
-
- self.index += 1;
-
- self.relation.get(index)
- }
-}
diff --git a/ecs/src/sole.rs b/ecs/src/sole.rs
index 084a06b..1cce419 100644
--- a/ecs/src/sole.rs
+++ b/ecs/src/sole.rs
@@ -5,12 +5,11 @@ use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Weak};
use crate::lock::{Lock, WriteGuard};
-use crate::system::{NoInitParamFlag, Param as SystemParam, System};
-use crate::type_name::TypeName;
+use crate::system::{Param as SystemParam, System};
use crate::World;
/// A type which has a single instance and is shared globally.
-pub trait Sole: Any + TypeName
+pub trait Sole: Any
{
fn drop_last(&self) -> bool;
@@ -40,14 +39,6 @@ impl Debug for dyn Sole
}
}
-impl TypeName for Box<dyn Sole>
-{
- fn type_name(&self) -> &'static str
- {
- self.as_ref().type_name()
- }
-}
-
/// Holds a reference to a globally shared singleton value.
#[derive(Debug)]
pub struct Single<'world, SoleT: Sole>
@@ -88,11 +79,10 @@ where
}
}
-unsafe impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT>
+impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT>
where
SoleT: Sole,
{
- type Flags = NoInitParamFlag;
type Input = ();
fn initialize<SystemImpl>(
@@ -115,7 +105,7 @@ where
}
}
-impl<'world, SoleT> Deref for Single<'world, SoleT>
+impl<SoleT> Deref for Single<'_, SoleT>
where
SoleT: Sole,
{
@@ -127,7 +117,7 @@ where
}
}
-impl<'world, SoleT> DerefMut for Single<'world, SoleT>
+impl<SoleT> DerefMut for Single<'_, SoleT>
where
SoleT: Sole,
{
@@ -174,7 +164,7 @@ where
_pd: PhantomData<&'weak_ref SoleT>,
}
-impl<'weak_ref, SoleT> SingleRef<'weak_ref, SoleT>
+impl<SoleT> SingleRef<'_, SoleT>
where
SoleT: Sole,
{
diff --git a/ecs/src/system.rs b/ecs/src/system.rs
index 046d25b..603c015 100644
--- a/ecs/src/system.rs
+++ b/ecs/src/system.rs
@@ -1,19 +1,12 @@
-use std::any::{type_name, Any};
+use std::any::Any;
use std::convert::Infallible;
use std::fmt::Debug;
-use std::marker::PhantomData;
-use std::ops::{Deref, DerefMut};
-use std::panic::{RefUnwindSafe, UnwindSafe};
+use ecs_macros::Component;
use seq_macro::seq;
-use crate::component::{
- Component,
- FromOptional as FromOptionalComponent,
- FromOptionalMut as FromOptionalMutComponent,
-};
-use crate::lock::{ReadGuard, WriteGuard};
-use crate::tuple::{ReduceElement as TupleReduceElement, With as TupleWith};
+use crate::component::{Component, HandleMut as ComponentHandleMut};
+use crate::tuple::{ReduceElement as TupleReduceElement, Tuple};
use crate::World;
pub mod stateful;
@@ -33,7 +26,7 @@ pub trait System<'world, Impl>: 'static
fn get_local_component_mut<LocalComponent: Component>(
&self,
- ) -> Option<ComponentRefMut<LocalComponent>>;
+ ) -> Option<ComponentHandleMut<LocalComponent>>;
fn set_local_component<LocalComponent: Component>(
&mut self,
@@ -47,8 +40,8 @@ macro_rules! impl_system {
impl<'world, Func, #(TParam~I,)*> System<'world, fn(#(TParam~I,)*)>
for Func
where
- Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static,
- #(TParam~I: Param<'world, Flags = NoInitParamFlag>,)*
+ Func: Fn(#(TParam~I,)*) + Copy + 'static,
+ #(TParam~I: Param<'world, Input = ()>,)*
{
type Input = Infallible;
@@ -92,7 +85,7 @@ macro_rules! impl_system {
fn get_local_component_mut<LocalComponent: Component>(
&self,
- ) -> Option<ComponentRefMut<LocalComponent>>
+ ) -> Option<ComponentHandleMut<LocalComponent>>
{
panic!("System does not have any local components");
}
@@ -121,7 +114,7 @@ pub trait Into<Impl>
pub struct TypeErased
{
- data: Box<dyn Any + RefUnwindSafe + UnwindSafe>,
+ data: Box<dyn Any>,
run: Box<TypeErasedRunFn>,
}
@@ -149,13 +142,12 @@ impl Debug for TypeErased
}
/// Function in [`TypeErased`] used to run the system.
-type TypeErasedRunFn = dyn Fn(&dyn Any, &World) + RefUnwindSafe + UnwindSafe;
+type TypeErasedRunFn = dyn Fn(&dyn Any, &World);
/// A parameter to a [`System`].
-pub unsafe trait Param<'world>
+pub trait Param<'world>
{
type Input;
- type Flags;
fn initialize<SystemImpl>(
system: &mut impl System<'world, SystemImpl>,
@@ -168,8 +160,6 @@ pub unsafe trait Param<'world>
) -> Self;
}
-pub struct NoInitParamFlag {}
-
/// A type which can be used as input to a [`System`].
pub trait Input: 'static {}
@@ -179,9 +169,9 @@ pub struct ParamWithInputFilter;
impl<InputT: Input, Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter>
for InputT
where
- Accumulator: TupleWith<Self>,
+ Accumulator: Tuple,
{
- type Return = Accumulator::With;
+ type Return = Accumulator::WithElementAtEnd<Self>;
}
impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for ()
@@ -189,128 +179,8 @@ impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for ()
type Return = Accumulator;
}
-#[derive(Debug)]
-pub struct ComponentRefMut<'a, ComponentT: Component>
-{
- inner: WriteGuard<'a, Box<dyn Component>>,
- _ph: PhantomData<ComponentT>,
-}
-
-impl<'a, ComponentT: Component> ComponentRefMut<'a, ComponentT>
-{
- pub(crate) fn new(inner: WriteGuard<'a, Box<dyn Component>>) -> Self
- {
- Self { inner, _ph: PhantomData }
- }
-}
-
-impl<'component, ComponentT: Component> FromOptionalMutComponent<'component>
- for ComponentRefMut<'component, ComponentT>
+#[derive(Debug, Component)]
+pub(crate) struct SystemComponent
{
- fn from_optional_mut_component(
- inner: Option<WriteGuard<'component, Box<dyn Component>>>,
- _world: &'component World,
- ) -> Self
- {
- Self {
- inner: inner.unwrap_or_else(|| {
- panic!(
- "Component {} was not found in entity",
- type_name::<ComponentT>()
- );
- }),
- _ph: PhantomData,
- }
- }
-}
-
-impl<'comp, ComponentT> FromOptionalMutComponent<'comp>
- for Option<ComponentRefMut<'comp, ComponentT>>
-where
- ComponentT: Component,
-{
- fn from_optional_mut_component(
- optional_component: Option<WriteGuard<'comp, Box<dyn Component>>>,
- _world: &'comp World,
- ) -> Self
- {
- optional_component.map(|component| ComponentRefMut::new(component))
- }
-}
-
-impl<'a, ComponentT: Component> Deref for ComponentRefMut<'a, ComponentT>
-{
- type Target = ComponentT;
-
- fn deref(&self) -> &Self::Target
- {
- self.inner.downcast_ref().unwrap()
- }
-}
-
-impl<'a, ComponentT: Component> DerefMut for ComponentRefMut<'a, ComponentT>
-{
- fn deref_mut(&mut self) -> &mut Self::Target
- {
- self.inner.downcast_mut().unwrap()
- }
-}
-
-#[derive(Debug)]
-pub struct ComponentRef<'a, ComponentT: Component>
-{
- inner: ReadGuard<'a, Box<dyn Component>>,
- _ph: PhantomData<ComponentT>,
-}
-
-impl<'a, ComponentT: Component> ComponentRef<'a, ComponentT>
-{
- pub(crate) fn new(inner: ReadGuard<'a, Box<dyn Component>>) -> Self
- {
- Self { inner, _ph: PhantomData }
- }
-}
-
-impl<'component, ComponentT: Component> FromOptionalComponent<'component>
- for ComponentRef<'component, ComponentT>
-{
- fn from_optional_component(
- inner: Option<ReadGuard<'component, Box<dyn Component>>>,
- _world: &'component World,
- ) -> Self
- {
- Self {
- inner: inner.unwrap_or_else(|| {
- panic!(
- "Component {} was not found in entity",
- type_name::<ComponentT>()
- );
- }),
- _ph: PhantomData,
- }
- }
-}
-
-impl<'comp, ComponentT> FromOptionalComponent<'comp>
- for Option<ComponentRef<'comp, ComponentT>>
-where
- ComponentT: Component,
-{
- fn from_optional_component(
- optional_component: Option<ReadGuard<'comp, Box<dyn Component>>>,
- _world: &'comp World,
- ) -> Self
- {
- optional_component.map(|component| ComponentRef::new(component))
- }
-}
-
-impl<'a, ComponentT: Component> Deref for ComponentRef<'a, ComponentT>
-{
- type Target = ComponentT;
-
- fn deref(&self) -> &Self::Target
- {
- self.inner.downcast_ref().unwrap()
- }
+ pub(crate) system: TypeErased,
}
diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs
index 536e6ed..54f6979 100644
--- a/ecs/src/system/stateful.rs
+++ b/ecs/src/system/stateful.rs
@@ -1,13 +1,12 @@
use std::any::{type_name, Any, TypeId};
-use std::collections::HashMap;
use std::panic::{RefUnwindSafe, UnwindSafe};
+use hashbrown::HashMap;
use seq_macro::seq;
-use crate::component::Component;
+use crate::component::{Component, HandleMut as ComponentHandleMut};
use crate::lock::Lock;
use crate::system::{
- ComponentRefMut,
Into as IntoSystem,
Param,
ParamWithInputFilter,
@@ -15,10 +14,9 @@ use crate::system::{
TypeErased,
};
use crate::tuple::{
- IntoInOptions as TupleIntoInOptions,
Reduce as TupleReduce,
- TakeOptionElementResult as TupleTakeOptionElementResult,
- WithOptionElements as TupleWithOptionElements,
+ Tuple,
+ WithAllElemLtStatic as TupleWithAllElemLtStatic,
};
use crate::uid::Uid;
use crate::World;
@@ -27,7 +25,7 @@ use crate::World;
pub struct Stateful<Func>
{
func: Func,
- local_components: HashMap<Uid, Lock<Box<dyn Component>>>,
+ local_components: HashMap<Uid, Lock<Box<dyn Any>>>,
}
macro_rules! impl_system {
@@ -39,9 +37,10 @@ macro_rules! impl_system {
Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static,
#(TParam~I: Param<'world>,)*
#(TParam~I::Input: 'static,)*
- (#(TParam~I::Input,)*): TupleReduce<ParamWithInputFilter>,
- <(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out:
- TupleIntoInOptions
+ (#(TParam~I::Input,)*): TupleReduce<
+ ParamWithInputFilter,
+ Out: Tuple<InOptions: TupleWithAllElemLtStatic>
+ >,
{
type Input =
<(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out;
@@ -50,35 +49,27 @@ macro_rules! impl_system {
{
let mut option_input = input.into_in_options();
+ let mut index = 0;
+
#(
if TypeId::of::<TParam~I::Input>() !=
TypeId::of::<()>()
{
- let input = match option_input.take::<TParam~I::Input>() {
- TupleTakeOptionElementResult::Found(input) => input,
- TupleTakeOptionElementResult::NotFound => {
- panic!(
- "Parameter input {} not found",
- type_name::<TParam~I::Input>()
- );
- }
- TupleTakeOptionElementResult::AlreadyTaken => {
- panic!(
- concat!(
- "Parameter {} is already initialized. ",
- "System cannot contain multiple inputs with ",
- "the same type",
- ),
- type_name::<TParam~I>()
- );
-
- }
- };
+ let input = option_input
+ .get_mut::<Option<TParam~I::Input>>(index)
+ .expect("Input element index out of range")
+ .take()
+ .expect("Input element is already taken");
TParam~I::initialize(
&mut self,
input
);
+
+ #[allow(unused_assignments)]
+ {
+ index += 1;
+ }
}
)*
@@ -118,14 +109,14 @@ macro_rules! impl_system {
fn get_local_component_mut<LocalComponent: Component>(
&self,
- ) -> Option<ComponentRefMut<LocalComponent>>
+ ) -> Option<ComponentHandleMut<LocalComponent>>
{
let local_component = self.local_components
.get(&LocalComponent::id())?
.write_nonblock()
.expect("Failed to aquire read-write local component lock");
- Some(ComponentRefMut::new(local_component))
+ Some(ComponentHandleMut::new(local_component))
}
fn set_local_component<LocalComponent: Component>(
@@ -136,7 +127,10 @@ macro_rules! impl_system {
self.local_components
.insert(
LocalComponent::id(),
- Lock::new(Box::new(local_component))
+ Lock::new(
+ Box::new(local_component),
+ type_name::<LocalComponent>()
+ )
);
}
}
diff --git a/ecs/src/tuple.rs b/ecs/src/tuple.rs
index 1434592..def25a0 100644
--- a/ecs/src/tuple.rs
+++ b/ecs/src/tuple.rs
@@ -1,36 +1,44 @@
use std::any::TypeId;
-use std::mem::{transmute_copy, ManuallyDrop};
use paste::paste;
use seq_macro::seq;
use util_macros::sub;
-/// Used to append a element to a tuple type.
-pub trait With<OtherElem>
+pub trait Tuple: sealed::Sealed
{
- type With;
-}
-
-/// Used to remove the last element of a tuple type.
-///
-/// For example: `(u32, String, PathBuf)` becomes `(u32, String)`.
-pub trait WithoutLast
-{
- type Return;
-}
-
-/// Used to make all elements of a tuple type wrapped in [`Option`].
-pub trait IntoInOptions
-{
- type InOptions: WithOptionElements;
-
+ /// `Self` with the given type added as the last element.
+ ///
+ /// `(String, i32, u8)::WithElementAtEnd<Path> = (String, i32, u8, Path)`
+ ///
+ /// # Important note
+ /// If `Self` has 16 elements, this will be `()`. The reason for this is that the
+ /// `Tuple` trait is only implemented for tuples with up to and including 16 elements.
+ type WithElementAtEnd<NewElem>: Tuple;
+
+ /// `Self` without the last element.
+ ///
+ /// `(u16, AtomicU8)::WithoutLastElement = (u16,)`
+ type WithoutLastElement: Tuple;
+
+ /// The last element of `Self`.
+ type LastElement;
+
+ /// Self with all elements wrapped in [`Option`].
+ type InOptions: Tuple;
+
+ /// Pops the last element from this tuple, returning the new tuple and the popped
+ /// element.
+ fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement);
+
+ /// Converts this tuple so that all elements are wrapped in [`Option`].
fn into_in_options(self) -> Self::InOptions;
}
-/// A tuple with all elements wrapped in [`Option`].
-pub trait WithOptionElements
+/// A tuple with element types that all have the lifetime `'static`.
+pub trait WithAllElemLtStatic: Tuple + sealed::Sealed
{
- fn take<Element: 'static>(&mut self) -> TakeOptionElementResult<Element>;
+ /// Returns the element at the given index.
+ fn get_mut<Element: 'static>(&mut self, index: usize) -> Option<&mut Element>;
}
/// Using the type system, reduces the elements of a tuple to a single one. Each element
@@ -45,22 +53,6 @@ pub trait ReduceElement<Accumulator, Operation>
type Return;
}
-/// The result of trying to [`take`] a element from a implementation of
-/// [`WithOptionElements`].
-///
-/// [`take`]: WithOptionElements::take
-pub enum TakeOptionElementResult<Element>
-{
- /// The element was succesfully taken.
- Found(Element),
-
- /// The element is not a element of the tuple.
- NotFound,
-
- /// The element has already been taken
- AlreadyTaken,
-}
-
macro_rules! tuple_reduce_elem_tuple {
(overflow) => {
()
@@ -95,13 +87,6 @@ macro_rules! elem_by_index {
};
}
-pub trait Pop: WithoutLast
-{
- type LastElem;
-
- fn pop(self) -> (<Self as WithoutLast>::Return, Self::LastElem);
-}
-
macro_rules! all_except_last {
(start $($rest: tt)*) => {
all_except_last!(@[] $($rest)*)
@@ -131,19 +116,23 @@ macro_rules! all_except_last {
macro_rules! impl_tuple_traits {
($cnt: tt) => {
seq!(I in 0..$cnt {
- impl<OtherElem, #(Elem~I,)*> With<OtherElem> for (#(Elem~I,)*)
+ impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*)
{
- type With = (#(Elem~I,)* OtherElem,);
- }
+ type WithElementAtEnd<NewElem> = (#(Elem~I,)* NewElem,);
- impl<#(Elem~I,)*> WithoutLast for (#(Elem~I,)*)
- {
- type Return = all_except_last!(start #(Elem~I)*);
- }
+ type WithoutLastElement = all_except_last!(start #(Elem~I)*);
- impl<#(Element~I: 'static,)*> IntoInOptions for (#(Element~I,)*)
- {
- type InOptions = (#(Option<Element~I>,)*);
+ type LastElement = sub!($cnt - 1, elem_type_by_index);
+
+ type InOptions = (#(Option<Elem~I>,)*);
+
+ fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement)
+ {
+ (
+ all_except_last!(start #((self.I))*),
+ sub!($cnt - 1, elem_by_index, (self))
+ )
+ }
fn into_in_options(self) -> Self::InOptions
{
@@ -152,32 +141,28 @@ macro_rules! impl_tuple_traits {
}
}
- impl<#(Element~I: 'static,)*> WithOptionElements for (#(Option<Element~I>,)*)
+ impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*)
{
- fn take<Element: 'static>(&mut self)
- -> TakeOptionElementResult<Element>
+ fn get_mut<Element: 'static>(&mut self, index: usize) -> Option<&mut Element>
{
- #(
- if TypeId::of::<Element~I>() == TypeId::of::<Element>() {
- let input = match self.I.take() {
- Some(input) => ManuallyDrop::new(input),
- None => {
- return TakeOptionElementResult::AlreadyTaken;
- }
- };
-
- return TakeOptionElementResult::Found(
- // SAFETY: It can be transmuted safely since it is the
- // same type and the type is 'static
- unsafe { transmute_copy(&input) }
- );
- }
- )*
-
- TakeOptionElementResult::NotFound
+ match index {
+ #(
+ I => {
+ assert!(TypeId::of::<Element>() == TypeId::of::<Elem~I>());
+
+ // SAFETY: It is checked above that the type is correct
+ Some(unsafe { &mut *(&raw mut self.I).cast::<Element>() })
+ }
+ )*
+ _ => None
+ }
}
}
+ impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*)
+ {
+ }
+
paste! {
impl<Operation, #(Elem~I,)*> Reduce<Operation> for (#(Elem~I,)*)
where
@@ -190,19 +175,6 @@ macro_rules! impl_tuple_traits {
type Out = sub!($cnt - 1, tuple_reduce_elem_tuple);
}
}
-
- impl<#(Elem~I,)*> Pop for (#(Elem~I,)*)
- {
- type LastElem = sub!($cnt - 1, elem_type_by_index);
-
- fn pop(self) -> (<Self as WithoutLast>::Return, Self::LastElem)
- {
- (
- all_except_last!(start #((self.I))*),
- sub!($cnt - 1, elem_by_index, (self))
- )
- }
- }
});
};
}
@@ -210,3 +182,57 @@ macro_rules! impl_tuple_traits {
seq!(N in 0..16 {
impl_tuple_traits!(N);
});
+
+seq!(I in 0..16 {
+ impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*)
+ {
+ type WithElementAtEnd<NewElem> = ();
+
+ type WithoutLastElement = all_except_last!(start #(Elem~I)*);
+
+ type LastElement = Elem15;
+
+ type InOptions = (#(Option<Elem~I>,)*);
+
+ fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement)
+ {
+ (
+ all_except_last!(start #((self.I))*),
+ self.15
+ )
+ }
+
+ fn into_in_options(self) -> Self::InOptions
+ {
+ #![allow(clippy::unused_unit)]
+ (#(Some(self.I),)*)
+ }
+ }
+
+ impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*)
+ {
+ fn get_mut<Element: 'static>(&mut self, index: usize) -> Option<&mut Element>
+ {
+ match index {
+ #(
+ I => {
+ assert!(TypeId::of::<Element>() == TypeId::of::<Elem~I>());
+
+ // SAFETY: It is checked above that the type is correct
+ Some(unsafe { &mut *(&raw mut self.I).cast::<Element>() })
+ }
+ )*
+ _ => None
+ }
+ }
+ }
+
+ impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*)
+ {
+ }
+});
+
+mod sealed
+{
+ pub trait Sealed {}
+}
diff --git a/ecs/src/type_name.rs b/ecs/src/type_name.rs
deleted file mode 100644
index 54179be..0000000
--- a/ecs/src/type_name.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use std::any::type_name;
-
-pub trait TypeName
-{
- /// Returns the name of this type.
- fn type_name(&self) -> &'static str;
-}
-
-impl<Item> TypeName for Vec<Item>
-{
- fn type_name(&self) -> &'static str
- {
- type_name::<Self>()
- }
-}
diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs
index 0e5d88a..feed62c 100644
--- a/ecs/src/uid.rs
+++ b/ecs/src/uid.rs
@@ -1,21 +1,29 @@
+use std::fmt::{Debug, Display, Formatter};
use std::mem::transmute;
use std::sync::atomic::{AtomicU32, Ordering};
-static NEXT: AtomicU32 = AtomicU32::new(1);
+use crate::component::Component;
+use crate::util::{gen_mask_64, BitMask, NumberExt};
-// Bit 0 and 1 for the kind
-const KIND_BITS: u64 = 0x03;
+static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID);
+
+static WILDCARD_ID: u32 = 1;
+
+const ID_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(32..=63));
+const RELATION_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(6..=31));
+const KIND_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(0..=1));
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum Kind
{
+ Pair = 3,
Entity = 2,
Component = 1,
}
-/// Unique entity/component ID.
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+/// A unique identifier.
+#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Uid
{
inner: u64,
@@ -23,21 +31,213 @@ pub struct Uid
impl Uid
{
+ /// The id part of the first unique `Uid`. The ids `0..Uid::FIRST_UNIQUE_ID` are
+ /// reserved.
+ pub const FIRST_UNIQUE_ID: u32 = 5;
+
/// Returns a new unique entity/component ID.
pub fn new_unique(kind: Kind) -> Self
{
- let id_part = NEXT.fetch_add(1, Ordering::Relaxed);
+ let id = NEXT.fetch_add(1, Ordering::Relaxed);
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(id)) | KIND_BITS.field_prep(kind as u64),
+ }
+ }
+ #[must_use]
+ pub fn wildcard() -> Self
+ {
Self {
- inner: (u64::from(id_part) << 32) | kind as u64,
+ inner: ID_BITS.field_prep(u64::from(WILDCARD_ID))
+ | KIND_BITS.field_prep(Kind::Component as u64),
}
}
+ /// Returns a new pair UID.
+ ///
+ /// # Panics
+ /// Will panic if either the given relation or target is a pair UID.
+ #[must_use]
+ pub fn new_pair(params: &PairParams) -> Self
+ {
+ assert_ne!(
+ params.relation.kind(),
+ Kind::Pair,
+ "Pair relation cannot be a pair"
+ );
+
+ assert_ne!(
+ params.target.kind(),
+ Kind::Pair,
+ "Pair target cannot be a pair"
+ );
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(params.target.id()))
+ | RELATION_BITS.field_prep(u64::from(params.relation.id()))
+ | KIND_BITS.field_prep(Kind::Pair as u64),
+ }
+ }
+
+ #[must_use]
+ pub fn id(&self) -> u32
+ {
+ let Ok(id) = u32::try_from(self.inner.field_get(ID_BITS)) else {
+ unreachable!("Uid id does not fit in u32");
+ };
+
+ id
+ }
+
#[must_use]
pub fn kind(&self) -> Kind
{
+ let Ok(kind) = u8::try_from(self.inner.field_get(KIND_BITS)) else {
+ unreachable!("Uid kind does not fit in u8");
+ };
+
// SAFETY: The kind bits cannot be invalid since they are set using the Kind enum
// in the new_unique function
- unsafe { transmute((self.inner & KIND_BITS) as u8) }
+ unsafe { transmute::<u8, Kind>(kind) }
+ }
+
+ /// If this `Uid` is a pair, returns the relation as a component `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn relation_component(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.relation()))
+ | KIND_BITS.field_prep(Kind::Component as u64),
+ }
+ }
+
+ #[must_use]
+ pub fn has_same_relation_as(&self, other: Self) -> bool
+ {
+ self.relation() == other.relation()
+ }
+
+ /// If this `Uid` is a pair, returns the relation as a entity `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn relation_entity(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.relation()))
+ | KIND_BITS.field_prep(Kind::Entity as u64),
+ }
+ }
+
+ /// If this `Uid` is a pair, returns the target as a component `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn target_component(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.id()))
+ | KIND_BITS.field_prep(Kind::Component as u64),
+ }
+ }
+
+ /// If this `Uid` is a pair, returns the target as a entity `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn target_entity(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.id()))
+ | KIND_BITS.field_prep(Kind::Entity as u64),
+ }
+ }
+
+ fn relation(self) -> u32
+ {
+ let Ok(relation) = u32::try_from(self.inner.field_get(RELATION_BITS)) else {
+ unreachable!("Uid relation does not fit in u32");
+ };
+
+ relation
+ }
+}
+
+impl Debug for Uid
+{
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
+ {
+ formatter
+ .debug_struct("Uid")
+ .field("id", &self.id())
+ .field("kind", &self.kind())
+ .finish_non_exhaustive()
+ }
+}
+
+impl Display for Uid
+{
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
+ {
+ if self.kind() == Kind::Pair {
+ return write!(
+ formatter,
+ "({}, {})",
+ self.relation(),
+ self.target_component()
+ );
+ }
+
+ if *self == Uid::wildcard() {
+ return write!(formatter, "*");
+ }
+
+ write!(formatter, "{}", self.id())
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct PairParams
+{
+ pub relation: Uid,
+ pub target: Uid,
+}
+
+pub trait With: 'static
+{
+ fn uid() -> Uid;
+}
+
+impl<ComponentT: Component> With for ComponentT
+{
+ fn uid() -> Uid
+ {
+ Self::id()
+ }
+}
+
+#[derive(Debug)]
+pub enum Wildcard {}
+
+impl With for Wildcard
+{
+ fn uid() -> Uid
+ {
+ Uid::wildcard()
}
}
diff --git a/ecs/src/util.rs b/ecs/src/util.rs
index 4480fc8..9ab4dc6 100644
--- a/ecs/src/util.rs
+++ b/ecs/src/util.rs
@@ -1,3 +1,186 @@
+use std::hash::Hash;
+use std::mem::transmute;
+use std::ops::{BitAnd, Deref};
+
+use hashbrown::HashMap;
+
+pub(crate) mod array_vec;
+
+pub trait VecExt<Item>
+{
+ fn insert_at_partition_point_by_key<Key>(
+ &mut self,
+ item: Item,
+ func: impl FnMut(&Item) -> Key,
+ ) where
+ Key: Ord;
+}
+
+impl<Item> VecExt<Item> for Vec<Item>
+{
+ fn insert_at_partition_point_by_key<Key>(
+ &mut self,
+ item: Item,
+ mut func: impl FnMut(&Item) -> Key,
+ ) where
+ Key: Ord,
+ {
+ let key = func(&item);
+
+ let insert_index = self.partition_point(|other_item| func(other_item) <= key);
+
+ self.insert(insert_index, item);
+ }
+}
+
+pub trait StreamingIterator
+{
+ type Item<'a>
+ where
+ Self: 'a;
+
+ fn streaming_next(&mut self) -> Option<Self::Item<'_>>;
+
+ fn streaming_map<NewItem, Func>(self, func: Func) -> StreamingMap<Self, Func>
+ where
+ Self: Sized,
+ Func: FnMut(Self::Item<'_>) -> NewItem,
+ {
+ StreamingMap { iter: self, func }
+ }
+
+ fn streaming_find<'this, Predicate>(
+ &'this mut self,
+ mut predicate: Predicate,
+ ) -> Option<Self::Item<'this>>
+ where
+ Self: Sized,
+ Predicate: FnMut(&Self::Item<'this>) -> bool,
+ {
+ while let Some(item) = unsafe {
+ transmute::<Option<Self::Item<'_>>, Option<Self::Item<'_>>>(
+ self.streaming_next(),
+ )
+ } {
+ if predicate(&item) {
+ return Some(item);
+ }
+ }
+
+ None
+ }
+}
+
+pub struct StreamingMap<Iter, Func>
+{
+ iter: Iter,
+ func: Func,
+}
+
+impl<Iter, Func, Item> StreamingIterator for StreamingMap<Iter, Func>
+where
+ Iter: StreamingIterator,
+ Func: FnMut(Iter::Item<'_>) -> Item,
+{
+ type Item<'a>
+ = Item
+ where
+ Iter: 'a,
+ Func: 'a;
+
+ fn streaming_next(&mut self) -> Option<Self::Item<'_>>
+ {
+ Some((self.func)(self.iter.streaming_next()?))
+ }
+}
+
+#[derive(Debug)]
+pub enum BorrowedOrOwned<'a, Value>
+{
+ Borrowned(&'a Value),
+ Owned(Value),
+}
+
+impl<Value> Deref for BorrowedOrOwned<'_, Value>
+{
+ type Target = Value;
+
+ fn deref(&self) -> &Self::Target
+ {
+ match self {
+ Self::Borrowned(value) => value,
+ Self::Owned(value) => value,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Either<A, B>
+{
+ A(A),
+ B(B),
+}
+
+impl<A, B> Iterator for Either<A, B>
+where
+ A: Iterator,
+ B: Iterator<Item = A::Item>,
+{
+ type Item = A::Item;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ match self {
+ Self::A(a) => a.next(),
+ Self::B(b) => b.next(),
+ }
+ }
+}
+
+pub trait HashMapExt<Key, Value>
+{
+ /// Returns true if the keys are a subset of another [`HashMap`]'s keys, i.e., `other`
+ /// contains at least all the keys in `self`.
+ fn keys_is_subset(&self, other: &Self) -> bool;
+
+ /// Returns true if the keys are a superset of another [`HashMap`]'s keys, i.e.,
+ /// `self` contains at least all the keys in `other`.
+ fn keys_is_superset(&self, other: &Self) -> bool;
+}
+
+impl<Key, Value> HashMapExt<Key, Value> for HashMap<Key, Value>
+where
+ Key: Eq + Hash,
+{
+ fn keys_is_subset(&self, other: &Self) -> bool
+ {
+ if self.len() <= other.len() {
+ self.keys().all(|key| other.contains_key(key))
+ } else {
+ false
+ }
+ }
+
+ fn keys_is_superset(&self, other: &Self) -> bool
+ {
+ other.keys_is_subset(self)
+ }
+}
+
+pub trait Array<Item>:
+ AsRef<[Item]>
+ + AsMut<[Item]>
+ + IntoIterator<Item = Item>
+ + Into<Vec<Item>>
+ + Sortable<Item = Item>
+ + sealed::Sealed
+{
+}
+
+impl<Item, const CNT: usize> Array<Item> for [Item; CNT] {}
+
+impl<Item, const CNT: usize> sealed::Sealed for [Item; CNT] {}
+
pub trait Sortable
{
type Item;
@@ -46,3 +229,105 @@ impl<Item> Sortable for Vec<Item>
self.sort_by_key(func);
}
}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct BitMask<Value>
+{
+ mask: Value,
+}
+
+impl BitMask<u64>
+{
+ #[must_use]
+ pub const fn new(mask: u64) -> Self
+ {
+ Self { mask }
+ }
+
+ #[must_use]
+ pub const fn value(self) -> u64
+ {
+ self.mask
+ }
+
+ /// Prepares a bitfield value in the range of bits specified by this `BitMask`.
+ #[must_use]
+ pub const fn field_prep(self, field_value: u64) -> u64
+ {
+ debug_assert!(field_value < 1 << self.mask.count_ones());
+
+ ((field_value) << self.mask.trailing_zeros()) & (self.mask)
+ }
+}
+
+impl BitAnd<u64> for BitMask<u64>
+{
+ type Output = u64;
+
+ fn bitand(self, rhs: u64) -> Self::Output
+ {
+ self.mask & rhs
+ }
+}
+
+pub trait NumberExt: Sized
+{
+ /// Returns a range of bits (field) specified by the provided [`BitMask`].
+ #[must_use]
+ fn field_get(self, field_mask: BitMask<Self>) -> Self;
+}
+
+impl NumberExt for u64
+{
+ fn field_get(self, field_mask: BitMask<Self>) -> Self
+ {
+ (field_mask & self) >> field_mask.value().trailing_zeros()
+ }
+}
+
+macro_rules! gen_mask_64 {
+ ($low: literal..=$high: literal) => {
+ const {
+ if $high <= $low {
+ panic!("High bit index cannot be less than or equal to low bit index");
+ }
+
+ (((!0u64) - (1u64 << ($low)) + 1)
+ & (!0u64 >> (u64::BITS as u64 - 1 - ($high))))
+ }
+ };
+}
+
+pub(crate) use gen_mask_64;
+
+mod sealed
+{
+ pub trait Sealed {}
+}
+
+#[cfg(test)]
+mod tests
+{
+
+ use super::BitMask;
+ use crate::util::NumberExt;
+
+ #[test]
+ fn field_get_works()
+ {
+ assert_eq!(0b11011u64.field_get(BitMask::new(0b11100)), 0b00110);
+ }
+
+ #[test]
+ fn bitmask_field_prep_works()
+ {
+ assert_eq!(BitMask::new(0b11000).field_prep(3), 0b11000);
+ }
+
+ #[test]
+ #[should_panic]
+ fn bitmask_field_prep_too_large_value_panics()
+ {
+ let _ = BitMask::new(0b001110).field_prep(9);
+ }
+}
diff --git a/ecs/src/util/array_vec.rs b/ecs/src/util/array_vec.rs
new file mode 100644
index 0000000..a37b1f9
--- /dev/null
+++ b/ecs/src/util/array_vec.rs
@@ -0,0 +1,131 @@
+use std::mem::MaybeUninit;
+use std::ops::{Deref, DerefMut};
+
+#[derive(Debug)]
+pub struct ArrayVec<Item, const CAPACITY: usize>
+{
+ items: [MaybeUninit<Item>; CAPACITY],
+ len: usize,
+}
+
+impl<Item, const CAPACITY: usize> ArrayVec<Item, CAPACITY>
+{
+ #[inline]
+ #[must_use]
+ pub fn len(&self) -> usize
+ {
+ self.len
+ }
+
+ #[inline]
+ #[must_use]
+ pub fn is_empty(&self) -> bool
+ {
+ self.len == 0
+ }
+
+ pub fn push(&mut self, item: Item)
+ {
+ assert!(self.len < CAPACITY);
+
+ self.items[self.len].write(item);
+
+ self.len += 1;
+ }
+
+ pub fn insert(&mut self, index: usize, item: Item)
+ {
+ assert!(index <= self.len);
+ assert!(self.len < CAPACITY);
+
+ if index == self.len {
+ self.push(item);
+ return;
+ }
+
+ unsafe {
+ std::ptr::copy(
+ &self.items[index],
+ &mut self.items[index + 1],
+ self.len - index,
+ );
+ }
+
+ self.items[index].write(item);
+
+ self.len += 1;
+ }
+}
+
+impl<Item, const CAPACITY: usize> Extend<Item> for ArrayVec<Item, CAPACITY>
+{
+ fn extend<IntoIter: IntoIterator<Item = Item>>(&mut self, iter: IntoIter)
+ {
+ for item in iter {
+ self.push(item);
+ }
+ }
+}
+
+impl<Item, const CAPACITY: usize> AsRef<[Item]> for ArrayVec<Item, CAPACITY>
+{
+ fn as_ref(&self) -> &[Item]
+ {
+ let ptr = &raw const self.items[..self.len];
+
+ unsafe { &*(ptr as *const [Item]) }
+ }
+}
+
+impl<Item, const CAPACITY: usize> AsMut<[Item]> for ArrayVec<Item, CAPACITY>
+{
+ fn as_mut(&mut self) -> &mut [Item]
+ {
+ let ptr = &raw mut self.items[..self.len];
+
+ unsafe { &mut *(ptr as *mut [Item]) }
+ }
+}
+
+impl<Item, const CAPACITY: usize> Deref for ArrayVec<Item, CAPACITY>
+{
+ type Target = [Item];
+
+ fn deref(&self) -> &Self::Target
+ {
+ self.as_ref()
+ }
+}
+
+impl<Item, const CAPACITY: usize> DerefMut for ArrayVec<Item, CAPACITY>
+{
+ fn deref_mut(&mut self) -> &mut Self::Target
+ {
+ self.as_mut()
+ }
+}
+
+impl<Item, const CAPACITY: usize> Default for ArrayVec<Item, CAPACITY>
+{
+ fn default() -> Self
+ {
+ Self {
+ items: [const { MaybeUninit::uninit() }; CAPACITY],
+ len: 0,
+ }
+ }
+}
+
+impl<Item, const CAPACITY: usize> Drop for ArrayVec<Item, CAPACITY>
+{
+ fn drop(&mut self)
+ {
+ for item in &mut self.items[..self.len] {
+ // SAFETY: The items from index 0 to the length index will always be
+ // initialized and satisfy all the invariants of the Item type.
+ unsafe {
+ item.assume_init_drop();
+ }
+ }
+ }
+}