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        context.index_property::<Person, Age>();
204
205        let flag = Rc::new(RefCell::new(false));
206        let flag_clone = flag.clone();
207        context.subscribe_to_event(move |context, event: EntityCreatedEvent<Person>| {
208            let matching = context.query(with!(Person, Age(18)));
209            assert!(matching.contains(event.entity_id));
210            *flag_clone.borrow_mut() = true;
211            assert_eq!(event.entity_id.0, 0);
212        });
213
214        let _ = context
215            .add_entity::<Person, _>(with!(Person, Age(18), RunningShoes(33), RiskCategory::Low))
216            .unwrap();
217        context.execute();
218        assert!(*flag.borrow());
219    }
220
221    #[test]
222    fn observe_entity_property_change() {
223        let mut context = Context::new();
224
225        let flag = Rc::new(RefCell::new(false));
226        let flag_clone = flag.clone();
227        context.subscribe_to_event(
228            move |_context, event: PropertyChangeEvent<Person, RiskCategory>| {
229                *flag_clone.borrow_mut() = true;
230                assert_eq!(event.entity_id.0, 0, "Entity id is correct");
231                assert_eq!(
232                    event.previous,
233                    RiskCategory::Low,
234                    "Previous value is correct"
235                );
236                assert_eq!(
237                    event.current,
238                    RiskCategory::High,
239                    "Current value is correct"
240                );
241            },
242        );
243
244        let person_id = context
245            .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
246            .unwrap();
247
248        context.set_property(person_id, RiskCategory::High);
249        context.execute();
250        assert!(*flag.borrow());
251    }
252
253    #[test]
254    fn observe_entity_property_change_with_set() {
255        let mut context = Context::new();
256
257        let flag = Rc::new(RefCell::new(false));
258        let flag_clone = flag.clone();
259        context.subscribe_to_event(
260            move |_context, _event: PropertyChangeEvent<Person, RunningShoes>| {
261                *flag_clone.borrow_mut() = true;
262            },
263        );
264        // Does not emit a change event.
265        let person_id = context
266            .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
267            .unwrap();
268        // Emits a change event.
269        context.set_property(person_id, RunningShoes(42));
270        context.execute();
271        assert!(*flag.borrow());
272    }
273
274    #[test]
275    fn get_entity_property_change_event() {
276        let mut context = Context::new();
277        let person = context
278            .add_entity(with!(Person, Age(17), RunningShoes(33), RiskCategory::Low))
279            .unwrap();
280
281        let flag = Rc::new(RefCell::new(false));
282
283        let flag_clone = flag.clone();
284        context.subscribe_to_event(
285            move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
286                assert_eq!(event.entity_id.0, 0);
287                assert_eq!(event.previous, AgeGroup::Child);
288                assert_eq!(event.current, AgeGroup::Adult);
289                *flag_clone.borrow_mut() = true;
290            },
291        );
292        context.set_property(person, Age(18));
293        context.execute();
294        assert!(*flag.borrow());
295    }
296
297    #[test]
298    fn test_person_property_change_event_no_people() {
299        let mut context = Context::new();
300        // Non derived person property -- no problems
301        context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, IsRunner>| {
302            unreachable!();
303        });
304
305        // Derived person property -- can't add an event without people being present
306        context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, AgeGroup>| {
307            unreachable!();
308        });
309    }
310}