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 ixa_derive::IxaEvent;
30
31use crate::entity::property::Property;
32use crate::entity::{ContextEntitiesExt, Entity, EntityId};
33use crate::{Context, IxaEvent};
34
35/// Type-erased interface to `PartialPropertyChangeEvent<E, P>`.
36pub(crate) trait PartialPropertyChangeEvent {
37    fn emit_in_context(self: Box<Self>, context: &mut Context);
38}
39
40impl<E: Entity, P: Property<E>> PartialPropertyChangeEvent
41    for PartialPropertyChangeEventCore<E, P>
42{
43    /// Updates the index with the current property value and emits a change event.
44    fn emit_in_context(self: Box<Self>, context: &mut Context) {
45        let current_value: P = context.get_property(self.0.entity_id);
46        let property_value_store = context.get_property_value_store_mut::<E, P>();
47
48        property_value_store
49            .index
50            .add_entity(&current_value.make_canonical(), self.0.entity_id);
51
52        // We decided not to do the following check.
53        // See `src/entity/context_extension::ContextEntitiesExt::set_property`.
54        // if current_value != self.0.previous {
55        //     context.emit_event(self.to_event(current_value));
56        // }
57
58        context.emit_event(self.to_event(current_value));
59    }
60}
61
62/// Represents a partially created `PropertyChangeEvent` of a derived property during the computation of property
63/// changes during the update of one of its non-derived property dependencies.
64///
65/// A `Box<PartialPropertyChangeEventCore<E, P>>` can be transformed into a `Box<PropertyChangeEvent<E, P>>` in place,
66/// avoiding an allocation.
67#[repr(transparent)]
68pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
69    PropertyChangeEvent<E, P>,
70);
71// We provide blanket impls for these because the compiler isn't smart enough to know
72// `PartialPropertyChangeEvent<E, P>` is always `Copy`/`Clone` if we derive them.
73impl<E: Entity, P: Property<E>> Clone for PartialPropertyChangeEventCore<E, P> {
74    fn clone(&self) -> Self {
75        *self
76    }
77}
78impl<E: Entity, P: Property<E>> Copy for PartialPropertyChangeEventCore<E, P> {}
79
80impl<E: Entity, P: Property<E>> PartialPropertyChangeEventCore<E, P> {
81    pub fn new(entity_id: EntityId<E>, previous_value: P) -> Self {
82        Self(PropertyChangeEvent {
83            entity_id,
84            current: previous_value,
85            previous: previous_value,
86        })
87    }
88
89    pub fn to_event(mut self, current_value: P) -> PropertyChangeEvent<E, P> {
90        self.0.current = current_value;
91        self.0
92    }
93}
94
95/// Emitted when a new entity is created.
96/// These should not be emitted outside this module.
97#[derive(IxaEvent)]
98#[allow(clippy::manual_non_exhaustive)]
99pub struct EntityCreatedEvent<E: Entity> {
100    /// The [`EntityId<E>`] of the new entity.
101    pub entity_id: EntityId<E>,
102}
103// We provide blanket impls for these because the compiler isn't smart enough to know
104// this type is always `Copy`/`Clone` if we derive them.
105impl<E: Entity> Copy for EntityCreatedEvent<E> {}
106impl<E: Entity> Clone for EntityCreatedEvent<E> {
107    fn clone(&self) -> Self {
108        *self
109    }
110}
111
112impl<E: Entity> EntityCreatedEvent<E> {
113    pub fn new(entity_id: EntityId<E>) -> Self {
114        Self { entity_id }
115    }
116}
117
118/// Emitted when a property is updated.
119/// These should not be emitted outside this module.
120#[derive(IxaEvent)]
121#[allow(clippy::manual_non_exhaustive)]
122pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
123    /// The [`EntityId<E>`] that changed
124    pub entity_id: EntityId<E>,
125    /// The new value
126    pub current: P,
127    /// The old value
128    pub previous: P,
129}
130// We provide blanket impls for these because the compiler isn't smart enough to know
131// this type is always `Copy`/`Clone` if we derive them.
132impl<E: Entity, P: Property<E>> Clone for PropertyChangeEvent<E, P> {
133    fn clone(&self) -> Self {
134        *self
135    }
136}
137impl<E: Entity, P: Property<E>> Copy for PropertyChangeEvent<E, P> {}
138
139#[cfg(test)]
140mod tests {
141    use std::cell::RefCell;
142    use std::rc::Rc;
143
144    use super::*;
145    use crate::{define_derived_property, define_entity, define_property, Context};
146
147    define_entity!(Person);
148
149    define_property!(struct Age(u8), Person );
150
151    // define_global_property!(Threshold, u8);
152
153    // An enum
154    define_derived_property!(
155        enum AgeGroup {
156            Child,
157            Adult,
158        },
159        Person,
160        [Age], // Depends only on age
161        [],    // No global dependencies
162        |age| {
163            let age: Age = age;
164            if age.0 < 18 {
165                AgeGroup::Child
166            } else {
167                AgeGroup::Adult
168            }
169        }
170    );
171
172    define_property!(
173        enum RiskCategory {
174            High,
175            Low,
176        },
177        Person
178    );
179
180    define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
181
182    define_property!(struct RunningShoes(u8), Person );
183
184    #[test]
185    fn observe_entity_addition() {
186        let mut context = Context::new();
187
188        let flag = Rc::new(RefCell::new(false));
189        let flag_clone = flag.clone();
190        context.subscribe_to_event(move |_context, event: EntityCreatedEvent<Person>| {
191            *flag_clone.borrow_mut() = true;
192            assert_eq!(event.entity_id.0, 0);
193        });
194
195        let _ = context
196            .add_entity::<Person, _>((Age(18), RunningShoes(33), RiskCategory::Low))
197            .unwrap();
198        context.execute();
199        assert!(*flag.borrow());
200    }
201
202    #[test]
203    fn observe_entity_property_change() {
204        let mut context = Context::new();
205
206        let flag = Rc::new(RefCell::new(false));
207        let flag_clone = flag.clone();
208        context.subscribe_to_event(
209            move |_context, event: PropertyChangeEvent<Person, RiskCategory>| {
210                *flag_clone.borrow_mut() = true;
211                assert_eq!(event.entity_id.0, 0, "Entity id is correct");
212                assert_eq!(
213                    event.previous,
214                    RiskCategory::Low,
215                    "Previous value is correct"
216                );
217                assert_eq!(
218                    event.current,
219                    RiskCategory::High,
220                    "Current value is correct"
221                );
222            },
223        );
224
225        let person_id = context
226            .add_entity((Age(9), RunningShoes(33), RiskCategory::Low))
227            .unwrap();
228
229        context.set_property(person_id, RiskCategory::High);
230        context.execute();
231        assert!(*flag.borrow());
232    }
233
234    #[test]
235    fn observe_entity_property_change_with_set() {
236        let mut context = Context::new();
237
238        let flag = Rc::new(RefCell::new(false));
239        let flag_clone = flag.clone();
240        context.subscribe_to_event(
241            move |_context, _event: PropertyChangeEvent<Person, RunningShoes>| {
242                *flag_clone.borrow_mut() = true;
243            },
244        );
245        // Does not emit a change event.
246        let person_id = context
247            .add_entity((Age(9), RunningShoes(33), RiskCategory::Low))
248            .unwrap();
249        // Emits a change event.
250        context.set_property(person_id, RunningShoes(42));
251        context.execute();
252        assert!(*flag.borrow());
253    }
254
255    #[test]
256    fn get_entity_property_change_event() {
257        let mut context = Context::new();
258        let person = context
259            .add_entity((Age(17), RunningShoes(33), RiskCategory::Low))
260            .unwrap();
261
262        let flag = Rc::new(RefCell::new(false));
263
264        let flag_clone = flag.clone();
265        context.subscribe_to_event(
266            move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
267                assert_eq!(event.entity_id.0, 0);
268                assert_eq!(event.previous, AgeGroup::Child);
269                assert_eq!(event.current, AgeGroup::Adult);
270                *flag_clone.borrow_mut() = true;
271            },
272        );
273        context.set_property(person, Age(18));
274        context.execute();
275        assert!(*flag.borrow());
276    }
277
278    #[test]
279    fn test_person_property_change_event_no_people() {
280        let mut context = Context::new();
281        // Non derived person property -- no problems
282        context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, IsRunner>| {
283            unreachable!();
284        });
285
286        // Derived person property -- can't add an event without people being present
287        context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, AgeGroup>| {
288            unreachable!();
289        });
290    }
291}