From 9d8c73dd2671131929967214433dae5479e95b5b Mon Sep 17 00:00:00 2001 From: HampusM Date: Tue, 9 Apr 2024 22:25:03 +0200 Subject: feat(ecs): add support for singleton components --- ecs/examples/with_single.rs | 71 ++++++++++++++++++++++++++++++++ ecs/src/component.rs | 1 + ecs/src/component/single.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++ ecs/src/lib.rs | 54 +++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 ecs/examples/with_single.rs create mode 100644 ecs/src/component/single.rs diff --git a/ecs/examples/with_single.rs b/ecs/examples/with_single.rs new file mode 100644 index 0000000..25b73d1 --- /dev/null +++ b/ecs/examples/with_single.rs @@ -0,0 +1,71 @@ +use ecs::component::single::Single; +use ecs::event::{Event, Id as EventId}; +use ecs::{Component, Query, World}; + +#[derive(Component)] +struct Ammo +{ + ammo_left: u32, +} + +#[derive(Component, Default)] +struct AmmoCounter +{ + counter: u32, +} + +fn count_ammo(query: Query<(Ammo,)>, mut ammo_counter: Single) +{ + for (ammo,) in &query { + println!("Found {} ammo", ammo.ammo_left); + + ammo_counter.counter += ammo.ammo_left; + } +} + +fn print_total_ammo_count(ammo_counter: Single) +{ + println!("Total ammo count: {}", ammo_counter.counter); + + assert_eq!(ammo_counter.counter, 19); +} + +#[derive(Debug)] +struct EventA; + +impl Event for EventA +{ + fn id(&self) -> EventId + { + EventId::of::() + } +} + +#[derive(Debug)] +struct EventB; + +impl Event for EventB +{ + fn id(&self) -> EventId + { + EventId::of::() + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(&EventA, count_ammo); + world.register_system(&EventB, print_total_ammo_count); + + world.create_entity((Ammo { ammo_left: 4 },)); + world.create_entity((Ammo { ammo_left: 7 },)); + world.create_entity((Ammo { ammo_left: 8 },)); + + world.add_single_component(AmmoCounter::default()).unwrap(); + + world.emit(&EventA); + + world.emit(&EventB); +} diff --git a/ecs/src/component.rs b/ecs/src/component.rs index 7a61f39..0e5f020 100644 --- a/ecs/src/component.rs +++ b/ecs/src/component.rs @@ -8,6 +8,7 @@ use crate::system::{ComponentRefMut, Input as SystemInput}; use crate::type_name::TypeName; pub mod local; +pub mod single; pub trait Component: SystemInput + Any + TypeName { diff --git a/ecs/src/component/single.rs b/ecs/src/component/single.rs new file mode 100644 index 0000000..a63dbe3 --- /dev/null +++ b/ecs/src/component/single.rs @@ -0,0 +1,98 @@ +use std::any::{type_name, Any, TypeId}; +use std::ops::{Deref, DerefMut}; + +use crate::component::Component; +use crate::system::{ComponentRefMut, NoInitParamFlag, Param as SystemParam, System}; +use crate::tuple::FilterExclude as TupleFilterExclude; +use crate::WorldData; + +/// Holds a component which has a single instance and is shared globally. +#[derive(Debug)] +pub struct Single<'world, SingleComponent: Component> +{ + single_component: ComponentRefMut<'world, SingleComponent>, +} + +unsafe impl<'world, SingleComponent> SystemParam<'world> + for Single<'world, SingleComponent> +where + SingleComponent: Component, +{ + type Flags = NoInitParamFlag; + type Input = TupleFilterExclude; + + fn initialize( + _system: &mut impl System<'world, SystemImpl>, + _input: Self::Input, + ) + { + } + + fn new( + _system: &'world impl System<'world, SystemImpl>, + world_data: &'world WorldData, + ) -> Self + { + let single_component = world_data + .single_component_storage + .get::() + .expect("Single component was not found in world") + .write_nonblock() + .unwrap_or_else(|_| { + panic!( + "Failed to aquire read-write lock to single component {}", + type_name::() + ) + }); + + Self { + single_component: ComponentRefMut::new(single_component), + } + } + + fn is_compatible>() -> bool + { + let other_comparable = Other::get_comparable(); + + let Some(comparable) = other_comparable.downcast_ref::() else { + // The other system param is not Single + return true; + }; + + TypeId::of::() != comparable.single_component_type_id + } + + fn get_comparable() -> Box + { + Box::new(Comparable { + single_component_type_id: TypeId::of::(), + }) + } +} + +impl<'world, SingleComponent> Deref for Single<'world, SingleComponent> +where + SingleComponent: Component, +{ + type Target = SingleComponent; + + fn deref(&self) -> &Self::Target + { + &self.single_component + } +} + +impl<'world, SingleComponent> DerefMut for Single<'world, SingleComponent> +where + SingleComponent: Component, +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.single_component + } +} + +struct Comparable +{ + single_component_type_id: TypeId, +} diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 33d981b..ce1a7d1 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -89,6 +89,20 @@ impl World }); } + /// Adds a single component. This component will be globally shared. + /// + /// # Errors + /// Returns `Err` if this component has already been added as a single component. + pub fn add_single_component( + &mut self, + single_component: SingleComponent, + ) -> Result<(), SingleComponentAlreadyExistsError> + where + SingleComponent: Component, + { + self.data.single_component_storage.insert(single_component) + } + pub fn register_system<'this, SystemImpl>( &'this mut self, event: &impl Event, @@ -206,6 +220,7 @@ pub struct WorldData { events: HashMap>, component_storage: Arc>, + single_component_storage: SingleComponentStorage, action_queue: Arc>, } @@ -324,3 +339,42 @@ impl Drop for ComponentStorage } } } + +#[derive(Debug, thiserror::Error)] +#[error("Single component {0} already exists")] +pub struct SingleComponentAlreadyExistsError(pub &'static str); + +#[derive(Debug, Default)] +struct SingleComponentStorage +{ + storage: HashMap>>, +} + +impl SingleComponentStorage +{ + fn get(&self) -> Option<&Lock>> + { + self.storage.get(&TypeId::of::()) + } + + fn insert( + &mut self, + single_component: SingleComponent, + ) -> Result<(), SingleComponentAlreadyExistsError> + { + let single_component_type_id = TypeId::of::(); + + if self.storage.contains_key(&single_component_type_id) { + return Err(SingleComponentAlreadyExistsError(type_name::< + SingleComponent, + >())); + } + + self.storage.insert( + single_component_type_id, + Lock::new(Box::new(single_component)), + ); + + Ok(()) + } +} -- cgit v1.2.3-18-g5258