use crate::people::index::{Index, IndexValue};
use crate::people::query::Query;
use crate::people::{index, InitializationList, PeoplePlugin, PersonPropertyHolder};
use crate::{
Context, ContextRandomExt, IxaError, PersonCreatedEvent, PersonId, PersonProperty,
PersonPropertyChangeEvent, RngId, Tabulator,
};
use crate::{HashMap, HashMapExt, HashSet, HashSetExt};
use rand::Rng;
use std::any::TypeId;
use std::cell::Ref;
use crate::people::methods::Methods;
pub trait ContextPeopleExt {
fn get_current_population(&self) -> usize;
fn add_person<T: InitializationList>(&mut self, props: T) -> Result<PersonId, IxaError>;
fn get_person_property<T: PersonProperty + 'static>(
&self,
person_id: PersonId,
_property: T,
) -> T::Value;
#[doc(hidden)]
fn register_property<T: PersonProperty + 'static>(&self);
fn set_person_property<T: PersonProperty + 'static>(
&mut self,
person_id: PersonId,
_property: T,
value: T::Value,
);
fn index_property<T: PersonProperty + 'static>(&mut self, property: T);
fn query_people<T: Query>(&self, q: T) -> Vec<PersonId>;
fn query_people_count<T: Query>(&self, q: T) -> usize;
fn match_person<T: Query>(&self, person_id: PersonId, q: T) -> bool;
fn tabulate_person_properties<T: Tabulator, F>(&self, tabulator: &T, print_fn: F)
where
F: Fn(&Context, &[String], usize);
fn sample_person<R: RngId + 'static, T: Query>(&self, rng_id: R, query: T) -> Option<PersonId>
where
R::RngType: Rng;
}
impl ContextPeopleExt for Context {
fn get_current_population(&self) -> usize {
self.get_data_container(PeoplePlugin)
.map_or(0, |data_container| data_container.current_population)
}
fn add_person<T: InitializationList>(&mut self, props: T) -> Result<PersonId, IxaError> {
let data_container = self.get_data_container_mut(PeoplePlugin);
data_container.check_initialization_list(&props)?;
let person_id = data_container.add_person();
data_container.is_initializing = true;
props.set_properties(self, person_id);
let data_container = self.get_data_container_mut(PeoplePlugin);
data_container.is_initializing = false;
self.emit_event(PersonCreatedEvent { person_id });
Ok(person_id)
}
fn get_person_property<T: PersonProperty + 'static>(
&self,
person_id: PersonId,
property: T,
) -> T::Value {
let data_container = self.get_data_container(PeoplePlugin)
.expect("PeoplePlugin is not initialized; make sure you add a person before accessing properties");
self.register_property::<T>();
if T::is_derived() {
return T::compute(self, person_id);
}
if let Some(value) = *data_container.get_person_property_ref(person_id, property) {
return value;
}
let initialized_value = T::compute(self, person_id);
data_container.set_person_property(person_id, property, initialized_value);
initialized_value
}
#[allow(clippy::single_match_else)]
fn set_person_property<T: PersonProperty + 'static>(
&mut self,
person_id: PersonId,
property: T,
value: T::Value,
) {
self.register_property::<T>();
assert!(!T::is_derived(), "Cannot set a derived property");
let initializing = self
.get_data_container(PeoplePlugin)
.unwrap()
.is_initializing;
let (previous_value, deps_temp) = if initializing {
(None, None)
} else {
let previous_value = self.get_person_property(person_id, property);
if previous_value != value {
self.remove_from_index_maybe(person_id, property);
}
(
Some(previous_value),
self.get_data_container(PeoplePlugin)
.unwrap()
.dependency_map
.borrow_mut()
.get_mut(&TypeId::of::<T>())
.map(std::mem::take),
)
};
let mut dependency_event_callbacks = Vec::new();
if let Some(mut deps) = deps_temp {
for dep in &mut deps {
dep.dependency_changed(self, person_id, &mut dependency_event_callbacks);
}
let data_container = self.get_data_container(PeoplePlugin).unwrap();
let mut dependencies = data_container.dependency_map.borrow_mut();
dependencies.insert(TypeId::of::<T>(), deps);
}
let data_container = self.get_data_container(PeoplePlugin).unwrap();
data_container.set_person_property(person_id, property, value);
if !initializing {
if previous_value.unwrap() != value {
self.add_to_index_maybe(person_id, property);
}
let change_event: PersonPropertyChangeEvent<T> = PersonPropertyChangeEvent {
person_id,
current: value,
previous: previous_value.unwrap(), };
self.emit_event(change_event);
}
for callback in dependency_event_callbacks {
callback(self);
}
}
fn index_property<T: PersonProperty + 'static>(&mut self, _property: T) {
{
let _ = self.get_data_container_mut(PeoplePlugin);
}
self.register_property::<T>();
let data_container = self.get_data_container(PeoplePlugin).unwrap();
let mut index = data_container
.get_index_ref_mut_by_prop(T::get_instance())
.unwrap();
if index.lookup.is_none() {
index.lookup = Some(HashMap::new());
}
}
fn query_people<T: Query>(&self, q: T) -> Vec<PersonId> {
if self.get_data_container(PeoplePlugin).is_none() {
return Vec::new();
}
T::setup(&q, self);
let mut result = Vec::new();
self.query_people_internal(
|person| {
result.push(person);
},
q.get_query(),
);
result
}
fn query_people_count<T: Query>(&self, q: T) -> usize {
if self.get_data_container(PeoplePlugin).is_none() {
return 0;
}
T::setup(&q, self);
let mut count: usize = 0;
self.query_people_internal(
|_person| {
count += 1;
},
q.get_query(),
);
count
}
fn match_person<T: Query>(&self, person_id: PersonId, q: T) -> bool {
T::setup(&q, self);
let data_container = self.get_data_container(PeoplePlugin).unwrap();
let query = q.get_query();
for (t, hash) in &query {
let methods = data_container.get_methods(*t);
if *hash != (*methods.indexer)(self, person_id) {
return false;
}
}
true
}
fn register_property<T: PersonProperty + 'static>(&self) {
let data_container = self.get_data_container(PeoplePlugin).
expect("PeoplePlugin is not initialized; make sure you add a person before accessing properties");
if data_container
.registered_derived_properties
.borrow()
.contains(&TypeId::of::<T>())
{
return;
}
let instance = T::get_instance();
let dependencies = instance.non_derived_dependencies();
for dependency in dependencies {
let mut dependency_map = data_container.dependency_map.borrow_mut();
let derived_prop_list = dependency_map.entry(dependency).or_default();
derived_prop_list.push(Box::new(instance));
}
data_container
.methods
.borrow_mut()
.insert(TypeId::of::<T>(), Methods::new::<T>());
data_container
.people_types
.borrow_mut()
.insert(T::name().to_string(), TypeId::of::<T>());
data_container
.registered_derived_properties
.borrow_mut()
.insert(TypeId::of::<T>());
self.register_indexer::<T>();
}
fn tabulate_person_properties<T: Tabulator, F>(&self, tabulator: &T, print_fn: F)
where
F: Fn(&Context, &[String], usize),
{
let type_ids = tabulator.get_typelist();
tabulator.setup(self).unwrap();
if let Some(data_container) = self.get_data_container(PeoplePlugin) {
for type_id in &type_ids {
let mut index = data_container.get_index_ref_mut(*type_id).unwrap();
if index.lookup.is_none() {
index.lookup = Some(HashMap::new());
}
let methods = data_container.get_methods(*type_id);
index.index_unindexed_people(self, &methods);
}
} else {
return;
}
let index_container = self
.get_data_container(PeoplePlugin)
.unwrap()
.property_indexes
.borrow();
let indices = type_ids
.iter()
.filter_map(|t| index_container.get(t))
.collect::<Vec<&Index>>();
index::process_indices(
self,
indices.as_slice(),
&mut Vec::new(),
&HashSet::new(),
&print_fn,
);
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn sample_person<R: RngId + 'static, T: Query>(&self, rng_id: R, query: T) -> Option<PersonId>
where
R::RngType: Rng,
{
if self.get_current_population() == 0 {
return None;
}
if query.get_query().is_empty() {
let result = self.sample_range(rng_id, 0..self.get_current_population());
return Some(PersonId(result));
}
T::setup(&query, self);
let mut selected: Option<PersonId> = None;
let mut w: f64 = self.sample_range(rng_id, 0.0..1.0);
let mut ctr: usize = 0;
let mut i: usize = 1;
self.query_people_internal(
|person| {
ctr += 1;
if i == ctr {
selected = Some(person);
i += (f64::ln(self.sample_range(rng_id, 0.0..1.0)) / f64::ln(1.0 - w)).floor()
as usize
+ 1;
w *= self.sample_range(rng_id, 0.0..1.0);
}
},
query.get_query(),
);
selected
}
}
pub trait ContextPeopleExtInternal {
fn register_indexer<T: PersonProperty + 'static>(&self);
fn add_to_index_maybe<T: PersonProperty + 'static>(&mut self, person_id: PersonId, property: T);
fn remove_from_index_maybe<T: PersonProperty + 'static>(
&mut self,
person_id: PersonId,
property: T,
);
fn query_people_internal(
&self,
accumulator: impl FnMut(PersonId),
property_hashes: Vec<(TypeId, IndexValue)>,
);
}
impl ContextPeopleExtInternal for Context {
fn register_indexer<T: PersonProperty + 'static>(&self) {
{
let data_container = self.get_data_container(PeoplePlugin).unwrap();
let property_indexes = data_container.property_indexes.borrow_mut();
if property_indexes.contains_key(&TypeId::of::<T>()) {
return; }
}
let index = Index::new(self, T::get_instance());
let data_container = self.get_data_container(PeoplePlugin).unwrap();
let mut property_indexes = data_container.property_indexes.borrow_mut();
property_indexes.insert(TypeId::of::<T>(), index);
}
fn add_to_index_maybe<T: PersonProperty + 'static>(
&mut self,
person_id: PersonId,
property: T,
) {
let data_container = self.get_data_container(PeoplePlugin).unwrap();
if let Some(mut index) = data_container.get_index_ref_mut_by_prop(property) {
let methods = data_container.get_methods(TypeId::of::<T>());
if index.lookup.is_some() {
index.add_person(self, &methods, person_id);
}
}
}
fn remove_from_index_maybe<T: PersonProperty + 'static>(
&mut self,
person_id: PersonId,
property: T,
) {
let data_container = self.get_data_container(PeoplePlugin).unwrap();
if let Some(mut index) = data_container.get_index_ref_mut_by_prop(property) {
let methods = data_container.get_methods(TypeId::of::<T>());
if index.lookup.is_some() {
index.remove_person(self, &methods, person_id);
}
}
}
fn query_people_internal(
&self,
mut accumulator: impl FnMut(PersonId),
property_hashes: Vec<(TypeId, IndexValue)>,
) {
let mut indexes = Vec::<Ref<HashSet<PersonId>>>::new();
let mut unindexed = Vec::<(TypeId, IndexValue)>::new();
let data_container = self.get_data_container(PeoplePlugin)
.expect("PeoplePlugin is not initialized; make sure you add a person before accessing properties");
for (t, _) in &property_hashes {
let mut index = data_container.get_index_ref_mut(*t).unwrap();
let methods = data_container.get_methods(*t);
index.index_unindexed_people(self, &methods);
}
for (t, hash) in property_hashes {
let index = data_container.get_index_ref(t).unwrap();
if let Ok(lookup) = Ref::filter_map(index, |x| x.lookup.as_ref()) {
if let Ok(matching_people) =
Ref::filter_map(lookup, |x| x.get(&hash).map(|entry| &entry.1))
{
indexes.push(matching_people);
} else {
return;
}
} else {
unindexed.push((t, hash));
}
}
let holder: Ref<HashSet<PersonId>>;
let to_check: Box<dyn Iterator<Item = PersonId>> = if indexes.is_empty() {
Box::new(data_container.people_iterator())
} else {
indexes.sort_by_key(|x| x.len());
holder = indexes.remove(0);
Box::new(holder.iter().copied())
};
'outer: for person in to_check {
for index in &indexes {
if !index.contains(&person) {
continue 'outer;
}
}
for (t, hash) in &unindexed {
let methods = data_container.get_methods(*t);
if *hash != (*methods.indexer)(self, person) {
continue 'outer;
}
}
accumulator(person);
}
}
}
#[cfg(test)]
mod tests {
use crate::people::{PeoplePlugin, PersonPropertyHolder};
use crate::random::{define_rng, ContextRandomExt};
use crate::{
define_derived_property, define_global_property, define_person_property,
define_person_property_with_default, Context, ContextGlobalPropertiesExt, ContextPeopleExt,
IxaError, PersonId, PersonPropertyChangeEvent,
};
use serde_derive::Serialize;
use std::any::TypeId;
use std::cell::RefCell;
use std::rc::Rc;
define_person_property!(Age, u8);
#[derive(Serialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum AgeGroupValue {
Child,
Adult,
}
define_global_property!(ThresholdP, u8);
define_derived_property!(IsEligible, bool, [Age], [ThresholdP], |age, threshold| {
age >= threshold
});
define_derived_property!(
NotUsed,
bool,
[Age],
[ThresholdP, ThresholdP],
|age, threshold, threshold2| { age >= threshold && age <= threshold2 }
);
define_derived_property!(AgeGroup, AgeGroupValue, [Age], |age| {
if age < 18 {
AgeGroupValue::Child
} else {
AgeGroupValue::Adult
}
});
#[derive(Serialize, Copy, Clone, PartialEq, Eq, Debug)]
pub enum RiskCategoryValue {
High,
Low,
}
define_person_property!(RiskCategory, RiskCategoryValue);
define_person_property_with_default!(IsRunner, bool, false);
define_person_property!(RunningShoes, u8, |context: &Context, person: PersonId| {
let is_runner = context.get_person_property(person, IsRunner);
if is_runner {
4
} else {
0
}
});
define_derived_property!(AdultRunner, bool, [IsRunner, Age], |is_runner, age| {
is_runner && age >= 18
});
define_derived_property!(
SeniorRunner,
bool,
[AdultRunner, Age],
|adult_runner, age| { adult_runner && age >= 65 }
);
define_person_property_with_default!(IsSwimmer, bool, false);
define_derived_property!(AdultSwimmer, bool, [IsSwimmer, Age], |is_swimmer, age| {
is_swimmer && age >= 18
});
define_derived_property!(
AdultAthlete,
bool,
[AdultRunner, AdultSwimmer],
|adult_runner, adult_swimmer| { adult_runner || adult_swimmer }
);
#[test]
fn set_get_properties() {
let mut context = Context::new();
let person = context.add_person((Age, 42)).unwrap();
assert_eq!(context.get_person_property(person, Age), 42);
}
#[allow(clippy::should_panic_without_expect)]
#[test]
#[should_panic]
fn get_uninitialized_property_panics() {
let mut context = Context::new();
let person = context.add_person(()).unwrap();
context.get_person_property(person, Age);
}
#[test]
fn get_current_population() {
let mut context = Context::new();
assert_eq!(context.get_current_population(), 0);
for _ in 0..3 {
context.add_person(()).unwrap();
}
assert_eq!(context.get_current_population(), 3);
}
#[test]
fn add_person() {
let mut context = Context::new();
let person_id = context
.add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
.unwrap();
assert_eq!(context.get_person_property(person_id, Age), 42);
assert_eq!(
context.get_person_property(person_id, RiskCategory),
RiskCategoryValue::Low
);
}
#[test]
fn add_person_with_initialize() {
let mut context = Context::new();
let person_id = context
.add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
.unwrap();
assert_eq!(context.get_person_property(person_id, Age), 42);
assert_eq!(
context.get_person_property(person_id, RiskCategory),
RiskCategoryValue::Low
);
}
#[test]
fn add_person_with_initialize_missing() {
let mut context = Context::new();
context.add_person((Age, 10)).unwrap();
assert!(matches!(context.add_person(()), Err(IxaError::IxaError(_))));
}
#[test]
fn add_person_with_initialize_missing_first() {
let mut context = Context::new();
context.add_person(()).unwrap();
}
#[test]
fn add_person_with_initialize_missing_with_default() {
let mut context = Context::new();
context.add_person((IsRunner, true)).unwrap();
context.add_person(()).unwrap();
}
#[test]
fn person_debug_display() {
let mut context = Context::new();
let person_id = context.add_person(()).unwrap();
assert_eq!(format!("{person_id}"), "0");
assert_eq!(format!("{person_id:?}"), "Person 0");
}
#[test]
fn add_person_initializers() {
let mut context = Context::new();
let person_id = context.add_person(()).unwrap();
assert_eq!(context.get_person_property(person_id, RunningShoes), 0);
assert!(!context.get_person_property(person_id, IsRunner));
}
#[test]
fn property_initialization_is_lazy() {
let mut context = Context::new();
let person = context.add_person((IsRunner, true)).unwrap();
let people_data = context.get_data_container_mut(PeoplePlugin);
let has_value = *people_data.get_person_property_ref(person, RunningShoes);
assert!(has_value.is_none());
let value = context.get_person_property(person, RunningShoes);
assert_eq!(value, 4);
}
#[test]
fn initialize_without_initializer_succeeds() {
let mut context = Context::new();
context
.add_person((RiskCategory, RiskCategoryValue::High))
.unwrap();
}
#[test]
#[should_panic(expected = "Property not initialized when person created")]
fn set_without_initializer_panics() {
let mut context = Context::new();
let person_id = context.add_person(()).unwrap();
context.set_person_property(person_id, RiskCategory, RiskCategoryValue::High);
}
#[test]
#[should_panic(expected = "Property not initialized when person created")]
fn get_without_initializer_panics() {
let mut context = Context::new();
let person_id = context.add_person(()).unwrap();
context.get_person_property(person_id, RiskCategory);
}
#[test]
fn get_person_property_returns_correct_value() {
let mut context = Context::new();
let person = context.add_person((Age, 10)).unwrap();
assert_eq!(
context.get_person_property(person, AgeGroup),
AgeGroupValue::Child
);
}
#[test]
fn get_person_property_changes_correctly() {
let mut context = Context::new();
let person = context.add_person((Age, 17)).unwrap();
assert_eq!(
context.get_person_property(person, AgeGroup),
AgeGroupValue::Child
);
context.set_person_property(person, Age, 18);
assert_eq!(
context.get_person_property(person, AgeGroup),
AgeGroupValue::Adult
);
}
#[test]
fn get_derived_property_multiple_deps() {
let mut context = Context::new();
let person = context.add_person(((Age, 17), (IsRunner, true))).unwrap();
let flag = Rc::new(RefCell::new(false));
let flag_clone = flag.clone();
context.subscribe_to_event(
move |_context, event: PersonPropertyChangeEvent<AdultRunner>| {
assert_eq!(event.person_id.0, 0);
assert!(!event.previous);
assert!(event.current);
*flag_clone.borrow_mut() = true;
},
);
context.set_person_property(person, Age, 18);
context.execute();
assert!(*flag.borrow());
}
#[test]
fn register_derived_only_once() {
let mut context = Context::new();
let person = context.add_person(((Age, 17), (IsRunner, true))).unwrap();
let flag = Rc::new(RefCell::new(0));
let flag_clone = flag.clone();
context.subscribe_to_event(
move |_context, _event: PersonPropertyChangeEvent<AdultRunner>| {
*flag_clone.borrow_mut() += 1;
},
);
context.subscribe_to_event(
move |_context, _event: PersonPropertyChangeEvent<AdultRunner>| {
},
);
context.set_person_property(person, Age, 18);
context.execute();
assert_eq!(*flag.borrow(), 1);
}
#[test]
fn test_resolve_dependencies() {
let mut actual = SeniorRunner.non_derived_dependencies();
let mut expected = vec![TypeId::of::<Age>(), TypeId::of::<IsRunner>()];
actual.sort();
expected.sort();
assert_eq!(actual, expected);
}
#[test]
fn get_derived_property_dependent_on_another_derived() {
let mut context = Context::new();
let person = context.add_person(((Age, 88), (IsRunner, false))).unwrap();
let flag = Rc::new(RefCell::new(0));
let flag_clone = flag.clone();
assert!(!context.get_person_property(person, SeniorRunner));
context.subscribe_to_event(
move |_context, event: PersonPropertyChangeEvent<SeniorRunner>| {
assert_eq!(event.person_id.0, 0);
assert!(!event.previous);
assert!(event.current);
*flag_clone.borrow_mut() += 1;
},
);
context.set_person_property(person, IsRunner, true);
context.execute();
assert_eq!(*flag.borrow(), 1);
}
#[test]
fn get_derived_property_diamond_dependencies() {
let mut context = Context::new();
let person = context.add_person(((Age, 17), (IsSwimmer, true))).unwrap();
let flag = Rc::new(RefCell::new(0));
let flag_clone = flag.clone();
assert!(!context.get_person_property(person, AdultAthlete));
context.subscribe_to_event(
move |_context, event: PersonPropertyChangeEvent<AdultAthlete>| {
assert_eq!(event.person_id.0, 0);
assert!(!event.previous);
assert!(event.current);
*flag_clone.borrow_mut() += 1;
},
);
context.set_person_property(person, Age, 18);
context.execute();
assert_eq!(*flag.borrow(), 1);
}
#[test]
fn get_derived_property_with_globals() {
let mut context = Context::new();
context.set_global_property_value(ThresholdP, 18).unwrap();
let child = context.add_person((Age, 17)).unwrap();
let adult = context.add_person((Age, 19)).unwrap();
assert!(!context.get_person_property(child, IsEligible));
assert!(context.get_person_property(adult, IsEligible));
}
#[test]
fn text_match_person() {
let mut context = Context::new();
let person = context
.add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
.unwrap();
assert!(context.match_person(person, ((Age, 42), (RiskCategory, RiskCategoryValue::High))));
assert!(!context.match_person(person, ((Age, 43), (RiskCategory, RiskCategoryValue::High))));
assert!(!context.match_person(person, ((Age, 42), (RiskCategory, RiskCategoryValue::Low))));
}
#[test]
fn test_sample_person_simple() {
define_rng!(SampleRng1);
let mut context = Context::new();
context.init_random(42);
assert!(context.sample_person(SampleRng1, ()).is_none());
let person = context.add_person(()).unwrap();
assert_eq!(context.sample_person(SampleRng1, ()).unwrap(), person);
}
#[test]
fn test_sample_matching_person() {
define_rng!(SampleRng2);
let mut context = Context::new();
context.init_random(42);
assert!(context.sample_person(SampleRng2, ()).is_none());
let person1 = context.add_person((Age, 10)).unwrap();
let person2 = context.add_person((Age, 10)).unwrap();
let person3 = context.add_person((Age, 10)).unwrap();
let person4 = context.add_person((Age, 30)).unwrap();
assert!(context.sample_person(SampleRng2, (Age, 50)).is_none());
for _ in 0..10 {
assert_eq!(
context.sample_person(SampleRng2, (Age, 30)).unwrap(),
person4
);
}
let mut count_p1: usize = 0;
let mut count_p2: usize = 0;
let mut count_p3: usize = 0;
for _ in 0..30000 {
let p = context.sample_person(SampleRng2, (Age, 10)).unwrap();
if p == person1 {
count_p1 += 1;
} else if p == person2 {
count_p2 += 1;
} else if p == person3 {
count_p3 += 1;
} else {
panic!("Unexpected person");
}
}
assert!(count_p1 >= 8700);
assert!(count_p2 >= 8700);
assert!(count_p3 >= 8700);
}
}