summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2024-04-09 22:25:03 +0200
committerHampusM <hampus@hampusmat.com>2024-04-09 22:25:03 +0200
commit9d8c73dd2671131929967214433dae5479e95b5b (patch)
treed522d975e14e6db7544474b641d824edd8edbae5
parent50234ddc9ee7acd6d2b8d0a5626caf9e9293c0da (diff)
feat(ecs): add support for singleton components
-rw-r--r--ecs/examples/with_single.rs71
-rw-r--r--ecs/src/component.rs1
-rw-r--r--ecs/src/component/single.rs98
-rw-r--r--ecs/src/lib.rs54
4 files changed, 224 insertions, 0 deletions
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<AmmoCounter>)
+{
+ for (ammo,) in &query {
+ println!("Found {} ammo", ammo.ammo_left);
+
+ ammo_counter.counter += ammo.ammo_left;
+ }
+}
+
+fn print_total_ammo_count(ammo_counter: Single<AmmoCounter>)
+{
+ 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::<Self>()
+ }
+}
+
+#[derive(Debug)]
+struct EventB;
+
+impl Event for EventB
+{
+ fn id(&self) -> EventId
+ {
+ EventId::of::<Self>()
+ }
+}
+
+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<SystemImpl>(
+ _system: &mut impl System<'world, SystemImpl>,
+ _input: Self::Input,
+ )
+ {
+ }
+
+ fn new<SystemImpl>(
+ _system: &'world impl System<'world, SystemImpl>,
+ world_data: &'world WorldData,
+ ) -> Self
+ {
+ let single_component = world_data
+ .single_component_storage
+ .get::<SingleComponent>()
+ .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::<SingleComponent>()
+ )
+ });
+
+ Self {
+ single_component: ComponentRefMut::new(single_component),
+ }
+ }
+
+ fn is_compatible<Other: SystemParam<'world>>() -> bool
+ {
+ let other_comparable = Other::get_comparable();
+
+ let Some(comparable) = other_comparable.downcast_ref::<Comparable>() else {
+ // The other system param is not Single
+ return true;
+ };
+
+ TypeId::of::<SingleComponent>() != comparable.single_component_type_id
+ }
+
+ fn get_comparable() -> Box<dyn Any>
+ {
+ Box::new(Comparable {
+ single_component_type_id: TypeId::of::<SingleComponent>(),
+ })
+ }
+}
+
+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<SingleComponent>(
+ &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<EventId, Vec<usize>>,
component_storage: Arc<Lock<ComponentStorage>>,
+ single_component_storage: SingleComponentStorage,
action_queue: Arc<Lock<ActionQueue>>,
}
@@ -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<TypeId, Lock<Box<dyn Component>>>,
+}
+
+impl SingleComponentStorage
+{
+ fn get<SingleComponent: Component>(&self) -> Option<&Lock<Box<dyn Component>>>
+ {
+ self.storage.get(&TypeId::of::<SingleComponent>())
+ }
+
+ fn insert<SingleComponent: Component>(
+ &mut self,
+ single_component: SingleComponent,
+ ) -> Result<(), SingleComponentAlreadyExistsError>
+ {
+ let single_component_type_id = TypeId::of::<SingleComponent>();
+
+ 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(())
+ }
+}