1use ixa_derive::IxaEvent;
30
31use crate::entity::property::Property;
32use crate::entity::{ContextEntitiesExt, Entity, EntityId};
33use crate::{Context, IxaEvent};
34
35pub(crate) trait PartialPropertyChangeEvent {
38 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 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 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 let property_value_store = context.get_property_value_store_mut::<E, P>();
63 property_value_store
65 .index
66 .remove_entity(&self.0.previous.make_canonical(), self.0.entity_id);
67 property_value_store
69 .index
70 .add_entity(&self.0.current.make_canonical(), self.0.entity_id);
71
72 context.emit_event(self.to_event());
79 }
80}
81
82#[repr(transparent)]
88pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
89 PropertyChangeEvent<E, P>,
90);
91impl<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#[derive(IxaEvent)]
117#[allow(clippy::manual_non_exhaustive)]
118pub struct EntityCreatedEvent<E: Entity> {
119 pub entity_id: EntityId<E>,
121}
122impl<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#[derive(IxaEvent)]
140#[allow(clippy::manual_non_exhaustive)]
141pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
142 pub entity_id: EntityId<E>,
144 pub current: P,
146 pub previous: P,
148}
149impl<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_derived_property!(
174 enum AgeGroup {
175 Child,
176 Adult,
177 },
178 Person,
179 [Age], [], |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 let person_id = context
266 .add_entity((Age(9), RunningShoes(33), RiskCategory::Low))
267 .unwrap();
268 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 context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, IsRunner>| {
302 unreachable!();
303 });
304
305 context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, AgeGroup>| {
307 unreachable!();
308 });
309 }
310}