1use smallbox::space::S4;
30use smallbox::SmallBox;
31
32use crate::entity::property::Property;
33use crate::entity::{ContextEntitiesExt, Entity, EntityId};
34use crate::{Context, IxaEvent};
35
36pub(crate) type PartialPropertyChangeEventBox = SmallBox<dyn PartialPropertyChangeEvent, S4>;
49
50pub(crate) trait PartialPropertyChangeEvent {
53 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 fn emit_in_context(&mut self, context: &mut Context) {
62 self.0.current = context.get_property(self.0.entity_id);
63
64 {
65 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 let property_value_store = context.get_property_value_store_mut::<E, P>();
78 if let Some(index) = property_value_store.index.as_mut() {
79 index.remove_entity(&self.0.previous, self.0.entity_id);
81 index.add_entity(&self.0.current, self.0.entity_id);
83 }
84
85 context.emit_event(self.to_event());
92 }
93}
94
95#[repr(transparent)]
102pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
103 PropertyChangeEvent<E, P>,
104);
105impl<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#[derive(IxaEvent)]
131pub struct EntityCreatedEvent<E: Entity> {
132 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#[derive(IxaEvent)]
146pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
147 pub entity_id: EntityId<E>,
149 pub current: P,
151 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_derived_property!(
171 enum AgeGroup {
172 Child,
173 Adult,
174 },
175 Person,
176 [Age], [], |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 let person_id = context
266 .add_entity(with!(Person, 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(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 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}