Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/book/models/disease_model/src/transmission_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ define_rng!(TransmissionRng);
fn attempt_infection(context: &mut Context) {
trace!("Attempting infection");
let person_to_infect = context.sample_entity(TransmissionRng, Person).unwrap();
let person_status: InfectionStatus = context.get_property(person_to_infect);
let person_status: InfectionStatus = context.get_property::<Person, InfectionStatus>(person_to_infect);

if person_status == InfectionStatus::S {
context.set_property(person_to_infect, InfectionStatus::I);
Expand Down
6 changes: 4 additions & 2 deletions examples/basic-infection/src/transmission_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ fn attempt_infection(context: &mut Context) {
let population_size: usize = context.get_entity_count::<Person>();
let person_to_infect = context.sample_entity(TransmissionRng, Person).unwrap();

let person_status: InfectionStatus = context.get_property(person_to_infect);
let person_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person_to_infect);

if person_status == InfectionStatus::S {
context.set_property(person_to_infect, InfectionStatus::I);
Expand Down Expand Up @@ -54,7 +55,8 @@ mod test {
context.init_random(SEED);
let person_id = context.add_entity(Person).unwrap();
attempt_infection(&mut context);
let person_status: InfectionStatus = context.get_property(person_id);
let person_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person_id);
assert_eq!(person_status, InfectionStatus::I);
context.execute();
}
Expand Down
6 changes: 3 additions & 3 deletions examples/births-deaths/src/demographics_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ define_report!(PersonReportItem);

fn handle_person_created(context: &mut Context, event: EntityCreatedEvent<Person>) {
let person = event.entity_id;
let age_group_person: AgeGroupRisk = context.get_property(person);
let age_group_person: AgeGroupRisk = context.get_property::<Person, AgeGroupRisk>(person);
context.send_report(PersonReportItem {
time: context.get_current_time(),
person_id: format!("{person}"),
Expand All @@ -34,7 +34,7 @@ fn handle_person_created(context: &mut Context, event: EntityCreatedEvent<Person

fn handle_person_aging(context: &mut Context, event: PropertyChangeEvent<Person, Age>) {
let person = event.entity_id;
let age_group_person: AgeGroupRisk = context.get_property(person);
let age_group_person: AgeGroupRisk = context.get_property::<Person, AgeGroupRisk>(person);
context.send_report(PersonReportItem {
time: context.get_current_time(),
person_id: format!("{person}"),
Expand All @@ -48,7 +48,7 @@ fn handle_person_aging(context: &mut Context, event: PropertyChangeEvent<Person,
fn handle_death_events(context: &mut Context, event: PropertyChangeEvent<Person, Alive>) {
if !event.current.0 {
let person = event.entity_id;
let age_group_person: AgeGroupRisk = context.get_property(person);
let age_group_person: AgeGroupRisk = context.get_property::<Person, AgeGroupRisk>(person);
context.send_report(PersonReportItem {
time: context.get_current_time(),
person_id: format!("{person}"),
Expand Down
5 changes: 3 additions & 2 deletions examples/births-deaths/src/incidence_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ fn handle_infection_status_change(
context: &mut Context,
event: PropertyChangeEvent<Person, InfectionStatus>,
) {
let age_person: Age = context.get_property(event.entity_id);
let age_group_person: AgeGroupRisk = context.get_property(event.entity_id);
let age_person: Age = context.get_property::<Person, Age>(event.entity_id);
let age_group_person: AgeGroupRisk =
context.get_property::<Person, AgeGroupRisk>(event.entity_id);
context.send_report(IncidenceReportItem {
time: context.get_current_time(),
person_id: format!("{}", event.entity_id),
Expand Down
2 changes: 1 addition & 1 deletion examples/births-deaths/src/infection_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn schedule_recovery(context: &mut Context, person_id: PersonId) {
let infection_duration = parameters.infection_duration;
let recovery_time = context.get_current_time()
+ context.sample_distr(InfectionRng1, Exp::new(1.0 / infection_duration).unwrap());
let is_alive: Alive = context.get_property(person_id);
let is_alive: Alive = context.get_property::<Person, Alive>(person_id);

if is_alive.0 {
let plan_id = context.add_plan(recovery_time, move |context| {
Expand Down
8 changes: 4 additions & 4 deletions examples/births-deaths/src/population_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ impl fmt::Display for AgeGroupRisk {
}

fn schedule_aging(context: &mut Context, person_id: PersonId) {
let is_alive: Alive = context.get_property(person_id);
let is_alive: Alive = context.get_property::<Person, Alive>(person_id);
if is_alive.0 {
let prev_age: Age = context.get_property(person_id);
let prev_age: Age = context.get_property::<Person, Age>(person_id);
context.set_property(person_id, Age(prev_age.0 + 1));
let next_age_event = context.get_current_time() + 365.0;
context.add_plan(next_age_event, move |context| {
Expand Down Expand Up @@ -168,8 +168,8 @@ mod test {
let population = context.get_entity_count::<Person>();

// Even if these people have died during simulation, we can still get their properties
let age_0: Age = context.get_property(person1);
let age_1: Age = context.get_property((*person2).borrow().unwrap());
let age_0: Age = context.get_property::<Person, Age>(person1);
let age_1: Age = context.get_property::<Person, Age>((*person2).borrow().unwrap());
assert_eq!(age_0.0, 10);
assert_eq!(age_1.0, 0);

Expand Down
3 changes: 2 additions & 1 deletion examples/births-deaths/src/transmission_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ fn attempt_infection(context: &mut Context, age_group: AgeGroupRisk) {
.get(&age_group)
.unwrap();

let person_status: InfectionStatus = context.get_property(person_to_infect);
let person_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person_to_infect);

if person_status == InfectionStatus::S {
context.set_property(person_to_infect, InfectionStatus::I);
Expand Down
4 changes: 2 additions & 2 deletions examples/network-hhmodel/incidence_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn handle_infection_status_change(
}

// figure out who infected whom
let infected_by: InfectedBy = context.get_property(event.entity_id);
let infected_by: InfectedBy = context.get_property::<Person, InfectedBy>(event.entity_id);
let infected_by_val = match infected_by.0 {
None => "NA".to_string(),
Some(id) => id.to_string(),
Expand Down Expand Up @@ -96,7 +96,7 @@ mod test {
return;
}

let infected_by: InfectedBy = context.get_property(event.entity_id);
let infected_by: InfectedBy = context.get_property::<Person, InfectedBy>(event.entity_id);
let infected_by_val = match infected_by.0 {
None => "NA".to_string(),
Some(id) => id.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion examples/network-hhmodel/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct EdgeRecord {
fn create_household_networks(context: &mut Context, people: &[PersonId]) {
let mut households = HashSet::new();
for person_id in people {
let household_id: HouseholdId = context.get_property(*person_id);
let household_id: HouseholdId = context.get_property::<Person, HouseholdId>(*person_id);
if households.insert(household_id) {
let mut members: Vec<PersonId> = Vec::new();
context.with_query_results(with!(Person, household_id), &mut |results| {
Expand Down
6 changes: 3 additions & 3 deletions integration-tests/ixa-runner-tests/tests/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,14 @@ mod tests {
TestPropOpt(Some(3u8)),
))
.unwrap();
let val: TestPropU32 = ctx.get_property(pid);
let val: TestPropU32 = ctx.get_property::<Person, TestPropU32>(pid);
assert_eq!(val.0, 10u32);
// Verify default property value is set for TestPropDefault
let default_val: TestPropDefault = ctx.get_property(pid);
let default_val: TestPropDefault = ctx.get_property::<Person, TestPropDefault>(pid);
assert_eq!(default_val.0, 7u32);

// Derived property should compute from TestPropU32
let d: DerivedProp = ctx.get_property(pid);
let d: DerivedProp = ctx.get_property::<Person, DerivedProp>(pid);
assert_eq!(d.0, 11u32);

// Derived property `impl_eq_hash = ...` variants should all compile and behave as hashable keys.
Expand Down
6 changes: 4 additions & 2 deletions integration-tests/ixa-wasm-tests/src/transmission_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ fn attempt_infection(context: &mut Context) {
let population_size: usize = context.get_entity_count::<Person>();
let person_to_infect: PersonId = context.sample_entity(TransmissionRng, Person).unwrap(); //.sample_range(TransmissionRng, 0..population_size);

let person_status: InfectionStatus = context.get_property(person_to_infect);
let person_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person_to_infect);

if person_status == InfectionStatus::S {
context.set_property(person_to_infect, InfectionStatus::I);
Expand Down Expand Up @@ -54,7 +55,8 @@ mod test {
context.init_random(SEED);
let person_id: PersonId = context.add_entity(Person).unwrap();
attempt_infection(&mut context);
let person_status: InfectionStatus = context.get_property(person_id);
let person_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person_id);
assert_eq!(person_status, InfectionStatus::I);
context.execute();
}
Expand Down
53 changes: 30 additions & 23 deletions src/entity/context_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,14 @@ pub trait ContextEntitiesExt {

/// Fetches the property value set for the given `entity_id`.
///
/// The easiest way to call this method is by assigning it to a variable with an explicit type:
/// Returns `P::Value` — for legacy newtype properties this is `Self` (the wrapper),
/// for primitive-form properties this is the inner primitive type.
///
/// The recommended call style is to name the entity and property explicitly:
/// ```rust, ignore
/// let vaccine_status: VaccineStatus = context.get_property(entity_id);
/// let vaccine_status = context.get_property::<Person, VaccineStatus>(entity_id);
/// ```
fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P::Value;

/// Sets the value of the given property. This method unconditionally emits a `PropertyChangeEvent`.
fn set_property<E: Entity, P: Property<E>>(
Expand Down Expand Up @@ -270,12 +273,12 @@ impl ContextEntitiesExt for Context {
Ok(new_entity_id)
}

fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P::Value {
if P::is_derived() {
P::compute_derived(self, entity_id)
P::compute_derived(self, entity_id).into_value()
} else {
let property_store = self.get_property_value_store::<E, P>();
property_store.get(entity_id)
property_store.get(entity_id).into_value()
}
}

Expand Down Expand Up @@ -839,11 +842,12 @@ mod tests {
let person = context.add_entity(with!(Person, Age(25))).unwrap();

// Retrieve and check their values
let age: Age = context.get_property(person);
let age: Age = context.get_property::<Person, Age>(person);
assert_eq!(age, Age(25));
let infection_status: InfectionStatus = context.get_property(person);
let infection_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person);
assert_eq!(infection_status, InfectionStatus::Susceptible);
let vaccinated: Vaccinated = context.get_property(person);
let vaccinated: Vaccinated = context.get_property::<Person, Vaccinated>(person);
assert_eq!(vaccinated, Vaccinated(false));

// Change them
Expand All @@ -852,11 +856,12 @@ mod tests {
context.set_property(person, Vaccinated(true));

// Retrieve and check their values
let age: Age = context.get_property(person);
let age: Age = context.get_property::<Person, Age>(person);
assert_eq!(age, Age(26));
let infection_status: InfectionStatus = context.get_property(person);
let infection_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person);
assert_eq!(infection_status, InfectionStatus::Infected);
let vaccinated: Vaccinated = context.get_property(person);
let vaccinated: Vaccinated = context.get_property::<Person, Vaccinated>(person);
assert_eq!(vaccinated, Vaccinated(true));
}

Expand All @@ -875,11 +880,12 @@ mod tests {
.unwrap();

// Retrieve and check their values
let age: Age = context.get_property(person);
let age: Age = context.get_property::<Person, Age>(person);
assert_eq!(age, Age(25));
let infection_status: InfectionStatus = context.get_property(person);
let infection_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person);
assert_eq!(infection_status, InfectionStatus::Recovered);
let vaccinated: Vaccinated = context.get_property(person);
let vaccinated: Vaccinated = context.get_property::<Person, Vaccinated>(person);
assert_eq!(vaccinated, Vaccinated(true));

// Change them
Expand All @@ -888,11 +894,12 @@ mod tests {
context.set_property(person, Vaccinated(false));

// Retrieve and check their values
let age: Age = context.get_property(person);
let age: Age = context.get_property::<Person, Age>(person);
assert_eq!(age, Age(26));
let infection_status: InfectionStatus = context.get_property(person);
let infection_status: InfectionStatus =
context.get_property::<Person, InfectionStatus>(person);
assert_eq!(infection_status, InfectionStatus::Infected);
let vaccinated: Vaccinated = context.get_property(person);
let vaccinated: Vaccinated = context.get_property::<Person, Vaccinated>(person);
assert_eq!(vaccinated, Vaccinated(false));
}

Expand Down Expand Up @@ -982,11 +989,11 @@ mod tests {
))
.unwrap();

let actual_high: RiskLevel = context.get_property(expected_high_id);
let actual_high: RiskLevel = context.get_property::<Person, RiskLevel>(expected_high_id);
assert_eq!(actual_high, RiskLevel::High);
let actual_med: RiskLevel = context.get_property(expected_med_id);
let actual_med: RiskLevel = context.get_property::<Person, RiskLevel>(expected_med_id);
assert_eq!(actual_med, RiskLevel::Medium);
let actual_low: RiskLevel = context.get_property(expected_low_id);
let actual_low: RiskLevel = context.get_property::<Person, RiskLevel>(expected_low_id);
assert_eq!(actual_low, RiskLevel::Low);
}

Expand Down Expand Up @@ -1066,7 +1073,7 @@ mod tests {
.add_entity(with!(Person, Age(17), IsSwimmer(true)))
.unwrap();

let is_adult_athlete: AdultAthlete = context.get_property(person);
let is_adult_athlete: AdultAthlete = context.get_property::<Person, AdultAthlete>(person);
assert!(!is_adult_athlete.0);

let flag = Rc::new(RefCell::new(0));
Expand All @@ -1082,7 +1089,7 @@ mod tests {

context.set_property(person, Age(20));
// Make sure the derived property is what we expect.
let is_adult_athlete: AdultAthlete = context.get_property(person);
let is_adult_athlete: AdultAthlete = context.get_property::<Person, AdultAthlete>(person);
assert!(is_adult_athlete.0);

// Execute queued event handlers
Expand Down
37 changes: 22 additions & 15 deletions src/entity/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ use crate::{Context, IxaEvent};
// `#[repr(transparent)]` over `PropertyChangeEvent<E, P>`. That event stores
//
// - `EntityId<E>`, one `usize`, so 8 bytes.
// - `current`: a property value, typically <= 8 bytes
// - `current`: a property value, typically <= 8 bytes
// - `current`: `P::Value`, typically <= 8 bytes
// - `previous`: `P::Value`, typically <= 8 bytes
//
// That puts the payload at 24 bytes, with 8-byte alignment. The `S4` size is 4 `usize`s of inline
// storage, i.e. 32 bytes, and inline storage is used when the payload size and alignment fit. So
Expand All @@ -60,30 +60,34 @@ impl<E: Entity, P: Property<E>> PartialPropertyChangeEvent
{
/// Updates the index with the current property value and emits a change event.
fn emit_in_context(&mut self, context: &mut Context) {
self.0.current = context.get_property(self.0.entity_id);
self.0.current = context.get_property::<E, P>(self.0.entity_id);

{
// Update value change counters
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);
counter.borrow_mut().update(
self.0.entity_id,
P::from_value(self.0.current),
context,
);
}
}
}

// Now update the indexes
let property_value_store = context.get_property_value_store_mut::<E, P>();
// Out with the old
property_value_store
.index
.remove_entity(&self.0.previous.make_canonical(), self.0.entity_id);
property_value_store.index.remove_entity(
&P::from_value(self.0.previous).make_canonical(),
self.0.entity_id,
);
// In with the new
property_value_store
.index
.add_entity(&self.0.current.make_canonical(), self.0.entity_id);
property_value_store.index.add_entity(
&P::from_value(self.0.current).make_canonical(),
self.0.entity_id,
);

// We decided not to do the following check.
// See `src/entity/context_extension::ContextEntitiesExt::set_property`.
Expand Down Expand Up @@ -115,7 +119,7 @@ impl<E: Entity, P: Property<E>> Clone for PartialPropertyChangeEventCore<E, P> {
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 {
pub fn new(entity_id: EntityId<E>, previous_value: <P as Property<E>>::Value) -> Self {
Self(PropertyChangeEvent {
entity_id,
current: previous_value,
Expand Down Expand Up @@ -153,15 +157,18 @@ impl<E: Entity> EntityCreatedEvent<E> {

/// Emitted when a property is updated.
/// These should not be emitted outside this module.
///
/// `current` and `previous` are `P::Value`: for legacy newtype properties this is the wrapper
/// (unchanged behavior); for primitive-form properties this is the inner primitive type.
#[derive(IxaEvent)]
#[allow(clippy::manual_non_exhaustive)]
pub struct PropertyChangeEvent<E: Entity, P: Property<E>> {
/// The [`EntityId<E>`] that changed
pub entity_id: EntityId<E>,
/// The new value
pub current: P,
pub current: <P as Property<E>>::Value,
/// The old value
pub previous: P,
pub previous: <P as Property<E>>::Value,
}
// We provide blanket impls for these because the compiler isn't smart enough to know
// this type is always `Copy`/`Clone` if we derive them.
Expand Down
Loading