1use ixa_derive::IxaEvent;
30use smallbox::space::S4;
31use smallbox::SmallBox;
32
33use crate::entity::property::Property;
34use crate::entity::{ContextEntitiesExt, Entity, EntityId};
35use crate::{Context, IxaEvent};
36
37pub(crate) type PartialPropertyChangeEventBox = SmallBox<dyn PartialPropertyChangeEvent, S4>;
50
51pub(crate) trait PartialPropertyChangeEvent {
54 fn emit_in_context(&mut self, context: &mut Context);
56}
57
58impl<E: Entity, P: Property<E>> PartialPropertyChangeEvent
59 for PartialPropertyChangeEventCore<E, P>
60{
61 fn emit_in_context(&mut self, context: &mut Context) {
63 self.0.current = context.get_property(self.0.entity_id);
64
65 {
66 let property_value_store = context.get_property_value_store::<E, P>();
68 if self.0.current != self.0.previous {
69 for counter in &property_value_store.value_change_counters {
70 counter
71 .borrow_mut()
72 .update(self.0.entity_id, self.0.current, context);
73 }
74 }
75 }
76
77 let property_value_store = context.get_property_value_store_mut::<E, P>();
79 property_value_store
81 .index
82 .remove_entity(&self.0.previous.make_canonical(), self.0.entity_id);
83 property_value_store
85 .index
86 .add_entity(&self.0.current.make_canonical(), self.0.entity_id);
87
88 context.emit_event(self.to_event());
95 }
96}
97
98#[repr(transparent)]
105pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
106 PropertyChangeEvent<E, P>,
107);
108impl<E: Entity, P: Property<E>> Clone for PartialPropertyChangeEventCore<E, P> {
111 fn clone(&self) -> Self {
112 *self
113 }
114}
115impl<E: Entity, P: Property<E>> Copy for PartialPropertyChangeEventCore<E, P> {}
116
117impl<E: Entity, P: Property<E>> PartialPropertyChangeEventCore<E, P> {
118 pub fn new(entity_id: EntityId<E>, previous_value: P) -> Self {
119 Self(PropertyChangeEvent {
120 entity_id,
121 current: previous_value,
122 previous: previous_value,
123 })
124 }
125
126 pub fn to_event(self) -> PropertyChangeEvent<E, P> {
127 self.0
128 }
129}
130
131#[derive(IxaEvent)]
134#[allow(clippy::manual_non_exhaustive)]
135pub struct EntityCreatedEvent<E: Entity> {
136 pub entity_id: EntityId<E>,
138}
139impl<E: Entity> Copy for EntityCreatedEvent<E> {}
142impl<E: Entity> Clone for EntityCreatedEvent<E> {
143 fn clone(&self) -> Self {
144 *self
145 }
146}
147
148impl<E: Entity> EntityCreatedEvent<E> {
149 pub fn new(entity_id: EntityId<E>) -> Self {
150 Self { entity_id }
151 }
152}
153
154#[derive(IxaEvent)]
157#[allow(clippy::manual_non_exhaustive)]
158pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
159 pub entity_id: EntityId<E>,
161 pub current: P,
163 pub previous: P,
165}
166impl<E: Entity, P: Property<E>> Clone for PropertyChangeEvent<E, P> {
169 fn clone(&self) -> Self {
170 *self
171 }
172}
173impl<E: Entity, P: Property<E>> Copy for PropertyChangeEvent<E, P> {}
174
175#[cfg(test)]
176mod tests {
177 use std::cell::RefCell;
178 use std::rc::Rc;
179
180 use super::*;
181 use crate::{define_derived_property, define_entity, define_property, with, Context};
182
183 define_entity!(Person);
184
185 define_property!(struct Age(u8), Person );
186
187 define_derived_property!(
191 enum AgeGroup {
192 Child,
193 Adult,
194 },
195 Person,
196 [Age], [], |age| {
199 let age: Age = age;
200 if age.0 < 18 {
201 AgeGroup::Child
202 } else {
203 AgeGroup::Adult
204 }
205 }
206 );
207
208 define_property!(
209 enum RiskCategory {
210 High,
211 Low,
212 },
213 Person
214 );
215
216 define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
217
218 define_property!(struct RunningShoes(u8), Person );
219
220 #[test]
221 fn observe_entity_addition() {
222 let mut context = Context::new();
223
224 let flag = Rc::new(RefCell::new(false));
225 let flag_clone = flag.clone();
226 context.subscribe_to_event(move |_context, event: EntityCreatedEvent<Person>| {
227 *flag_clone.borrow_mut() = true;
228 assert_eq!(event.entity_id.0, 0);
229 });
230
231 let _ = context
232 .add_entity::<Person, _>(with!(Person, Age(18), RunningShoes(33), RiskCategory::Low))
233 .unwrap();
234 context.execute();
235 assert!(*flag.borrow());
236 }
237
238 #[test]
239 fn observe_entity_property_change() {
240 let mut context = Context::new();
241
242 let flag = Rc::new(RefCell::new(false));
243 let flag_clone = flag.clone();
244 context.subscribe_to_event(
245 move |_context, event: PropertyChangeEvent<Person, RiskCategory>| {
246 *flag_clone.borrow_mut() = true;
247 assert_eq!(event.entity_id.0, 0, "Entity id is correct");
248 assert_eq!(
249 event.previous,
250 RiskCategory::Low,
251 "Previous value is correct"
252 );
253 assert_eq!(
254 event.current,
255 RiskCategory::High,
256 "Current value is correct"
257 );
258 },
259 );
260
261 let person_id = context
262 .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
263 .unwrap();
264
265 context.set_property(person_id, RiskCategory::High);
266 context.execute();
267 assert!(*flag.borrow());
268 }
269
270 #[test]
271 fn observe_entity_property_change_with_set() {
272 let mut context = Context::new();
273
274 let flag = Rc::new(RefCell::new(false));
275 let flag_clone = flag.clone();
276 context.subscribe_to_event(
277 move |_context, _event: PropertyChangeEvent<Person, RunningShoes>| {
278 *flag_clone.borrow_mut() = true;
279 },
280 );
281 let person_id = context
283 .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
284 .unwrap();
285 context.set_property(person_id, RunningShoes(42));
287 context.execute();
288 assert!(*flag.borrow());
289 }
290
291 #[test]
292 fn get_entity_property_change_event() {
293 let mut context = Context::new();
294 let person = context
295 .add_entity(with!(Person, Age(17), RunningShoes(33), RiskCategory::Low))
296 .unwrap();
297
298 let flag = Rc::new(RefCell::new(false));
299
300 let flag_clone = flag.clone();
301 context.subscribe_to_event(
302 move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
303 assert_eq!(event.entity_id.0, 0);
304 assert_eq!(event.previous, AgeGroup::Child);
305 assert_eq!(event.current, AgeGroup::Adult);
306 *flag_clone.borrow_mut() = true;
307 },
308 );
309 context.set_property(person, Age(18));
310 context.execute();
311 assert!(*flag.borrow());
312 }
313
314 #[test]
315 fn test_person_property_change_event_no_people() {
316 let mut context = Context::new();
317 context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, IsRunner>| {
319 unreachable!();
320 });
321
322 context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, AgeGroup>| {
324 unreachable!();
325 });
326 }
327}