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>`.
36/// Interacts with the index on behalf of the erased type.
37pub(crate) trait PartialPropertyChangeEvent {
38    /// Updates the index with the current property value and emits a change event.
39    fn emit_in_context(self: Box<Self>, context: &mut Context);
40}
41
42impl<E: Entity, P: Property<E>> PartialPropertyChangeEvent
43    for PartialPropertyChangeEventCore<E, P>
44{
45    /// Updates the index with the current property value and emits a change event.
46    fn emit_in_context(mut self: Box<Self>, context: &mut Context) {
47        self.0.current = context.get_property(self.0.entity_id);
48
49        {
50            // Update value change counters
51            let property_value_store = context.get_property_value_store::<E, P>();
52            if self.0.current != self.0.previous {
53                for counter in &property_value_store.value_change_counters {
54                    counter
55                        .borrow_mut()
56                        .update(self.0.entity_id, self.0.current, context);
57                }
58            }
59        }
60
61        // Now update the indexes
62        let property_value_store = context.get_property_value_store_mut::<E, P>();
63        // Out with the old
64        property_value_store
65            .index
66            .remove_entity(&self.0.previous.make_canonical(), self.0.entity_id);
67        // In with the new
68        property_value_store
69            .index
70            .add_entity(&self.0.current.make_canonical(), self.0.entity_id);
71
72        // We decided not to do the following check.
73        // See `src/entity/context_extension::ContextEntitiesExt::set_property`.
74        // if self.0.current != self.0.previous {
75        //     context.emit_event(self.to_event());
76        // }
77
78        context.emit_event(self.to_event());
79    }
80}
81
82/// Represents a partially created `PropertyChangeEvent` of a derived property during the computation of property
83/// changes during the update of one of its non-derived property dependencies.
84///
85/// A `Box<PartialPropertyChangeEventCore<E, P>>` can be transformed into a `Box<PropertyChangeEvent<E, P>>` in place,
86/// avoiding an allocation.
87#[repr(transparent)]
88pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
89    PropertyChangeEvent<E, P>,
90);
91// We provide blanket impls for these because the compiler isn't smart enough to know
92// `PartialPropertyChangeEvent<E, P>` is always `Copy`/`Clone` if we derive them.
93impl<E: Entity, P: Property<E>> Clone for PartialPropertyChangeEventCore<E, P> {
94    fn clone(&self) -> Self {
95        *self
96    }
97}
98impl<E: Entity, P: Property<E>> Copy for PartialPropertyChangeEventCore<E, P> {}
99
100impl<E: Entity, P: Property<E>> PartialPropertyChangeEventCore<E, P> {
101    pub fn new(entity_id: EntityId<E>, previous_value: P) -> Self {
102        Self(PropertyChangeEvent {
103            entity_id,
104            current: previous_value,
105            previous: previous_value,
106        })
107    }
108
109    pub fn to_event(self) -> PropertyChangeEvent<E, P> {
110        self.0
111    }
112}
113
114/// Emitted when a new entity is created.
115/// These should not be emitted outside this module.
116#[derive(IxaEvent)]
117#[allow(clippy::manual_non_exhaustive)]
118pub struct EntityCreatedEvent<E: Entity> {
119    /// The [`EntityId<E>`] of the new entity.
120    pub entity_id: EntityId<E>,
121}
122// We provide blanket impls for these because the compiler isn't smart enough to know
123// this type is always `Copy`/`Clone` if we derive them.
124impl<E: Entity> Copy for EntityCreatedEvent<E> {}
125impl<E: Entity> Clone for EntityCreatedEvent<E> {
126    fn clone(&self) -> Self {
127        *self
128    }
129}
130
131impl<E: Entity> EntityCreatedEvent<E> {
132    pub fn new(entity_id: EntityId<E>) -> Self {
133        Self { entity_id }
134    }
135}
136
137/// Emitted when a property is updated.
138/// These should not be emitted outside this module.
139#[derive(IxaEvent)]
140#[allow(clippy::manual_non_exhaustive)]
141pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
142    /// The [`EntityId<E>`] that changed
143    pub entity_id: EntityId<E>,
144    /// The new value
145    pub current: P,
146    /// The old value
147    pub previous: P,
148}
149// We provide blanket impls for these because the compiler isn't smart enough to know
150// this type is always `Copy`/`Clone` if we derive them.
151impl<E: Entity, P: Property<E>> Clone for PropertyChangeEvent<E, P> {
152    fn clone(&self) -> Self {
153        *self
154    }
155}
156impl<E: Entity, P: Property<E>> Copy for PropertyChangeEvent<E, P> {}
157
158#[cfg(test)]
159mod tests {
160    use std::cell::RefCell;
161    use std::rc::Rc;
162
163    use super::*;
164    use crate::{define_derived_property, define_entity, define_property, Context};
165
166    define_entity!(Person);
167
168    define_property!(struct Age(u8), Person );
169
170    // define_global_property!(Threshold, u8);
171
172    // An enum
173    define_derived_property!(
174        enum AgeGroup {
175            Child,
176            Adult,
177        },
178        Person,
179        [Age], // Depends only on age
180        [],    // No global dependencies
181        |age| {
182            let age: Age = age;
183            if age.0 < 18 {
184                AgeGroup::Child
185            } else {
186                AgeGroup::Adult
187            }
188        }
189    );
190
191    define_property!(
192        enum RiskCategory {
193            High,
194            Low,
195        },
196        Person
197    );
198
199    define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
200
201    define_property!(struct RunningShoes(u8), Person );
202
203    #[test]
204    fn observe_entity_addition() {
205        let mut context = Context::new();
206
207        let flag = Rc::new(RefCell::new(false));
208        let flag_clone = flag.clone();
209        context.subscribe_to_event(move |_context, event: EntityCreatedEvent<Person>| {
210            *flag_clone.borrow_mut() = true;
211            assert_eq!(event.entity_id.0, 0);
212        });
213
214        let _ = context
215            .add_entity::<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((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((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((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}