use ixa_derive::IxaEvent;
use smallbox::space::S4;
use smallbox::SmallBox;
use crate::entity::property::Property;
use crate::entity::{ContextEntitiesExt, Entity, EntityId};
use crate::{Context, IxaEvent};
pub(crate) type PartialPropertyChangeEventBox = SmallBox<dyn PartialPropertyChangeEvent, S4>;
pub(crate) trait PartialPropertyChangeEvent {
fn emit_in_context(&mut self, context: &mut Context);
}
impl<E: Entity, P: Property<E>> PartialPropertyChangeEvent
for PartialPropertyChangeEventCore<E, P>
{
fn emit_in_context(&mut self, context: &mut Context) {
self.0.current = context.get_property(self.0.entity_id);
{
let property_value_store = context.get_property_value_store::<E, P>();
if self.0.current != self.0.previous {
for counter in &property_value_store.value_change_counters {
counter
.borrow_mut()
.update(self.0.entity_id, self.0.current, context);
}
}
}
let property_value_store = context.get_property_value_store_mut::<E, P>();
property_value_store
.index
.remove_entity(&self.0.previous.make_canonical(), self.0.entity_id);
property_value_store
.index
.add_entity(&self.0.current.make_canonical(), self.0.entity_id);
context.emit_event(self.to_event());
}
}
#[repr(transparent)]
pub(crate) struct PartialPropertyChangeEventCore<E: Entity, P: Property<E>>(
PropertyChangeEvent<E, P>,
);
impl<E: Entity, P: Property<E>> Clone for PartialPropertyChangeEventCore<E, P> {
fn clone(&self) -> Self {
*self
}
}
impl<E: Entity, P: Property<E>> Copy for PartialPropertyChangeEventCore<E, P> {}
impl<E: Entity, P: Property<E>> PartialPropertyChangeEventCore<E, P> {
pub fn new(entity_id: EntityId<E>, previous_value: P) -> Self {
Self(PropertyChangeEvent {
entity_id,
current: previous_value,
previous: previous_value,
})
}
pub fn to_event(self) -> PropertyChangeEvent<E, P> {
self.0
}
}
#[derive(IxaEvent)]
#[allow(clippy::manual_non_exhaustive)]
pub struct EntityCreatedEvent<E: Entity> {
pub entity_id: EntityId<E>,
}
impl<E: Entity> Copy for EntityCreatedEvent<E> {}
impl<E: Entity> Clone for EntityCreatedEvent<E> {
fn clone(&self) -> Self {
*self
}
}
impl<E: Entity> EntityCreatedEvent<E> {
pub fn new(entity_id: EntityId<E>) -> Self {
Self { entity_id }
}
}
#[derive(IxaEvent)]
#[allow(clippy::manual_non_exhaustive)]
pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
pub entity_id: EntityId<E>,
pub current: P,
pub previous: P,
}
impl<E: Entity, P: Property<E>> Clone for PropertyChangeEvent<E, P> {
fn clone(&self) -> Self {
*self
}
}
impl<E: Entity, P: Property<E>> Copy for PropertyChangeEvent<E, P> {}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::*;
use crate::{define_derived_property, define_entity, define_property, with, Context};
define_entity!(Person);
define_property!(struct Age(u8), Person );
define_derived_property!(
enum AgeGroup {
Child,
Adult,
},
Person,
[Age], [], |age| {
let age: Age = age;
if age.0 < 18 {
AgeGroup::Child
} else {
AgeGroup::Adult
}
}
);
define_property!(
enum RiskCategory {
High,
Low,
},
Person
);
define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
define_property!(struct RunningShoes(u8), Person );
#[test]
fn observe_entity_addition() {
let mut context = Context::new();
let flag = Rc::new(RefCell::new(false));
let flag_clone = flag.clone();
context.subscribe_to_event(move |_context, event: EntityCreatedEvent<Person>| {
*flag_clone.borrow_mut() = true;
assert_eq!(event.entity_id.0, 0);
});
let _ = context
.add_entity::<Person, _>(with!(Person, Age(18), RunningShoes(33), RiskCategory::Low))
.unwrap();
context.execute();
assert!(*flag.borrow());
}
#[test]
fn observe_entity_property_change() {
let mut context = Context::new();
let flag = Rc::new(RefCell::new(false));
let flag_clone = flag.clone();
context.subscribe_to_event(
move |_context, event: PropertyChangeEvent<Person, RiskCategory>| {
*flag_clone.borrow_mut() = true;
assert_eq!(event.entity_id.0, 0, "Entity id is correct");
assert_eq!(
event.previous,
RiskCategory::Low,
"Previous value is correct"
);
assert_eq!(
event.current,
RiskCategory::High,
"Current value is correct"
);
},
);
let person_id = context
.add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
.unwrap();
context.set_property(person_id, RiskCategory::High);
context.execute();
assert!(*flag.borrow());
}
#[test]
fn observe_entity_property_change_with_set() {
let mut context = Context::new();
let flag = Rc::new(RefCell::new(false));
let flag_clone = flag.clone();
context.subscribe_to_event(
move |_context, _event: PropertyChangeEvent<Person, RunningShoes>| {
*flag_clone.borrow_mut() = true;
},
);
let person_id = context
.add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low))
.unwrap();
context.set_property(person_id, RunningShoes(42));
context.execute();
assert!(*flag.borrow());
}
#[test]
fn get_entity_property_change_event() {
let mut context = Context::new();
let person = context
.add_entity(with!(Person, Age(17), RunningShoes(33), RiskCategory::Low))
.unwrap();
let flag = Rc::new(RefCell::new(false));
let flag_clone = flag.clone();
context.subscribe_to_event(
move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
assert_eq!(event.entity_id.0, 0);
assert_eq!(event.previous, AgeGroup::Child);
assert_eq!(event.current, AgeGroup::Adult);
*flag_clone.borrow_mut() = true;
},
);
context.set_property(person, Age(18));
context.execute();
assert!(*flag.borrow());
}
#[test]
fn test_person_property_change_event_no_people() {
let mut context = Context::new();
context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, IsRunner>| {
unreachable!();
});
context.subscribe_to_event(|_context, _event: PropertyChangeEvent<Person, AgeGroup>| {
unreachable!();
});
}
}