Skip to main content

moirai/event/
component.rs

1//! Component lifecycle event payloads and per-component channel wiring.
2//!
3//! Each registered component index receives paired [`ComponentAdded`] and [`ComponentRemoved`]
4//! channels emitted after successful structural commits.
5
6use alloc::vec::Vec;
7
8use crate::component::ComponentId;
9use crate::entity::EntityId;
10use crate::event::queue::EventStorage;
11use crate::event::registry::{EventId, EventOptions, EventRegistrationError, EventRegistry};
12use crate::operation::StageOperation;
13use crate::world::{WorldError, WorldOwner};
14
15/// Payload emitted after a component addition commits.
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct ComponentAdded {
18    /// Entity that received the component.
19    pub entity: EntityId,
20    /// Registered component handle for the added type.
21    pub component: ComponentId,
22}
23
24/// Payload emitted after a component removal commits.
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct ComponentRemoved {
27    /// Entity that lost the component.
28    pub entity: EntityId,
29    /// Registered component handle for the removed type.
30    pub component: ComponentId,
31}
32
33#[derive(Copy, Clone, Debug, Eq, PartialEq)]
34pub(crate) enum LifecycleKind {
35    Added,
36    Removed,
37}
38
39pub(crate) struct ComponentLifecycleRegistry {
40    added_event_indices: Vec<Option<u32>>,
41    removed_event_indices: Vec<Option<u32>>,
42}
43
44impl ComponentLifecycleRegistry {
45    pub fn new() -> Self {
46        Self {
47            added_event_indices: Vec::new(),
48            removed_event_indices: Vec::new(),
49        }
50    }
51
52    pub fn register_component(
53        &mut self,
54        registry: &mut EventRegistry,
55        owner: &WorldOwner,
56        component_index: usize,
57    ) -> Result<(), EventRegistrationError> {
58        self.ensure_slots(component_index);
59        if self.added_event_indices[component_index].is_none() {
60            let id = registry
61                .register_lifecycle::<ComponentAdded>(
62                    owner,
63                    component_index,
64                    LifecycleKind::Added,
65                    EventOptions::frame(StageOperation::Update),
66                )
67                .expect("lifecycle added registration is infallible");
68            self.added_event_indices[component_index] = Some(id.index() as u32);
69        }
70        if self.removed_event_indices[component_index].is_none() {
71            let id = registry
72                .register_lifecycle::<ComponentRemoved>(
73                    owner,
74                    component_index,
75                    LifecycleKind::Removed,
76                    EventOptions::frame(StageOperation::Update),
77                )
78                .expect("lifecycle removed registration is infallible");
79            self.removed_event_indices[component_index] = Some(id.index() as u32);
80        }
81        Ok(())
82    }
83
84    pub fn ensure_storage_channels(
85        &self,
86        storage: &mut EventStorage,
87        registry: &EventRegistry,
88        owner: &WorldOwner,
89    ) {
90        for index in 0..self.added_event_indices.len() {
91            if let Some(event_index) = self.added_event_indices[index] {
92                let event_id = EventId::new(owner.clone(), event_index);
93                if let Some(options) = registry.options(&event_id) {
94                    storage.ensure_channel(event_index as usize, options.retention())
95                }
96            }
97            if let Some(event_index) = self.removed_event_indices[index] {
98                let event_id = EventId::new(owner.clone(), event_index);
99                if let Some(options) = registry.options(&event_id) {
100                    storage.ensure_channel(event_index as usize, options.retention())
101                }
102            }
103        }
104    }
105
106    pub fn emit_added(
107        &self,
108        storage: &mut EventStorage,
109        owner: &WorldOwner,
110        entity: EntityId,
111        component_index: usize,
112    ) -> Result<(), WorldError> {
113        let Some(event_index) = self
114            .added_event_indices
115            .get(component_index)
116            .and_then(|id| *id)
117        else {
118            return Ok(());
119        };
120        let event_id = EventId::new(owner.clone(), event_index);
121        let component = ComponentId::new(owner.clone(), component_index as u32);
122        storage.send(&event_id, ComponentAdded { entity, component })
123    }
124
125    pub fn emit_removed(
126        &self,
127        storage: &mut EventStorage,
128        owner: &WorldOwner,
129        entity: EntityId,
130        component_index: usize,
131    ) -> Result<(), WorldError> {
132        let Some(event_index) = self
133            .removed_event_indices
134            .get(component_index)
135            .and_then(|id| *id)
136        else {
137            return Ok(());
138        };
139        let event_id = EventId::new(owner.clone(), event_index);
140        let component = ComponentId::new(owner.clone(), component_index as u32);
141        storage.send(&event_id, ComponentRemoved { entity, component })
142    }
143
144    pub fn added_event_id(&self, owner: &WorldOwner, component_index: usize) -> Option<EventId> {
145        self.added_event_indices
146            .get(component_index)
147            .and_then(|index| *index)
148            .map(|index| EventId::new(owner.clone(), index))
149    }
150
151    pub fn removed_event_id(&self, owner: &WorldOwner, component_index: usize) -> Option<EventId> {
152        self.removed_event_indices
153            .get(component_index)
154            .and_then(|index| *index)
155            .map(|index| EventId::new(owner.clone(), index))
156    }
157
158    fn ensure_slots(&mut self, component_index: usize) {
159        while self.added_event_indices.len() <= component_index {
160            self.added_event_indices.push(None);
161            self.removed_event_indices.push(None);
162        }
163    }
164
165    #[cfg(test)]
166    pub(crate) fn clear_added_event_for_test(&mut self, component_index: usize) {
167        if let Some(slot) = self.added_event_indices.get_mut(component_index) {
168            *slot = None;
169        }
170    }
171
172    #[cfg(test)]
173    pub(crate) fn clear_removed_event_for_test(&mut self, component_index: usize) {
174        if let Some(slot) = self.removed_event_indices.get_mut(component_index) {
175            *slot = None;
176        }
177    }
178}
179
180impl Default for ComponentLifecycleRegistry {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::component::ComponentId;
190
191    #[test]
192    fn default_registry_has_no_slots() {
193        let registry = ComponentLifecycleRegistry::default();
194        assert!(registry.added_event_id(&WorldOwner::new(), 0).is_none());
195    }
196
197    #[test]
198    fn register_component_is_idempotent_for_same_index() {
199        let owner = WorldOwner::new();
200        let mut events = EventRegistry::new();
201        let mut lifecycle = ComponentLifecycleRegistry::new();
202        lifecycle
203            .register_component(&mut events, &owner, 0)
204            .expect("first");
205        lifecycle
206            .register_component(&mut events, &owner, 0)
207            .expect("repeat");
208        assert_eq!(events.len(), 2);
209    }
210
211    #[test]
212    fn emit_without_lifecycle_channel_is_noop() {
213        let owner = WorldOwner::new();
214        let lifecycle = ComponentLifecycleRegistry::new();
215        let mut storage = EventStorage::new(0);
216        let entity = EntityId::from_parts(0, 1);
217        lifecycle
218            .emit_added(&mut storage, &owner, entity, 0)
219            .expect("noop added");
220        lifecycle
221            .emit_removed(&mut storage, &owner, entity, 0)
222            .expect("noop removed");
223    }
224
225    #[test]
226    fn register_component_opens_sparse_lifecycle_slots() {
227        let owner = WorldOwner::new();
228        let mut registry = EventRegistry::new();
229        let mut lifecycle = ComponentLifecycleRegistry::new();
230        lifecycle
231            .register_component(&mut registry, &owner, 4)
232            .expect("register");
233        let mut storage = EventStorage::new(0);
234        lifecycle.ensure_storage_channels(&mut storage, &registry, &owner);
235        let entity = EntityId::from_parts(0, 1);
236        let component = ComponentId::new(owner.clone(), 4);
237        let added = lifecycle.added_event_id(&owner, 4).expect("added");
238        storage
239            .send(
240                &added,
241                ComponentAdded {
242                    entity,
243                    component: component.clone(),
244                },
245            )
246            .expect("send added");
247    }
248
249    #[test]
250    fn ensure_storage_channels_opens_registered_lifecycle_events() {
251        let owner = WorldOwner::new();
252        let mut registry = EventRegistry::new();
253        let mut lifecycle = ComponentLifecycleRegistry::new();
254        lifecycle
255            .register_component(&mut registry, &owner, 0)
256            .expect("register");
257        let mut storage = EventStorage::new(2);
258        lifecycle.ensure_storage_channels(&mut storage, &registry, &owner);
259        let entity = EntityId::from_parts(0, 1);
260        let component = ComponentId::new(owner.clone(), 0);
261        let added = lifecycle.added_event_id(&owner, 0).expect("added");
262        storage
263            .send(
264                &added,
265                ComponentAdded {
266                    entity,
267                    component: component.clone(),
268                },
269            )
270            .expect("send added");
271        let removed = lifecycle.removed_event_id(&owner, 0).expect("removed");
272        storage
273            .send(&removed, ComponentRemoved { entity, component })
274            .expect("send removed");
275
276        let empty_registry = EventRegistry::new();
277        lifecycle.ensure_storage_channels(&mut storage, &empty_registry, &owner);
278
279        lifecycle.clear_added_event_for_test(0);
280        lifecycle.clear_removed_event_for_test(0);
281        assert!(lifecycle.added_event_id(&owner, 0).is_none());
282        assert!(lifecycle.removed_event_id(&owner, 0).is_none());
283        lifecycle.clear_added_event_for_test(99);
284        lifecycle.clear_removed_event_for_test(99);
285    }
286}