Skip to main content

ixa/entity/
events.rs

1/*!
2
3`EntityCreatedEvent` and `EntityPropertyChangeEvent` types are emitted when an entity is created or an entity's
4property value is changed.
5
6Client code can subscribe to these events with the `Context::subscribe_to_event<IxaEvent>(handler)` method:
7
8```rust,ignore
9// Suppose `InfectionStatus` is a property of the entity `Person`.
10// A type alias for property change events makes code more concise and readable.
11pub type InfectionStatusEvent = PropertyChangeEvent<Person, InfectionStatus>;
12// Suppose we want to execute the following function whenever `InfectionStatus` changes.
13fn handle_infection_status_change(context: &mut Context, event: InfectionStatusEvent){
14    // ... handle the infection status change event ...
15}
16// We do so by subscribing to this event.
17context.subscribe_to_event::<InfectionStatusEvent>(handle_infection_status_change);
18```
19
20
21A non-derived property sits on the type-erased side of the boundary of its dependent's `PropertyValueStore`, so it
22needs to somehow trigger the creation of and emit the change events for its dependents in a type-erased way.
23
24Property change events are triggered and collected on the outside of the type-erased `PropertyValueStore` boundary,
25because a non-derived p
26
27*/
28
29use smallbox::space::S4;
30use smallbox::SmallBox;
31
32use crate::entity::property::Property;
33use crate::entity::{ContextEntitiesExt, Entity, EntityId};
34use crate::{Context, IxaEvent};
35
36// We choose the size parameter for `PartialPropertyChangeEventBox` based on the assumption that
37// most properties are 64 bits or fewer. The concrete object behind `PartialPropertyChangeEventBox`
38// (the alias for the `SmallBox`) is `PartialPropertyChangeEventCore<E, P>`, which is
39// `#[repr(transparent)]` over `PropertyChangeEvent<E, P>`. That event stores
40//
41// - `EntityId<E>`, one `usize`, so 8 bytes.
42// - `current`: a property value, typically <= 8 bytes
43// - `current`: a property value, typically <= 8 bytes
44//
45// That puts the payload at 24 bytes, with 8-byte alignment. The `S4` size is 4 `usize`s of inline
46// storage, i.e. 32 bytes, and inline storage is used when the payload size and alignment fit. So
47// `S4` comfortably holds the common 24-byte case inline with 8 bytes of slack.
48pub(crate) type PartialPropertyChangeEventBox = SmallBox<dyn PartialPropertyChangeEvent, S4>;
49
50/// Type-erased interface to `PartialPropertyChangeEvent<E, P>`.
51/// Interacts with the index on behalf of the erased type.
52pub(crate) trait PartialPropertyChangeEvent {
53    /// Updates the index with the current property value and emits a change event.
54    fn emit_in_context(&mut self, context: &mut Context);
55}
56
57impl<E: Entity, P: Property<E>> PartialPropertyChangeEvent
58    for PartialPropertyChangeEventCore<E, P>
59{
60    /// Updates the index with the current property value and emits a change event.
61    fn emit_in_context(&mut self, context: &mut Context) {
62        self.0.current = context.get_property(self.0.entity_id);
63
64        {
65            // Update value change counters
66            let property_value_store = context.get_property_value_store::<E, P>();
67            if self.0.current != self.0.previous {
68                for counter in &property_value_store.value_change_counters {
69                    counter
70                        .borrow_mut()
71                        .update(self.0.entity_id, self.0.current, context);
72                }
73            }
74        }
75
76        // Now update the indexes
77        let property_value_store = context.get_property_value_store_mut::<E, P>();
78        if let Some(index) = property_value_store.index.as_mut() {
79            // Out with the old
80            index.remove_entity(&self.0.previous, self.0.entity_id);
81            // In with the new
82            index.add_entity(&self.0.current, self.0.entity_id);
83        }
84
85        // We decided not to do the following check.
86        // See `src/entity/context_extension::ContextEntitiesExt::set_property`.
87        // if self.0.current != self.0.previous {
88        //     context.emit_event(self.to_event());
89        // }
90
91        context.emit_event(self.to_event());
92    }
93}
94
95/// Represents a partially created `PropertyChangeEvent` of a derived property during the computation of property
96/// changes during the update of one of its non-derived property dependencies.
97///
98/// A `PartialPropertyChangeEventCore<E, P>` is layout-compatible with
99/// `PropertyChangeEvent<E, P>`, so converting via `to_event()` does not require an extra heap
100/// allocation.
101#[repr(transparent)]
102pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
103    PropertyChangeEvent<E, P>,
104);
105// We provide blanket impls for these because the compiler isn't smart enough to know
106// `PartialPropertyChangeEvent<E, P>` is always `Copy`/`Clone` if we derive them.
107impl<E: Entity, P: Property<E>> Clone for PartialPropertyChangeEventCore<E, P> {
108    fn clone(&self) -> Self {
109        *self
110    }
111}
112impl<E: Entity, P: Property<E>> Copy for PartialPropertyChangeEventCore<E, P> {}
113
114impl<E: Entity, P: Property<E>> PartialPropertyChangeEventCore<E, P> {
115    pub fn new(entity_id: EntityId<E>, previous_value: P) -> Self {
116        Self(PropertyChangeEvent {
117            entity_id,
118            current: previous_value,
119            previous: previous_value,
120        })
121    }
122
123    pub fn to_event(self) -> PropertyChangeEvent<E, P> {
124        self.0
125    }
126}
127
128/// Emitted when a new entity is created.
129/// These should not be emitted outside this module.
130#[derive(IxaEvent)]
131pub struct EntityCreatedEvent<E: Entity> {
132    /// The [`EntityId<E>`] of the new entity.
133    pub entity_id: EntityId<E>,
134}
135
136impl<E: Entity> EntityCreatedEvent<E> {
137    #[must_use]
138    pub fn new(entity_id: EntityId<E>) -> Self {
139        Self { entity_id }
140    }
141}
142
143/// Emitted when a property is updated.
144/// These should not be emitted outside this module.
145#[derive(IxaEvent)]
146pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
147    /// The [`EntityId<E>`] that changed
148    pub entity_id: EntityId<E>,
149    /// The new value
150    pub current: P,
151    /// The old value
152    pub previous: P,
153}
154
155#[cfg(test)]
156mod tests {
157    use std::cell::RefCell;
158    use std::rc::Rc;
159
160    use super::*;
161    use crate::{define_derived_property, define_entity, define_property, with, Context};
162
163    define_entity!(Person);
164
165    define_property!(struct Age(u8), Person );
166
167    // define_global_property!(Threshold, u8);
168
169    // An enum
170    define_derived_property!(
171        enum AgeGroup {
172            Child,
173            Adult,
174        },
175        Person,
176        [Age], // Depends only on age
177        [],    // No global dependencies
178        |age| {
179            let age: Age = age;
180            if age.0 < 18 {
181                AgeGroup::Child
182            } else {
183                AgeGroup::Adult
184            }
185        }
186    );
187
188    define_property!(
189        enum RiskCategory {
190            High,
191            Low,
192        },
193        Person
194    );
195
196    define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
197
198    define_property!(struct RunningShoes(u8), Person );
199
200    #[test]
201    fn observe_entity_addition() {
202        let mut context = Context::new();
203
204        let flag = Rc::new(RefCell::new(false));
205        let flag_clone = flag.clone();
206        context.subscribe_to_event(move |_context, event: EntityCreatedEvent<Person>| {
207            *flag_clone.borrow_mut() = true;
208            assert_eq!(event.entity_id.0, 0);
209        });
210
211        let _ = context
212            .add_entity::<Person, _>(with!(Person, Age(18), RunningShoes(33), RiskCategory::Low))
213            .unwrap();
214        context.execute();
215        assert!(*flag.borrow());
216    }
217
218    #[test]
219    fn observe_entity_property_change() {
220        let mut context = Context::new();
221
222        let flag = Rc::new(RefCell::new(false));
223        let flag_clone = flag.clone();
224        context.subscribe_to_event(
225            move |_context, event: PropertyChangeEvent<Person, RiskCategory>| {
226                *flag_clone.borrow_mut() = true;
227                assert_eq!(event.entity_id.0, 0, "Entity id is correct");
228                assert_eq!(
229                    event.previous,
230                    RiskCategory::Low,
231                    "Previous value is correct"
232                );
233                assert_eq!(
234                    event.current,
235                    RiskCategory::High,
236                    "Current value is correct"
237                );
238            },
239        );
240
241        let person_id = context
242            .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
243            .unwrap();
244
245        context.set_property(person_id, RiskCategory::High);
246        context.execute();
247        assert!(*flag.borrow());
248    }
249
250    #[test]
251    fn observe_entity_property_change_with_set() {
252        let mut context = Context::new();
253
254        let flag = Rc::new(RefCell::new(false));
255        let flag_clone = flag.clone();
256        context.subscribe_to_event(
257            move |_context, _event: PropertyChangeEvent<Person, RunningShoes>| {
258                *flag_clone.borrow_mut() = true;
259            },
260        );
261        // Does not emit a change event.
262        let person_id = context
263            .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
264            .unwrap();
265        // Emits a change event.
266        context.set_property(person_id, RunningShoes(42));
267        context.execute();
268        assert!(*flag.borrow());
269    }
270
271    #[test]
272    fn get_entity_property_change_event() {
273        let mut context = Context::new();
274        let person = context
275            .add_entity(with!(Person, Age(17), RunningShoes(33), RiskCategory::Low))
276            .unwrap();
277
278        let flag = Rc::new(RefCell::new(false));
279
280        let flag_clone = flag.clone();
281        context.subscribe_to_event(
282            move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
283                assert_eq!(event.entity_id.0, 0);
284                assert_eq!(event.previous, AgeGroup::Child);
285                assert_eq!(event.current, AgeGroup::Adult);
286                *flag_clone.borrow_mut() = true;
287            },
288        );
289        context.set_property(person, Age(18));
290        context.execute();
291        assert!(*flag.borrow());
292    }
293
294    #[test]
295    fn test_person_property_change_event_no_people() {
296        let mut context = Context::new();
297        // Non derived person property -- no problems
298        context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, IsRunner>| {
299            unreachable!();
300        });
301
302        // Derived person property -- can't add an event without people being present
303        context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, AgeGroup>| {
304            unreachable!();
305        });
306    }
307}