summaryrefslogtreecommitdiff
path: root/ecs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2025-08-26 16:43:40 +0200
committerHampusM <hampus@hampusmat.com>2025-09-11 18:24:48 +0200
commit09981e0173a2427264e432226804292c91e1f920 (patch)
tree70112b6cda98a55da625ec9f6762927f00affe91 /ecs
parentce1bade2c21cc3129fa8bc2b4bc67bc4dc2c25c3 (diff)
feat(ecs): add component changed event
Diffstat (limited to 'ecs')
-rw-r--r--ecs/examples/component_changed_event.rs80
-rw-r--r--ecs/src/component.rs59
-rw-r--r--ecs/src/entity.rs61
-rw-r--r--ecs/src/event.rs100
-rw-r--r--ecs/src/event/component.rs7
-rw-r--r--ecs/src/lib.rs158
-rw-r--r--ecs/src/pair.rs19
-rw-r--r--ecs/src/query.rs4
-rw-r--r--ecs/src/query/flexible.rs4
-rw-r--r--ecs/src/query/term.rs3
-rw-r--r--ecs/src/system.rs3
-rw-r--r--ecs/src/system/observer.rs276
-rw-r--r--ecs/src/system/stateful.rs127
13 files changed, 807 insertions, 94 deletions
diff --git a/ecs/examples/component_changed_event.rs b/ecs/examples/component_changed_event.rs
new file mode 100644
index 0000000..643f338
--- /dev/null
+++ b/ecs/examples/component_changed_event.rs
@@ -0,0 +1,80 @@
+use ecs::event::component::OnChanged;
+use ecs::pair::Pair;
+use ecs::phase::UPDATE as UPDATE_PHASE;
+use ecs::system::observer::Observe;
+use ecs::{Component, Query, World};
+
+#[derive(Component)]
+struct SomeData
+{
+ num: u64,
+}
+
+#[derive(Component)]
+struct Greeting
+{
+ greeting: String,
+}
+
+fn say_hello(query: Query<(&SomeData, &mut Greeting)>)
+{
+ for (data, mut greeting) in &query {
+ println!("{}: {}", greeting.greeting, data.num);
+
+ if greeting.greeting == "Good evening" {
+ greeting.greeting = "Good morning".to_string();
+ greeting.set_changed();
+ }
+ }
+}
+
+fn print_changed_greetings(observe: Observe<'_, Pair<OnChanged, Greeting>>)
+{
+ println!("\nChanged greetings:");
+
+ for ent in &observe {
+ let Some(greeting) = ent.get::<Greeting>() else {
+ unreachable!();
+ };
+
+ println!("A greeting changed to {}", greeting.greeting);
+ }
+
+ println!("");
+}
+
+fn main()
+{
+ let mut world = World::new();
+
+ world.register_system(*UPDATE_PHASE, say_hello);
+
+ world.register_observer(print_changed_greetings);
+
+ world.create_entity((
+ SomeData { num: 987_654 },
+ Greeting {
+ greeting: "Good afternoon".to_string(),
+ },
+ ));
+
+ world.create_entity((
+ SomeData { num: 345 },
+ Greeting { greeting: "Good evening".to_string() },
+ ));
+
+ world.step();
+
+ world.step();
+
+ for (mut greeting,) in &world.query::<(&mut Greeting,), ()>() {
+ if greeting.greeting == "Good afternoon" {
+ greeting.greeting = "Yo yo".to_string();
+ greeting.set_changed();
+ }
+ }
+
+ world.step();
+
+ world.step();
+}
diff --git a/ecs/src/component.rs b/ecs/src/component.rs
index 8f946f0..9e3975f 100644
--- a/ecs/src/component.rs
+++ b/ecs/src/component.rs
@@ -4,6 +4,8 @@ use std::ops::{Deref, DerefMut};
use seq_macro::seq;
+use crate::event::component::OnChanged;
+use crate::event::Submitter as EventSubmitter;
use crate::lock::{
Error as LockError,
MappedReadGuard,
@@ -11,10 +13,11 @@ use crate::lock::{
ReadGuard,
WriteGuard,
};
+use crate::pair::Pair;
use crate::system::Input as SystemInput;
use crate::uid::Uid;
use crate::util::Array;
-use crate::EntityComponentRef;
+use crate::{EntityComponentRef, World};
pub mod local;
@@ -69,12 +72,12 @@ pub trait Sequence
}
#[derive(Debug)]
-pub struct Handle<'a, ComponentData: 'static>
+pub struct Handle<'a, DataT: 'static>
{
- inner: MappedReadGuard<'a, ComponentData>,
+ inner: MappedReadGuard<'a, DataT>,
}
-impl<'comp, ComponentData: 'static> Handle<'comp, ComponentData>
+impl<'comp, DataT: 'static> Handle<'comp, DataT>
{
/// Creates a new handle instance from a [`EntityComponentRef`].
///
@@ -96,16 +99,16 @@ impl<'comp, ComponentData: 'static> Handle<'comp, ComponentData>
{
Ok(Self {
inner: ReadGuard::try_map(inner, |component| {
- component.downcast_ref::<ComponentData>()
+ component.downcast_ref::<DataT>()
})
.map_err(|_| HandleError::IncorrectType)?,
})
}
}
-impl<ComponentData: 'static> Deref for Handle<'_, ComponentData>
+impl<DataT: 'static> Deref for Handle<'_, DataT>
{
- type Target = ComponentData;
+ type Target = DataT;
fn deref(&self) -> &Self::Target
{
@@ -114,12 +117,14 @@ impl<ComponentData: 'static> Deref for Handle<'_, ComponentData>
}
#[derive(Debug)]
-pub struct HandleMut<'a, ComponentData: 'static>
+pub struct HandleMut<'a, DataT: 'static>
{
- inner: MappedWriteGuard<'a, ComponentData>,
+ entity_component_ref: EntityComponentRef<'a>,
+ inner: MappedWriteGuard<'a, DataT>,
+ event_submitter: EventSubmitter<'a>,
}
-impl<'comp, ComponentData: 'static> HandleMut<'comp, ComponentData>
+impl<'comp, DataT: 'static> HandleMut<'comp, DataT>
{
/// Creates a new handle instance from a [`EntityComponentRef`].
///
@@ -127,32 +132,36 @@ impl<'comp, ComponentData: 'static> HandleMut<'comp, ComponentData>
/// Will return `Err` if acquiring the component's lock fails.
pub fn from_entity_component_ref(
entity_component_ref: &EntityComponentRef<'comp>,
+ world: &'comp World,
) -> Result<Self, HandleError>
{
- Self::new(
- entity_component_ref
- .component()
- .write_nonblock()
- .map_err(AcquireLockError)?,
- )
- }
+ let inner = entity_component_ref
+ .component()
+ .write_nonblock()
+ .map_err(AcquireLockError)?;
- // TODO: Make this function private
- pub(crate) fn new(inner: WriteGuard<'comp, Box<dyn Any>>)
- -> Result<Self, HandleError>
- {
Ok(Self {
+ entity_component_ref: entity_component_ref.clone(),
inner: WriteGuard::try_map(inner, |component| {
- component.downcast_mut::<ComponentData>()
+ component.downcast_mut::<DataT>()
})
.map_err(|_| HandleError::IncorrectType)?,
+ event_submitter: world.event_submitter(),
})
}
+
+ pub fn set_changed(&self)
+ {
+ self.event_submitter.submit_event(
+ &Pair::new::<OnChanged>(self.entity_component_ref.id()),
+ self.entity_component_ref.entity_id(),
+ );
+ }
}
-impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData>
+impl<DataT: 'static> Deref for HandleMut<'_, DataT>
{
- type Target = ComponentData;
+ type Target = DataT;
fn deref(&self) -> &Self::Target
{
@@ -160,7 +169,7 @@ impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData>
}
}
-impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData>
+impl<DataT: 'static> DerefMut for HandleMut<'_, DataT>
{
fn deref_mut(&mut self) -> &mut Self::Target
{
diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs
index 34fd19f..ca867f9 100644
--- a/ecs/src/entity.rs
+++ b/ecs/src/entity.rs
@@ -21,6 +21,7 @@ pub struct Handle<'a>
{
archetype: &'a Archetype,
entity: &'a ArchetypeEntity,
+ world: &'a World,
}
impl<'a> Handle<'a>
@@ -41,7 +42,7 @@ impl<'a> Handle<'a>
/// - 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>>
+ pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'a, ComponentT>>
{
assert_eq!(ComponentT::id().kind(), UidKind::Component);
@@ -76,12 +77,44 @@ impl<'a> Handle<'a>
let component = self.get_matching_components(ComponentT::id()).next()?;
Some(
- ComponentHandleMut::from_entity_component_ref(&component).unwrap_or_else(
- |err| {
+ ComponentHandleMut::from_entity_component_ref(&component, self.world)
+ .unwrap_or_else(|err| {
panic!(
"Creating handle to component {} failed: {err}",
type_name::<ComponentT>()
);
+ }),
+ )
+ }
+
+ /// Returns a reference to the component with the ID `id` in this entity.
+ /// `None` is returned if the component isn't found.
+ ///
+ /// # Panics
+ /// Will panic if:
+ /// - The ID is not a component/pair ID
+ /// - The component is borrowed mutably elsewhere
+ /// - The component type is incorrect
+ #[must_use]
+ pub fn get_with_id<ComponentDataT: 'static>(
+ &self,
+ id: Uid,
+ ) -> Option<ComponentHandle<'a, ComponentDataT>>
+ {
+ assert!(
+ matches!(id.kind(), UidKind::Component | UidKind::Pair),
+ "ID {id:?} is not a component/pair ID"
+ );
+
+ let component = self.get_matching_components(id).next()?;
+
+ Some(
+ ComponentHandle::from_entity_component_ref(&component).unwrap_or_else(
+ |err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentDataT>()
+ );
},
),
)
@@ -96,10 +129,10 @@ impl<'a> Handle<'a>
/// - The component is borrowed elsewhere
/// - The component type is incorrect
#[must_use]
- pub fn get_with_id_mut<ComponentData: 'static>(
+ pub fn get_with_id_mut<ComponentDataT: 'static>(
&self,
id: Uid,
- ) -> Option<ComponentHandleMut<'a, ComponentData>>
+ ) -> Option<ComponentHandleMut<'a, ComponentDataT>>
{
assert!(
matches!(id.kind(), UidKind::Component | UidKind::Pair),
@@ -109,14 +142,13 @@ impl<'a> Handle<'a>
let component = self.get_matching_components(id).next()?;
Some(
- ComponentHandleMut::from_entity_component_ref(&component).unwrap_or_else(
- |err| {
+ ComponentHandleMut::from_entity_component_ref(&component, self.world)
+ .unwrap_or_else(|err| {
panic!(
"Creating handle to component {} failed: {err}",
- type_name::<ComponentData>()
+ type_name::<ComponentDataT>()
);
- },
- ),
+ }),
)
}
@@ -131,9 +163,13 @@ impl<'a> Handle<'a>
}
}
- pub(crate) fn new(archetype: &'a Archetype, entity: &'a ArchetypeEntity) -> Self
+ pub(crate) fn new(
+ archetype: &'a Archetype,
+ entity: &'a ArchetypeEntity,
+ world: &'a World,
+ ) -> Self
{
- Self { archetype, entity }
+ Self { archetype, entity, world }
}
}
@@ -155,6 +191,7 @@ impl<'a> Iterator for MatchingComponentIter<'a>
Some(EntityComponentRef::new(
matching_component_id,
self.entity.components().get(index).unwrap(),
+ self.entity.uid(),
))
}
}
diff --git a/ecs/src/event.rs b/ecs/src/event.rs
index 9cea807..2934b82 100644
--- a/ecs/src/event.rs
+++ b/ecs/src/event.rs
@@ -1 +1,101 @@
+use crate::lock::Lock;
+use crate::pair::Pair;
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::VecExt;
+use crate::World;
+
pub mod component;
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct Emitted<'a>
+{
+ pub event: Uid,
+ pub match_ids: &'a [Uid],
+}
+
+#[derive(Debug)]
+pub struct Submitter<'world>
+{
+ new_events: &'world Lock<NewEvents>,
+}
+
+impl<'world> Submitter<'world>
+{
+ /// Submits a event to be handled later.
+ ///
+ /// # Panics
+ /// Will panic if unable to acquire a read-write lock to the event store.
+ pub fn submit_event(&self, event: &Pair<Uid, Uid>, match_id: Uid)
+ {
+ let mut new_events_lock = self
+ .new_events
+ .write_nonblock()
+ .expect("Failed to acquire read-write lock to new events");
+
+ new_events_lock.push_event_match(event, match_id);
+ }
+
+ pub(crate) fn new(world: &'world World) -> Self
+ {
+ Self { new_events: &world.data.new_events }
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct NewEvents
+{
+ events: Vec<(Uid, Matches)>,
+}
+
+impl NewEvents
+{
+ pub fn push_event_match(&mut self, event: &Pair<Uid, Uid>, match_id: Uid)
+ {
+ let event_id = event.id();
+
+ assert_eq!(event_id.kind(), UidKind::Pair);
+
+ if let Ok(event_index) = self
+ .events
+ .binary_search_by_key(&event_id, |(other_event_id, _)| *other_event_id)
+ {
+ let Some((_, matches)) = self.events.get_mut(event_index) else {
+ unreachable!();
+ };
+
+ matches.sorted_push(match_id);
+
+ return;
+ }
+
+ self.events.insert_at_partition_point_by_key(
+ (event_id, Matches { match_ids: Vec::from([match_id]) }),
+ |(other_event_id, _)| *other_event_id,
+ );
+ }
+
+ pub fn take(&mut self) -> Vec<(Uid, Matches)>
+ {
+ std::mem::take(&mut self.events)
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Matches
+{
+ pub match_ids: Vec<Uid>,
+}
+
+impl Matches
+{
+ fn sorted_push(&mut self, match_id: Uid)
+ {
+ if self.match_ids.binary_search(&match_id).is_ok() {
+ return;
+ }
+
+ self.match_ids
+ .insert_at_partition_point_by_key(match_id, |other_match_id| *other_match_id);
+ }
+}
diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs
index b96f23b..1bc39b1 100644
--- a/ecs/src/event/component.rs
+++ b/ecs/src/event/component.rs
@@ -1,5 +1,9 @@
//! Component events.
+use std::convert::Infallible;
+
+use crate::Component;
+
// TODO: Implement
// /// Pair relation for events emitted when:
// /// a) A entity with the target component is spawned.
@@ -13,3 +17,6 @@
// /// b) A entity with the target component is despawned.
// #[derive(Debug, Component)]
// pub struct Removed(Infallible);
+
+#[derive(Debug, Component)]
+pub struct OnChanged(Infallible);
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
index ab30980..6f49a91 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -14,10 +14,12 @@ use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComp
use crate::component::storage::Storage as ComponentStorage;
use crate::component::{
Component,
+ IntoParts as IntoComponentParts,
Parts as ComponentParts,
Sequence as ComponentSequence,
};
use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle};
+use crate::event::{Emitted as EmittedEvent, NewEvents, Submitter as EventSubmitter};
use crate::extension::{Collector as ExtensionCollector, Extension};
use crate::lock::Lock;
use crate::pair::{ChildOf, DependsOn, Pair, Wildcard};
@@ -33,6 +35,7 @@ use crate::query::{
};
use crate::sole::Sole;
use crate::stats::Stats;
+use crate::system::observer::{Observer, WrapperComponent as ObserverWrapperComponent};
use crate::system::{Callbacks, Metadata as SystemMetadata, System, SystemComponent};
use crate::uid::{Kind as UidKind, Uid};
@@ -103,18 +106,7 @@ impl World
where
Comps: ComponentSequence,
{
- debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
- if let Err(err) = self.data.component_storage.create_entity(entity_uid) {
- tracing::warn!("Failed to create entity: {err}");
- return;
- }
-
- Self::add_entity_components(
- entity_uid,
- components.into_parts_array(),
- &mut self.data.component_storage,
- );
+ self.create_ent(entity_uid, components.into_parts_array());
}
pub fn add_component(&mut self, entity_id: Uid, component_parts: ComponentParts)
@@ -142,32 +134,40 @@ impl World
self.data.sole_storage.insert(sole)
}
- pub fn register_system<'this, SystemImpl>(
+ pub fn register_observer<'this, SystemImpl, ObserverT>(
&'this mut self,
- phase_euid: Uid,
- system: impl System<'this, SystemImpl>,
- )
+ observer: ObserverT,
+ ) where
+ ObserverT: Observer<'this, SystemImpl>,
{
- let (type_erased_system, mut system_callbacks) = system.finish();
+ let (wrapper_comp, mut system_callbacks) = observer.finish_observer();
- let system_ent_id = self.create_entity((
- SystemComponent { system: type_erased_system },
- Pair::new::<DependsOn>(phase_euid),
- ));
+ let ent_id = Uid::new_unique(UidKind::Entity);
- system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id });
+ self.create_ent(
+ ent_id,
+ [wrapper_comp.into_parts()].into_iter().chain(
+ ObserverT::observed_events()
+ .into_iter()
+ .map(IntoComponentParts::into_parts),
+ ),
+ );
+
+ system_callbacks.on_created(self, SystemMetadata { ent_id });
}
- pub fn register_observer_system<'this, SystemImpl>(
+ pub fn register_system<'this, SystemImpl>(
&'this mut self,
+ phase_euid: Uid,
system: impl System<'this, SystemImpl>,
- event: Pair<Uid, Uid>,
)
{
let (type_erased_system, mut system_callbacks) = system.finish();
- let system_ent_id =
- self.create_entity((SystemComponent { system: type_erased_system }, event));
+ let system_ent_id = self.create_entity((
+ SystemComponent { system: type_erased_system },
+ Pair::new::<DependsOn>(phase_euid),
+ ));
system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id });
}
@@ -209,7 +209,12 @@ impl World
unreachable!("Should exist since archetype was found by entity id");
};
- Some(EntityHandle::new(archetype, entity))
+ Some(EntityHandle::new(archetype, entity, self))
+ }
+
+ pub fn event_submitter(&self) -> EventSubmitter<'_>
+ {
+ EventSubmitter::new(self)
}
/// Performs a single tick.
@@ -231,6 +236,8 @@ impl World
self.perform_phases();
+ self.emit_new_events();
+
self.data.component_storage.create_imaginary_archetypes();
self.perform_queued_actions();
@@ -309,6 +316,27 @@ impl World
)
}
+ #[tracing::instrument(skip_all)]
+ fn create_ent(
+ &mut self,
+ entity_uid: Uid,
+ components: impl IntoIterator<Item = ComponentParts>,
+ )
+ {
+ debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
+
+ if let Err(err) = self.data.component_storage.create_entity(entity_uid) {
+ tracing::warn!("Failed to create entity: {err}");
+ return;
+ }
+
+ Self::add_entity_components(
+ entity_uid,
+ components,
+ &mut self.data.component_storage,
+ );
+ }
+
fn query_and_run_systems(&self, phase_euid: Uid)
{
let system_query = Query::<(&SystemComponent,)>::from_flexible_query(
@@ -363,6 +391,26 @@ impl World
}
}
+ fn emit_new_events(&self)
+ {
+ let new_events = self
+ .data
+ .new_events
+ .write_nonblock()
+ .expect("Failed to acquire read-write lock to new events")
+ .take();
+
+ for (event_id, event_matches) in new_events {
+ self.emit_event_observers(
+ event_id,
+ &EmittedEvent {
+ event: event_id,
+ match_ids: &event_matches.match_ids,
+ },
+ );
+ }
+ }
+
#[tracing::instrument(skip_all)]
fn perform_queued_actions(&mut self)
{
@@ -473,6 +521,29 @@ impl World
removed_component_ids
}
+
+ fn emit_event_observers(&self, event_id: Uid, emitted_event: &EmittedEvent<'_>)
+ {
+ assert_eq!(event_id.kind(), UidKind::Pair);
+
+ let query = Query::<(&ObserverWrapperComponent,)>::from_flexible_query(
+ self.flexible_query(
+ QueryTerms::<QUERY_MAX_TERM_CNT>::builder()
+ .with_required([ObserverWrapperComponent::id(), event_id])
+ .build(),
+ ),
+ );
+
+ for (observer_ent_id, (observer,)) in query.iter_with_euids() {
+ unsafe {
+ observer.run(
+ self,
+ SystemMetadata { ent_id: observer_ent_id },
+ emitted_event.clone(),
+ );
+ }
+ }
+ }
}
impl Default for World
@@ -493,31 +564,21 @@ pub enum StepResult
Stop,
}
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct WorldData
{
component_storage: ComponentStorage,
sole_storage: SoleStorage,
action_queue: Rc<ActionQueue>,
+ new_events: Lock<NewEvents>,
}
-impl Default for WorldData
-{
- fn default() -> Self
- {
- Self {
- component_storage: ComponentStorage::default(),
- sole_storage: SoleStorage::default(),
- action_queue: Rc::new(ActionQueue::default()),
- }
- }
-}
-
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct EntityComponentRef<'a>
{
component_id: Uid,
component: &'a ArchetypeEntityComponent,
+ entity_id: Uid,
}
impl<'a> EntityComponentRef<'a>
@@ -533,9 +594,20 @@ impl<'a> EntityComponentRef<'a>
self.component_id
}
- fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent) -> Self
+ #[must_use]
+ pub fn entity_id(&self) -> Uid
{
- Self { component_id, component: comp }
+ self.entity_id
+ }
+
+ fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent, entity_id: Uid)
+ -> Self
+ {
+ Self {
+ component_id,
+ component: comp,
+ entity_id,
+ }
}
}
diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs
index 7b5f54a..553652e 100644
--- a/ecs/src/pair.rs
+++ b/ecs/src/pair.rs
@@ -133,7 +133,7 @@ where
fn get_field<'world>(
entity_handle: &EntityHandle<'world>,
- _world: &'world World,
+ world: &'world World,
) -> Self::Field<'world>
{
let target_component = entity_handle
@@ -141,12 +141,14 @@ where
.next()
.expect("Not possible");
- Self::Field::from_entity_component_ref(&target_component).unwrap_or_else(|err| {
- panic!(
- "Creating handle to target component {} failed: {err}",
- type_name::<Target>()
- );
- })
+ Self::Field::from_entity_component_ref(&target_component, world).unwrap_or_else(
+ |err| {
+ panic!(
+ "Creating handle to target component {} failed: {err}",
+ type_name::<Target>()
+ );
+ },
+ )
}
}
@@ -271,7 +273,7 @@ impl WildcardTargetHandle<'_>
unreachable!();
};
- Some(EntityHandle::new(archetype, archetype_entity))
+ Some(EntityHandle::new(archetype, archetype_entity, self.world))
}
/// Attempts to retrieve the target as a component, returning `None` if the component
@@ -317,6 +319,7 @@ impl WildcardTargetHandle<'_>
{
ComponentHandleMut::<ComponentData>::from_entity_component_ref(
&self.component_ref,
+ self.world,
)
.map_or_else(
|err| match err {
diff --git a/ecs/src/query.rs b/ecs/src/query.rs
index dc9b036..5f13579 100644
--- a/ecs/src/query.rs
+++ b/ecs/src/query.rs
@@ -391,7 +391,7 @@ impl<ComponentT: Component> TermWithField for &mut ComponentT
fn get_field<'world>(
entity_handle: &EntityHandle<'world>,
- _world: &'world World,
+ world: &'world World,
) -> Self::Field<'world>
{
assert_eq!(ComponentT::id().kind(), UidKind::Component);
@@ -410,7 +410,7 @@ impl<ComponentT: Component> TermWithField for &mut ComponentT
);
};
- Self::Field::from_entity_component_ref(&component).unwrap_or_else(|err| {
+ Self::Field::from_entity_component_ref(&component, world).unwrap_or_else(|err| {
panic!(
"Creating handle to component {} failed: {err}",
type_name::<ComponentT>()
diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs
index 0caaa27..936ab82 100644
--- a/ecs/src/query/flexible.rs
+++ b/ecs/src/query/flexible.rs
@@ -36,6 +36,7 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
.zip(archetype.entities())
}) as ComponentIterMapFn,
),
+ world: self.world,
}
}
@@ -65,6 +66,7 @@ impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_T
pub struct Iter<'query>
{
iter: QueryEntityIter<'query>,
+ world: &'query World,
}
impl<'query> Iterator for Iter<'query>
@@ -75,7 +77,7 @@ impl<'query> Iterator for Iter<'query>
{
let (archetype, entity) = self.iter.next()?;
- Some(EntityHandle::new(archetype, entity))
+ Some(EntityHandle::new(archetype, entity, self.world))
}
}
diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs
index c8a96b6..0683918 100644
--- a/ecs/src/query/term.rs
+++ b/ecs/src/query/term.rs
@@ -95,7 +95,7 @@ impl<ComponentT: Component> TermWithField for Option<&mut ComponentT>
fn get_field<'world>(
entity_handle: &crate::entity::Handle<'world>,
- _world: &'world crate::World,
+ world: &'world crate::World,
) -> Self::Field<'world>
{
Some(
@@ -103,6 +103,7 @@ impl<ComponentT: Component> TermWithField for Option<&mut ComponentT>
&entity_handle
.get_matching_components(ComponentT::id())
.next()?,
+ world,
)
.unwrap_or_else(|err| {
panic!(
diff --git a/ecs/src/system.rs b/ecs/src/system.rs
index ae6dd2e..95ab7a8 100644
--- a/ecs/src/system.rs
+++ b/ecs/src/system.rs
@@ -7,6 +7,7 @@ use crate::uid::Uid;
use crate::World;
pub mod initializable;
+pub mod observer;
pub mod stateful;
/// Metadata of a system.
@@ -60,7 +61,7 @@ seq!(C in 1..16 {
impl_system!(C);
});
-pub trait Into<Impl>
+pub trait Into<'world, Impl>
{
type System;
diff --git a/ecs/src/system/observer.rs b/ecs/src/system/observer.rs
new file mode 100644
index 0000000..5455fd4
--- /dev/null
+++ b/ecs/src/system/observer.rs
@@ -0,0 +1,276 @@
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::mem::transmute;
+use std::slice::Iter as SliceIter;
+
+use ecs_macros::Component;
+use seq_macro::seq;
+
+use crate::component::Component;
+use crate::entity::Handle as EntityHandle;
+use crate::event::Emitted as EmittedEvent;
+use crate::pair::Pair;
+use crate::system::{
+ Metadata,
+ NoCallbacks,
+ Param,
+ System,
+ TypeErased as TypeErasedSystem,
+};
+use crate::uid::Uid;
+use crate::util::Array;
+use crate::World;
+
+pub trait Observed
+{
+ type Events: Array<Pair<Uid, Uid>>;
+
+ fn events() -> Self::Events;
+}
+
+impl<Relation, Target> Observed for Pair<Relation, Target>
+where
+ Relation: Component,
+ Target: Component,
+{
+ type Events = [Pair<Uid, Uid>; 1];
+
+ fn events() -> Self::Events
+ {
+ [Pair::new::<Relation>(Target::id())]
+ }
+}
+
+/// Observer system.
+pub trait Observer<'world, Impl>: System<'world, Impl>
+{
+ type ObservedEvents: Array<Pair<Uid, Uid>>;
+
+ fn observed_events() -> Self::ObservedEvents;
+
+ fn finish_observer(self) -> (WrapperComponent, Self::Callbacks);
+}
+
+pub struct Observe<'world, ObservedT: Observed>
+{
+ _pd: PhantomData<ObservedT>,
+ world: &'world World,
+ emitted_event: EmittedEvent<'world>,
+}
+
+impl<'world, ObservedT: Observed> Observe<'world, ObservedT>
+{
+ pub fn new(world: &'world World, emitted_event: EmittedEvent<'world>) -> Self
+ {
+ Self {
+ _pd: PhantomData,
+ world,
+ emitted_event,
+ }
+ }
+
+ #[must_use]
+ pub fn event(&self) -> Uid
+ {
+ self.emitted_event.event
+ }
+}
+
+impl<ObservedT: Observed> Observe<'_, ObservedT>
+{
+ #[must_use]
+ pub fn iter(&self) -> ObserveIter<'_>
+ {
+ ObserveIter {
+ world: self.world,
+ inner: self.emitted_event.match_ids.iter(),
+ }
+ }
+}
+
+impl<'a, ObservedT: Observed> IntoIterator for &'a Observe<'_, ObservedT>
+{
+ type IntoIter = ObserveIter<'a>;
+ type Item = EntityHandle<'a>;
+
+ fn into_iter(self) -> Self::IntoIter
+ {
+ self.iter()
+ }
+}
+
+pub struct ObserveIter<'observe>
+{
+ world: &'observe World,
+ inner: SliceIter<'observe, Uid>,
+}
+
+impl<'observe> Iterator for ObserveIter<'observe>
+{
+ type Item = EntityHandle<'observe>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let target = *self.inner.next()?;
+
+ self.world.get_entity(target)
+ }
+}
+
+macro_rules! impl_observer {
+ ($c: tt) => {
+ seq!(I in 0..$c {
+ impl<'world, ObservedT, Func, #(TParam~I,)*> System<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*)
+ > for Func
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static,
+ #(TParam~I: Param<'world, Input = ()>,)*
+ {
+ type Callbacks = NoCallbacks;
+
+ fn finish(self) -> (TypeErasedSystem, NoCallbacks)
+ {
+ unimplemented!();
+ }
+ }
+
+ impl<'world, ObservedT, Func, #(TParam~I,)*> Observer<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*)
+ > for Func
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static,
+ #(TParam~I: Param<'world, Input = ()>,)*
+ {
+ type ObservedEvents = ObservedT::Events;
+
+ fn observed_events() -> Self::ObservedEvents
+ {
+ ObservedT::events()
+ }
+
+ fn finish_observer(self) -> (WrapperComponent, NoCallbacks)
+ {
+ let wrapper_comp = WrapperComponent {
+ run: Box::new(move |world, metadata, emitted_event| {
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let world = unsafe { &*std::ptr::from_ref(world) };
+
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let emitted_event = unsafe {
+ transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(
+ emitted_event
+ )
+ };
+
+ self(Observe::new(world, emitted_event), #({
+ TParam~I::new(world, &metadata)
+ },)*);
+ }),
+ };
+
+ (wrapper_comp, NoCallbacks)
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 1..16 {
+ impl_observer!(C);
+});
+
+impl<'world, ObservedT, Func> System<'world, fn(Observe<'world, ObservedT>)> for Func
+where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>) + Copy + 'static,
+{
+ type Callbacks = NoCallbacks;
+
+ fn finish(self) -> (TypeErasedSystem, NoCallbacks)
+ {
+ const {
+ panic!("Observers cannot be used as regular systems");
+ }
+ }
+}
+
+impl<'world, ObservedT, Func> Observer<'world, fn(Observe<'world, ObservedT>)> for Func
+where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>) + Copy + 'static,
+{
+ type ObservedEvents = ObservedT::Events;
+
+ fn observed_events() -> Self::ObservedEvents
+ {
+ ObservedT::events()
+ }
+
+ fn finish_observer(self) -> (WrapperComponent, NoCallbacks)
+ {
+ let wrapper_comp = WrapperComponent {
+ run: Box::new(move |world, _metadata, emitted_event| {
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let world = unsafe { &*std::ptr::from_ref(world) };
+
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let emitted_event = unsafe {
+ transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(emitted_event)
+ };
+
+ self(Observe::new(world, emitted_event));
+ }),
+ };
+
+ (wrapper_comp, NoCallbacks)
+ }
+}
+
+#[derive(Component)]
+pub struct WrapperComponent
+{
+ run: Box<RunFn>,
+}
+
+impl WrapperComponent
+{
+ pub fn new(run: impl Fn(&World, Metadata, EmittedEvent<'_>) + 'static) -> Self
+ {
+ Self { run: Box::new(run) }
+ }
+
+ /// Runs the observer system.
+ ///
+ /// # Safety
+ /// `world` must live at least as long as the [`World`] the system belongs to.
+ pub unsafe fn run(
+ &self,
+ world: &World,
+ metadata: Metadata,
+ emitted_event: EmittedEvent<'_>,
+ )
+ {
+ (self.run)(world, metadata, emitted_event);
+ }
+}
+
+impl Debug for WrapperComponent
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ formatter
+ .debug_struct("WrapperComponent")
+ .finish_non_exhaustive()
+ }
+}
+
+type RunFn = dyn Fn(&World, Metadata, EmittedEvent<'_>);
diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs
index 7b2b608..e74ef31 100644
--- a/ecs/src/system/stateful.rs
+++ b/ecs/src/system/stateful.rs
@@ -1,10 +1,18 @@
+use std::mem::transmute;
use std::panic::{RefUnwindSafe, UnwindSafe};
use seq_macro::seq;
use crate::component::local::SystemWithLocalComponents;
use crate::component::Parts as ComponentParts;
+use crate::event::Emitted as EmittedEvent;
use crate::system::initializable::{Initializable, MaybeInitializableParamTuple};
+use crate::system::observer::{
+ Observe,
+ Observed,
+ Observer,
+ WrapperComponent as ObserverWrapperComponent,
+};
use crate::system::{Into as IntoSystem, Metadata, Param, System, TypeErased};
use crate::World;
@@ -68,9 +76,10 @@ macro_rules! impl_system {
}
}
- impl<Func, #(TParam~I,)*> IntoSystem<fn(#(TParam~I,)*)>
+ impl<'world, Func, #(TParam~I,)*> IntoSystem<'world, fn(#(TParam~I,)*)>
for Func
where
+ #(TParam~I: Param<'world>,)*
Func: Fn(#(TParam~I,)*) + Copy + 'static,
{
type System = Stateful<Func>;
@@ -123,3 +132,119 @@ fn init_initializable_params<'world, SystemT, Params>(
{
Params::init_initializable(system, inputs);
}
+
+macro_rules! impl_observer {
+ ($c: tt) => {
+ seq!(I in 0..$c {
+ impl<'world, ObservedT, Func, #(TParam~I,)*> System<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*)
+ > for Stateful<Func>
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static,
+ #(TParam~I: Param<'world>,)*
+ {
+ type Callbacks = Callbacks;
+
+ fn finish(self) -> (TypeErased, Callbacks)
+ {
+ unimplemented!();
+ }
+ }
+
+ impl<'world, ObservedT, Func, #(TParam~I,)*> Initializable<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*)
+ > for Stateful<Func>
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static,
+ #(TParam~I: Param<'world>,)*
+ (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self>
+ {
+ type Inputs = <
+ (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self>
+ >::Inputs;
+
+ fn initialize(mut self, inputs: Self::Inputs) -> Self
+ {
+ init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs);
+
+ self
+ }
+ }
+
+ impl<'world, ObservedT, Func, #(TParam~I,)*> Observer<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*)
+ > for Stateful<Func>
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static,
+ #(TParam~I: Param<'world>,)*
+ {
+ type ObservedEvents = ObservedT::Events;
+
+ fn observed_events() -> Self::ObservedEvents
+ {
+ ObservedT::events()
+ }
+
+ fn finish_observer(self) -> (ObserverWrapperComponent, Callbacks)
+ {
+ let Self { func, local_components } = self;
+
+ let callbacks = Callbacks { local_components };
+
+ let wrapper_comp = ObserverWrapperComponent::new(
+ move |world, metadata, emitted_event| {
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let world = unsafe { &*std::ptr::from_ref(world) };
+
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let emitted_event = unsafe {
+ transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(
+ emitted_event
+ )
+ };
+
+ func(Observe::new(world, emitted_event), #({
+ TParam~I::new(world, &metadata)
+ },)*);
+ },
+ );
+
+ (wrapper_comp, callbacks)
+ }
+ }
+
+ impl<'world, Func, ObservedT, #(TParam~I,)*> IntoSystem<
+ 'world,
+ fn(Observe<'world, ObservedT>,
+ #(TParam~I,)*)
+ > for Func
+ where
+ ObservedT: Observed,
+ #(TParam~I: Param<'world>,)*
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static,
+ {
+ type System = Stateful<Func>;
+
+ fn into_system(self) -> Stateful<Func>
+ {
+ Stateful {
+ func: self,
+ local_components: Vec::new(), // TODO: Use Vec::with_capacity
+ }
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 1..16 {
+ impl_observer!(C);
+});