diff --git a/docs/book/src/appendix_rust/turbo-fish.md b/docs/book/src/appendix_rust/turbo-fish.md index bb42c784..30e43630 100644 --- a/docs/book/src/appendix_rust/turbo-fish.md +++ b/docs/book/src/appendix_rust/turbo-fish.md @@ -21,8 +21,8 @@ let status: InfectionStatus = context.get_property(person_id); context.set_property(other_person_id, status); ``` -The generic types for querying and sampling -methods can usually be inferred by the compiler: +The generic types for querying, sampling, and entity initialization can usually +be inferred by the compiler: ```rust // A silly example, but no turbo fish is required. @@ -30,6 +30,12 @@ context.with_query_results( with!(Person, Age(30), Alive(true)), |people_set| println("{:?}", people_set) ); + +let person_id = context + .add_entity(with!(Person, Age(30), Alive(true))) + .unwrap(); + +let population = context.query_entity_count(Person); ``` A few methods always require the user to specify the generic type when they are diff --git a/docs/book/src/first_model/transmission.md b/docs/book/src/first_model/transmission.md index c2fcdca5..7c4cffcc 100644 --- a/docs/book/src/first_model/transmission.md +++ b/docs/book/src/first_model/transmission.md @@ -93,7 +93,7 @@ accomplishes the three tasks above. A few observations: which can have the value of `Some(PersonId)` or `None`. In this case, we use `Person` and no property filters, which means we want to sample from the entire population. If we wanted to, we could pass filters with the `with!` macro - (e.g., `with!(Person, Region("California"))`) The population will + (e.g., `with!(Person, Region("California"))`). The population will never be empty, so the result will never be `None`, and so we just call `unwrap()` on the `Some(PersonId)` value to get the `PersonId`. - If the sampled person is not susceptible, then the only thing this function diff --git a/docs/book/src/migration_guide.md b/docs/book/src/migration_guide.md index 15f8ba30..39d89c12 100644 --- a/docs/book/src/migration_guide.md +++ b/docs/book/src/migration_guide.md @@ -154,12 +154,25 @@ property values. The properties must be distinct, of course, and there must be a value for every "required" property, that is, for every (non-derived) property that doesn't have a default value. +Public entity initialization no longer accepts naked tuples such as +`(Age(25), InfectionStatus::Infected)`. Use `with!(Person, ...)` when you want +to provide one or more property values. + Adding a new entity with just one property value: ```rust let person_id = context.add_entity(with!(Person, Age(25))).unwrap(); ``` +To initialize an entity with only default values, pass the entity type directly: + +```rust +let person_id = context.add_entity(Person).unwrap(); +``` + +The same pattern applies to queries. Use `with!(Person, ...)` to filter by property values, or use `Person` when you +want to work with the entire population. Public query APIs no longer accept naked tuples such as `(Age(25),)`. + ### Getting a property value for an entity ```rust diff --git a/examples/basic-infection/src/infection_manager.rs b/examples/basic-infection/src/infection_manager.rs index 78cb0a80..38b86e9a 100644 --- a/examples/basic-infection/src/infection_manager.rs +++ b/examples/basic-infection/src/infection_manager.rs @@ -43,7 +43,7 @@ mod test { use ixa::prelude::*; use crate::infection_manager::InfectionStatusEvent; - use crate::people::InfectionStatus; + use crate::people::{InfectionStatus, Person}; define_data_plugin!(RecoveryPlugin, usize, 0); @@ -66,7 +66,9 @@ mod test { let population_size = 10; for _ in 0..population_size { - let person_id = context.add_entity((InfectionStatus::S,)).unwrap(); + let person_id = context + .add_entity(with!(Person, InfectionStatus::S)) + .unwrap(); context.set_property(person_id, InfectionStatus::I); } diff --git a/examples/network-hhmodel/loader.rs b/examples/network-hhmodel/loader.rs index f3b0220c..378d5fc3 100644 --- a/examples/network-hhmodel/loader.rs +++ b/examples/network-hhmodel/loader.rs @@ -41,7 +41,13 @@ struct PeopleRecord { fn create_person_from_record(context: &mut Context, record: &PeopleRecord) -> PersonId { context - .add_entity((record.id, record.age_group, record.sex, record.household_id)) + .add_entity(with!( + Person, + record.id, + record.age_group, + record.sex, + record.household_id + )) .unwrap() } @@ -94,19 +100,37 @@ mod tests { let person = people[0]; assert!(context.match_entity( person, - (Id(676), AgeGroup::Age18to64, Sex::Female, HouseholdId(1)) + with!( + Person, + Id(676), + AgeGroup::Age18to64, + Sex::Female, + HouseholdId(1) + ) )); let person = people[246]; assert!(context.match_entity( person, - (Id(213), AgeGroup::AgeUnder5, Sex::Female, HouseholdId(162)) + with!( + Person, + Id(213), + AgeGroup::AgeUnder5, + Sex::Female, + HouseholdId(162) + ) )); let person = people[1591]; assert!(context.match_entity( person, - (Id(1591), AgeGroup::Age65Plus, Sex::Male, HouseholdId(496)) + with!( + Person, + Id(1591), + AgeGroup::Age65Plus, + Sex::Male, + HouseholdId(496) + ) )); } } diff --git a/examples/network-hhmodel/network.rs b/examples/network-hhmodel/network.rs index b5e1cac8..e9bd7061 100644 --- a/examples/network-hhmodel/network.rs +++ b/examples/network-hhmodel/network.rs @@ -22,7 +22,7 @@ fn create_household_networks(context: &mut Context, people: &[PersonId]) { let household_id: HouseholdId = context.get_property(*person_id); if households.insert(household_id) { let mut members: Vec = Vec::new(); - context.with_query_results((household_id,), &mut |results| { + context.with_query_results(with!(Person, household_id), &mut |results| { members = results.to_owned_vec() }); // create a dense network @@ -43,13 +43,13 @@ fn load_edge_list>(context: &mut Context, file_name: &str, for result in reader.deserialize() { let record: EdgeRecord = result.expect("Failed to parse edge"); let mut p1_vec = Vec::new(); - context.with_query_results((Id(record.v1),), &mut |people| { + context.with_query_results(with!(Person, Id(record.v1)), &mut |people| { p1_vec = people.to_owned_vec() }); assert_eq!(p1_vec.len(), 1); let p1 = p1_vec[0]; let mut p2_vec = Vec::new(); - context.with_query_results((Id(record.v2),), &mut |people| { + context.with_query_results(with!(Person, Id(record.v2)), &mut |people| { p2_vec = people.to_owned_vec() }); assert_eq!(p2_vec.len(), 1); diff --git a/examples/network-hhmodel/seir.rs b/examples/network-hhmodel/seir.rs index cc6be56f..80aa7b80 100644 --- a/examples/network-hhmodel/seir.rs +++ b/examples/network-hhmodel/seir.rs @@ -37,11 +37,13 @@ fn calculate_waiting_time(context: &Context, shape: f64, mean_period: f64) -> f6 } fn expose_network>(context: &mut Context, beta: f64) { - let infectious_people = context.query((DiseaseStatus::I,)).to_owned_vec(); + let infectious_people = context + .query(with!(Person, DiseaseStatus::I)) + .to_owned_vec(); for infectious in infectious_people { let edges = context.get_matching_edges::(infectious, |context, edge| { - context.match_entity(edge.neighbor, (DiseaseStatus::S,)) + context.match_entity(edge.neighbor, with!(Person, DiseaseStatus::S)) }); for e in edges { @@ -187,7 +189,7 @@ mod tests { network::init(&mut context, &people); let mut to_infect = Vec::::new(); - context.with_query_results((Id(71),), &mut |people| { + context.with_query_results(with!(Person, Id(71)), &mut |people| { to_infect.extend(people); }); @@ -196,19 +198,19 @@ mod tests { context.execute(); assert_eq!( - context.query_entity_count::((DiseaseStatus::S,)), + context.query_entity_count::(with!(Person, DiseaseStatus::S)), 399 ); assert_eq!( - context.query_entity_count::((DiseaseStatus::E,)), + context.query_entity_count::(with!(Person, DiseaseStatus::E)), 0 ); assert_eq!( - context.query_entity_count::((DiseaseStatus::I,)), + context.query_entity_count::(with!(Person, DiseaseStatus::I)), 0 ); assert_eq!( - context.query_entity_count::((DiseaseStatus::R,)), + context.query_entity_count::(with!(Person, DiseaseStatus::R)), 1207 ); } diff --git a/integration-tests/ixa-runner-tests/bin/runner_generic.rs b/integration-tests/ixa-runner-tests/bin/runner_generic.rs index e09136ff..3d56b8c1 100644 --- a/integration-tests/ixa-runner-tests/bin/runner_generic.rs +++ b/integration-tests/ixa-runner-tests/bin/runner_generic.rs @@ -6,9 +6,9 @@ define_entity!(Person); fn main() { run_with_args(|context, _args, _| { - let _: PersonId = context.add_entity(()).unwrap(); - let _: PersonId = context.add_entity(()).unwrap(); - let _: PersonId = context.add_entity(()).unwrap(); + let _: PersonId = context.add_entity(Person).unwrap(); + let _: PersonId = context.add_entity(Person).unwrap(); + let _: PersonId = context.add_entity(Person).unwrap(); trace!("A TRACE message"); debug!("A DEBUG message"); diff --git a/integration-tests/ixa-runner-tests/tests/macros.rs b/integration-tests/ixa-runner-tests/tests/macros.rs index 31351d63..f3df75c3 100644 --- a/integration-tests/ixa-runner-tests/tests/macros.rs +++ b/integration-tests/ixa-runner-tests/tests/macros.rs @@ -149,7 +149,8 @@ mod tests { // Entity properties: add a Person with TestPropU32 let pid: EntityId = ctx - .add_entity(( + .add_entity(with!( + Person, TestPropU32(10u32), TestPropU32b(20u32), TestPropOpt(Some(3u8)), @@ -192,10 +193,10 @@ mod tests { // Edge type (entity-based network): create two people and add an edge of type TestEdge let p1 = ctx - .add_entity((TestPropU32(1u32), TestPropU32b(1u32))) + .add_entity(with!(Person, TestPropU32(1u32), TestPropU32b(1u32))) .unwrap(); let p2 = ctx - .add_entity((TestPropU32(2u32), TestPropU32b(2u32))) + .add_entity(with!(Person, TestPropU32(2u32), TestPropU32b(2u32))) .unwrap(); ctx.add_edge::(p1, p2, 1.0, TestEdge) .unwrap(); diff --git a/integration-tests/ixa-wasm-tests/src/infection_manager.rs b/integration-tests/ixa-wasm-tests/src/infection_manager.rs index 36ac7422..3e6f16ed 100644 --- a/integration-tests/ixa-wasm-tests/src/infection_manager.rs +++ b/integration-tests/ixa-wasm-tests/src/infection_manager.rs @@ -45,7 +45,7 @@ mod test { use ixa::prelude::*; use crate::infection_manager::InfectionStatusEvent; - use crate::people::InfectionStatus; + use crate::people::{InfectionStatus, Person}; define_data_plugin!(RecoveryPlugin, usize, 0); @@ -68,7 +68,9 @@ mod test { let population_size = 10; for _ in 0..population_size { - let person_id = context.add_entity((InfectionStatus::S,)).unwrap(); + let person_id = context + .add_entity(with!(Person, InfectionStatus::S)) + .unwrap(); context.set_property(person_id, InfectionStatus::I); } diff --git a/integration-tests/ixa-wasm-tests/src/people.rs b/integration-tests/ixa-wasm-tests/src/people.rs index 61488301..993e8d81 100644 --- a/integration-tests/ixa-wasm-tests/src/people.rs +++ b/integration-tests/ixa-wasm-tests/src/people.rs @@ -20,6 +20,6 @@ define_property!( pub fn init(context: &mut Context) { trace!("Initializing people"); for _ in 0..POPULATION { - let _: PersonId = context.add_entity(()).unwrap(); + let _: PersonId = context.add_entity(Person).unwrap(); } } diff --git a/integration-tests/ixa-wasm-tests/src/transmission_manager.rs b/integration-tests/ixa-wasm-tests/src/transmission_manager.rs index c48f063e..b7513922 100644 --- a/integration-tests/ixa-wasm-tests/src/transmission_manager.rs +++ b/integration-tests/ixa-wasm-tests/src/transmission_manager.rs @@ -10,7 +10,7 @@ define_rng!(TransmissionRng); fn attempt_infection(context: &mut Context) { trace!("Attempting infection"); let population_size: usize = context.get_entity_count::(); - let person_to_infect: PersonId = context.sample_entity(TransmissionRng, ()).unwrap(); //.sample_range(TransmissionRng, 0..population_size); + 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); @@ -52,7 +52,7 @@ mod test { fn test_attempt_infection() { let mut context = Context::new(); context.init_random(SEED); - let person_id: PersonId = context.add_entity(()).unwrap(); + let person_id: PersonId = context.add_entity(Person).unwrap(); attempt_infection(&mut context); let person_status: InfectionStatus = context.get_property(person_id); assert_eq!(person_status, InfectionStatus::I); diff --git a/ixa-bench/criterion/counts.rs b/ixa-bench/criterion/counts.rs index 422811e6..9f798639 100644 --- a/ixa-bench/criterion/counts.rs +++ b/ixa-bench/criterion/counts.rs @@ -37,7 +37,8 @@ fn populate_entities(context: &mut Context, n: usize) { context.init_random(SEED); for person in generate_population_with_seed(n, 0.2, 10.0, Some(SEED)) { - let _ = context.add_entity(( + let _ = context.add_entity(with!( + Person, Age(person.age), HomeId(person.home_id as u32), SchoolId(person.school_id as u32), @@ -59,14 +60,18 @@ pub fn criterion_benchmark(c: &mut Criterion) { // Unindexed single property group.bench_function("single_property_unindexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count(black_box((HomeId(HOME_VAL),)))); + black_box(context.query_entity_count(black_box(with!(Person, HomeId(HOME_VAL))))); }); }); // Unindexed concrete + unindexed derived property group.bench_function("concrete_plus_derived_unindexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count(black_box((HomeId(HOME_VAL), AgeGroupFoi(1))))); + black_box(context.query_entity_count(black_box(with!( + Person, + HomeId(HOME_VAL), + AgeGroupFoi(1) + )))); }); }); @@ -74,14 +79,14 @@ pub fn criterion_benchmark(c: &mut Criterion) { context.index_property::(); group.bench_function("single_property_indexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count(black_box((HomeId(HOME_VAL),)))); + black_box(context.query_entity_count(black_box(with!(Person, HomeId(HOME_VAL))))); }); }); // Unindexed multi-property group.bench_function("multi_property_unindexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count(black_box((Age(30), SchoolId(1))))); + black_box(context.query_entity_count(black_box(with!(Person, Age(30), SchoolId(1))))); }); }); @@ -89,10 +94,11 @@ pub fn criterion_benchmark(c: &mut Criterion) { context.index_property::(); group.bench_function("multi_property_indexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count(black_box(( + black_box(context.query_entity_count(black_box(with!( + Person, Age(30), SchoolId(1), - WorkplaceId(1), + WorkplaceId(1) )))); }); }); @@ -107,7 +113,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { }, |mut ctx| { ctx.index_property::(); - black_box(ctx.query_entity_count(black_box((HomeId(HOME_VAL),)))); + black_box(ctx.query_entity_count(black_box(with!(Person, HomeId(HOME_VAL))))); }, ); }); @@ -119,13 +125,13 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut ctx = Context::new(); populate_entities(&mut ctx, 5_000); ctx.index_property::(); - black_box(ctx.query_entity_count(black_box((HomeId(HOME_VAL),)))); + black_box(ctx.query_entity_count(black_box(with!(Person, HomeId(HOME_VAL))))); ctx }, |mut ctx| { populate_entities(&mut ctx, 2_000); ctx.index_property::(); - black_box(ctx.query_entity_count(black_box((HomeId(HOME_VAL),)))); + black_box(ctx.query_entity_count(black_box(with!(Person, HomeId(HOME_VAL))))); }, ); }); diff --git a/ixa-bench/criterion/index_benches.rs b/ixa-bench/criterion/index_benches.rs index 4944b5d3..b3b9db95 100644 --- a/ixa-bench/criterion/index_benches.rs +++ b/ixa-bench/criterion/index_benches.rs @@ -20,7 +20,8 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { let mut context = Context::new(); for _ in 0..100_000 { context - .add_entity(( + .add_entity(with!( + Person, Property10(context.sample_range(IndexBenchRng, 0..10)), Property100(context.sample_range(IndexBenchRng, 0..100)), MultiProperty10(context.sample_range(IndexBenchRng, 0..10)), @@ -44,7 +45,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { bencher.iter(|| { for number in &numbers { context.with_query_results( - black_box((Property10(number % 10),)), + black_box(with!(Person, Property10(number % 10))), &mut |entity_ids| { black_box(entity_ids.try_len()); }, @@ -60,9 +61,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { bencher.iter(|| { for number in &numbers { context.with_query_results( - black_box(( + black_box(with!( + Person, Property10(number.wrapping_mul(3) % 10), - Property100(*number), + Property100(*number) )), &mut |entity_ids| { black_box(entity_ids.try_len()); @@ -80,9 +82,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for number in &numbers { context.with_query_results( // We are using the fact that a query detects when it is equivalent to a multi-property. - black_box(( + black_box(with!( + Person, MultiProperty10(number.wrapping_mul(3) % 10), - MultiProperty100(*number), + MultiProperty100(*number) )), &mut |entity_ids| { black_box(entity_ids.try_len()); @@ -98,7 +101,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { |bencher| { bencher.iter(|| { for number in &numbers { - black_box(context.query_entity_count(black_box((Property10(number % 10),)))); + black_box( + context + .query_entity_count(black_box(with!(Person, Property10(number % 10)))), + ); } }); }, @@ -109,9 +115,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { |bencher| { bencher.iter(|| { for number in &numbers { - black_box(context.query_entity_count(black_box(( + black_box(context.query_entity_count(black_box(with!( + Person, Property10(number.wrapping_mul(3) % 10), - Property100(*number), + Property100(*number) )))); } }); @@ -125,9 +132,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for number in &numbers { black_box( // We are using the fact that a query detects when it is equivalent to a multi-property. - context.query_entity_count(black_box(( + context.query_entity_count(black_box(with!( + Person, MultiProperty10(number.wrapping_mul(3) % 10), - MultiProperty100(*number), + MultiProperty100(*number) ))), ); } @@ -139,8 +147,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { bencher.iter(|| { for number in &numbers { // There is no exact equivalent to `Context::query_people`. - let result_iter = - black_box(context.query_result_iterator(black_box((Property10(number % 10),)))); + let result_iter = black_box( + context + .query_result_iterator(black_box(with!(Person, Property10(number % 10)))), + ); black_box(result_iter.collect::>()); } }); @@ -152,9 +162,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { bencher.iter(|| { for number in &numbers { // There is no exact equivalent to `Context::query_people`. - let result_iter = black_box(context.query_result_iterator(black_box(( + let result_iter = black_box(context.query_result_iterator(black_box(with!( + Person, Property10(number % 10), - Property100(*number), + Property100(*number) )))); black_box(result_iter.collect::>()); } @@ -167,9 +178,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for number in &numbers { // There is no exact equivalent to `Context::query_people`. // We are using the fact that a query detects when it is equivalent to a multi-property. - let result_iter = black_box(context.query_result_iterator(black_box(( + let result_iter = black_box(context.query_result_iterator(black_box(with!( + Person, MultiProperty10(number % 10), - MultiProperty100(*number), + MultiProperty100(*number) )))); black_box(result_iter.collect::>()); } diff --git a/ixa-bench/criterion/large_dataset.rs b/ixa-bench/criterion/large_dataset.rs index a8ab63e7..9123db4d 100644 --- a/ixa-bench/criterion/large_dataset.rs +++ b/ixa-bench/criterion/large_dataset.rs @@ -37,7 +37,8 @@ define_multi_property!((Age, SchoolId, WorkplaceId), Person); fn initialize_entities(context: &mut Context) { for person in generate_population_with_seed(10_000, 0.2, 10.0, Some(SEED)) { context - .add_entity(( + .add_entity(with!( + Person, Age(person.age), HomeId(person.home_id as u32), SchoolId(person.school_id as u32), @@ -58,7 +59,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { criterion.bench_function("bench_query_population_property_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count((HomeId(1),))); + black_box(context.query_entity_count(with!(Person, HomeId(1)))); }); }); @@ -67,7 +68,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { "bench_query_population_indexed_property_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count((HomeId(1),))); + black_box(context.query_entity_count(with!(Person, HomeId(1)))); }); }, ); @@ -76,7 +77,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { "bench_query_population_derived_property_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count((AgeGroupRisk::Senior,))); + black_box(context.query_entity_count(with!(Person, AgeGroupRisk::Senior))); }); }, ); @@ -86,7 +87,12 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { "bench_query_population_multi_unindexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count((Age(30), SchoolId(1), WorkplaceId(1)))); + black_box(context.query_entity_count(with!( + Person, + Age(30), + SchoolId(1), + WorkplaceId(1) + ))); }); }, ); @@ -94,7 +100,12 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { context.index_property::(); criterion.bench_function("bench_query_population_multi_indexed_entities", |bencher| { bencher.iter(|| { - black_box(context.query_entity_count((Age(30), SchoolId(1), WorkplaceId(1)))); + black_box(context.query_entity_count(with!( + Person, + Age(30), + SchoolId(1), + WorkplaceId(1) + ))); }); }); @@ -106,7 +117,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { bencher.iter(|| { black_box(context.match_entity( entity_ids[person_idx % total_population], - (Age(30u8), SchoolId(1u32), WorkplaceId(1u32)), + with!(Person, Age(30u8), SchoolId(1u32), WorkplaceId(1u32)), )); person_idx += 1; }); @@ -121,7 +132,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { |mut entities| { context.filter_entities( &mut entities, - (Age(30u8), SchoolId(1u32), WorkplaceId(1u32)), + with!(Person, Age(30u8), SchoolId(1u32), WorkplaceId(1u32)), ); }, BatchSize::SmallInput, @@ -137,7 +148,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { |mut entities| { context.filter_entities( &mut entities, - (Age(30u8), HomeId(1u32), WorkplaceId(1u32)), + with!(Person, Age(30u8), HomeId(1u32), WorkplaceId(1u32)), ); }, BatchSize::SmallInput, diff --git a/ixa-bench/criterion/property_semantics.rs b/ixa-bench/criterion/property_semantics.rs index 273362e4..e15cd31d 100644 --- a/ixa-bench/criterion/property_semantics.rs +++ b/ixa-bench/criterion/property_semantics.rs @@ -99,7 +99,9 @@ fn build_float_query_context() -> Context { let mut context = Context::new(); for i in 0..100_000 { let value = ((i % 2048) as f64) * 0.25; - context.add_entity((FloatQueryValue(value),)).unwrap(); + context + .add_entity(with!(Person, FloatQueryValue(value))) + .unwrap(); } context.index_property::(); context @@ -114,7 +116,9 @@ fn float_query_benchmarks(criterion: &mut Criterion) { group.bench_function("float_query_entity_count_indexed", |bencher| { bencher.iter(|| { for probe in &probes { - black_box(context.query_entity_count(black_box((FloatQueryValue(*probe),)))); + black_box( + context.query_entity_count(black_box(with!(Person, FloatQueryValue(*probe)))), + ); } }); }); @@ -123,7 +127,7 @@ fn float_query_benchmarks(criterion: &mut Criterion) { bencher.iter(|| { for probe in &probes { context.with_query_results( - black_box((FloatQueryValue(*probe),)), + black_box(with!(Person, FloatQueryValue(*probe))), &mut |entity_ids| { black_box(entity_ids.try_len()); }, @@ -141,7 +145,8 @@ fn build_value_change_counter_context() -> Context { for i in 0..10_000 { let entity_id = context - .add_entity(( + .add_entity(with!( + Person, CounterBucket((i % 32) as u8), FloatCounterValue(((i % 256) as f64) * 0.5), )) diff --git a/ixa-bench/criterion/sample_entity_scaling.rs b/ixa-bench/criterion/sample_entity_scaling.rs index 90c4705e..403361c9 100644 --- a/ixa-bench/criterion/sample_entity_scaling.rs +++ b/ixa-bench/criterion/sample_entity_scaling.rs @@ -29,7 +29,8 @@ fn setup_context(population_size: usize) -> Context { for _ in 0..population_size { context - .add_entity(( + .add_entity(with!( + Mosquito, Species(context.sample_range(SampleScalingRng, 0..10)), Region(context.sample_range(SampleScalingRng, 0..10)), Unindexed10(context.sample_range(SampleScalingRng, 0..10)), @@ -101,7 +102,8 @@ pub fn bench_sample_entity_whole_population(c: &mut Criterion, results: Results) group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { let ns = bench_ns_per_sample(b, || { - let _: Option> = context.sample_entity(SampleScalingRng, ()); + let _: Option> = + context.sample_entity(SampleScalingRng, Mosquito); }); results @@ -123,7 +125,7 @@ pub fn bench_sample_entity_single_property_indexed(c: &mut Criterion, results: R group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { let ns = bench_ns_per_sample(b, || { - let _ = context.sample_entity(SampleScalingRng, (Species(5),)); + let _ = context.sample_entity(SampleScalingRng, with!(Mosquito, Species(5))); }); results @@ -145,7 +147,8 @@ pub fn bench_sample_entity_multi_property_indexed(c: &mut Criterion, results: Re group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { let ns = bench_ns_per_sample(b, || { - let _ = context.sample_entity(SampleScalingRng, (Species(5), Region(3))); + let _ = + context.sample_entity(SampleScalingRng, with!(Mosquito, Species(5), Region(3))); }); results @@ -169,7 +172,7 @@ pub fn bench_sample_entity_single_property_unindexed(c: &mut Criterion, results: group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { let ns = bench_ns_per_sample(b, || { - let _ = context.sample_entity(SampleScalingRng, (Unindexed10(5),)); + let _ = context.sample_entity(SampleScalingRng, with!(Mosquito, Unindexed10(5))); }); results diff --git a/ixa-bench/criterion/sample_people.rs b/ixa-bench/criterion/sample_people.rs index 82975724..afda1618 100644 --- a/ixa-bench/criterion/sample_people.rs +++ b/ixa-bench/criterion/sample_people.rs @@ -50,7 +50,8 @@ fn setup() -> (Context, Vec) { // Add population for _ in 0..100_000 { context - .add_entity(( + .add_entity(with!( + Person, Property10(context.sample_range(SampleBenchRng, 0..10)), Property100(context.sample_range(SampleBenchRng, 0..100)), Unindexed10(context.sample_range(SampleBenchRng, 0..10)), @@ -72,9 +73,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { let counts = black_box(&counts); for value in counts { - let _selected = black_box( - context.sample_entity(SampleBenchRng, black_box((Property100(*value),))), - ); + let _selected = black_box(context.sample_entity( + SampleBenchRng, + black_box(with!(Person, Property100(*value))), + )); } }); }); @@ -88,7 +90,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.count_and_sample_entity( SampleBenchRng, - black_box((Property100(*value),)), + black_box(with!(Person, Property100(*value))), )); } }); @@ -104,7 +106,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.sample_entity( SampleBenchRng, - black_box((Property10(*value % 10), Property100(*value))), + black_box(with!(Person, Property10(*value % 10), Property100(*value))), )); } }); @@ -118,7 +120,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.sample_entities( SampleBenchRng, - black_box((Property100(*value),)), + black_box(with!(Person, Property100(*value))), *black_box(value) as usize, )); } @@ -134,7 +136,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.sample_entities( SampleBenchRng, - black_box((Property10(*value % 10), Property100(*value))), + black_box(with!(Person, Property10(*value % 10), Property100(*value))), *black_box(value) as usize, )); } @@ -148,9 +150,10 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { let counts = black_box(&counts); for value in counts { - let _selected = black_box( - context.sample_entity(SampleBenchRng, black_box((Unindexed10(*value % 10),))), - ); + let _selected = black_box(context.sample_entity( + SampleBenchRng, + black_box(with!(Person, Unindexed10(*value % 10))), + )); } }); }); @@ -164,7 +167,7 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.sample_entities( SampleBenchRng, - black_box((Unindexed10(*value % 10),)), + black_box(with!(Person, Unindexed10(*value % 10))), *black_box(value) as usize, )); } @@ -181,7 +184,11 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.sample_entity( SampleBenchRng, - black_box((Unindexed10(*value % 10), AgeGroupFoi(*value % 3))), + black_box(with!( + Person, + Unindexed10(*value % 10), + AgeGroupFoi(*value % 3) + )), )); } }); @@ -197,7 +204,11 @@ pub fn criterion_benchmark(criterion: &mut Criterion) { for value in counts { let _selected = black_box(context.count_and_sample_entity( SampleBenchRng, - black_box((Unindexed10(*value % 10), AgeGroupFoi(*value % 3))), + black_box(with!( + Person, + Unindexed10(*value % 10), + AgeGroupFoi(*value % 3) + )), )); } }); diff --git a/ixa-bench/criterion/set_property.rs b/ixa-bench/criterion/set_property.rs index ba136edf..100fa9cf 100644 --- a/ixa-bench/criterion/set_property.rs +++ b/ixa-bench/criterion/set_property.rs @@ -83,7 +83,9 @@ pub fn criterion_benchmark(c: &mut Criterion) { group.bench_function("set_property_no_dependents", |bencher| { let mut context = Context::new(); - let person = context.add_entity((IndependentValue(0),)).unwrap(); + let person = context + .add_entity(with!(Person, IndependentValue(0))) + .unwrap(); let mut next_value = 0u64; bencher.iter(|| { @@ -94,7 +96,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { group.bench_function("set_property_three_dependents", |bencher| { let mut context = Context::new(); - let person = context.add_entity((BaseValue(0),)).unwrap(); + let person = context.add_entity(with!(Person, BaseValue(0))).unwrap(); let mut next_value = 0u64; bencher.iter(|| { @@ -106,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { group.bench_function("set_property_three_dependents_mixed", |bencher| { let mut context = Context::new(); let person = context - .add_entity((MixedBaseValue(0), IndependentValue(0))) + .add_entity(with!(Person, MixedBaseValue(0), IndependentValue(0))) .unwrap(); context.index_property::(); diff --git a/ixa-bench/src/reference_sir/sir_ixa.rs b/ixa-bench/src/reference_sir/sir_ixa.rs index 71dfc2cf..1efbef5a 100644 --- a/ixa-bench/src/reference_sir/sir_ixa.rs +++ b/ixa-bench/src/reference_sir/sir_ixa.rs @@ -52,13 +52,13 @@ impl InfectionLoop for Context { self.get_data(ModelStatsPlugin) } fn infected_people(&self) -> usize { - self.query_entity_count::((InfectionStatus::Infectious,)) + self.query_entity_count(with!(Person, InfectionStatus::Infectious)) } fn random_person(&mut self) -> Option { - self.sample_entity(NextPersonRng, ()) + self.sample_entity(NextPersonRng, Person) } fn random_infected_person(&mut self) -> Option { - self.sample_entity(NextPersonRng, (InfectionStatus::Infectious,)) + self.sample_entity(NextPersonRng, with!(Person, InfectionStatus::Infectious)) } fn infect_person(&mut self, p: PersonId, t: Option) { if self.get_property::<_, InfectionStatus>(p) != InfectionStatus::Susceptible { @@ -149,13 +149,13 @@ impl InfectionLoop for Context { // Set up population for _ in 0..population { - self.add_entity::(()).unwrap(); + self.add_entity(Person).unwrap(); } // Seed infections let sampled_entities: Vec = self.sample_entities( NextPersonRng, - (InfectionStatus::Susceptible,), + with!(Person, InfectionStatus::Susceptible), initial_infections, ); for p in sampled_entities { @@ -222,7 +222,7 @@ mod test { assert_eq!(model.ctx.infected_people(), 5); let p = model .ctx - .sample_entity(NextPersonRng, (InfectionStatus::Susceptible,)) + .sample_entity(NextPersonRng, with!(Person, InfectionStatus::Susceptible)) .unwrap(); model.ctx.infect_person(p, Some(0.0)); assert_eq!(model.ctx.infected_people(), 6); diff --git a/src/context.rs b/src/context.rs index c6473d77..94cf4094 100644 --- a/src/context.rs +++ b/src/context.rs @@ -534,7 +534,7 @@ mod tests { use ixa_derive::IxaEvent; use super::*; - use crate::{define_data_plugin, define_entity, define_property, ContextEntitiesExt}; + use crate::{define_data_plugin, define_entity, define_property, with, ContextEntitiesExt}; define_data_plugin!(ComponentA, Vec, vec![]); @@ -1188,7 +1188,7 @@ mod tests { // This test verifies that shutdown_requested is properly reset after // being acted upon. This allows the context to be reused after shutdown. let mut context = Context::new(); - let _: PersonId = context.add_entity((Age(50),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(50))).unwrap(); // Schedule a plan at time 0.0 that calls shutdown context.add_plan(0.0, |ctx| { @@ -1202,7 +1202,7 @@ mod tests { // Add a new plan at time 2.0 context.add_plan(2.0, |ctx| { - let _: PersonId = ctx.add_entity((Age(50),)).unwrap(); + let _: PersonId = ctx.add_entity(with!(Person, Age(50))).unwrap(); }); // Second execute - should execute the new plan diff --git a/src/entity/context_extension.rs b/src/entity/context_extension.rs index 1422677e..096bc1ef 100644 --- a/src/entity/context_extension.rs +++ b/src/entity/context_extension.rs @@ -1,4 +1,3 @@ -use std::any::{Any, TypeId}; use std::hash::Hash; use smallvec::SmallVec; @@ -7,7 +6,7 @@ use crate::entity::entity_set::{EntitySet, EntitySetIterator, SourceSet}; use crate::entity::events::{EntityCreatedEvent, PartialPropertyChangeEventBox}; use crate::entity::index::{IndexCountResult, IndexSetResult, PropertyIndexType}; use crate::entity::property::Property; -use crate::entity::property_list::PropertyList; +use crate::entity::property_list::{PropertyInitializationList, PropertyList}; use crate::entity::query::Query; use crate::entity::value_change_counter::StratifiedValueChangeCounter; use crate::entity::{Entity, EntityId, PopulationIterator}; @@ -96,7 +95,7 @@ fn handle_periodic_value_change_count_event( /// A trait extension for [`Context`] that exposes entity-related /// functionality. pub trait ContextEntitiesExt { - fn add_entity>( + fn add_entity>( &mut self, property_list: PL, ) -> Result, IxaError>; @@ -177,13 +176,13 @@ pub trait ContextEntitiesExt { /// Gives the count of distinct entity IDs satisfying the query. This is especially /// efficient for indexed queries. /// - /// Supplying an empty query `()` is equivalent to calling `get_entity_count::()`. + /// Supplying a naked entity, e.g. `Person`, is equivalent to calling `get_entity_count::()`. fn query_entity_count>(&self, query: Q) -> usize; /// Sample a single entity uniformly from the query results. Returns `None` if the /// query's result set is empty. /// - /// To sample from the entire population, pass in the empty query `()`. + /// To sample from the entire population, pass the entity type directly, for example `Person`. fn sample_entity(&self, rng_id: R, query: Q) -> Option> where E: Entity, @@ -194,7 +193,7 @@ pub trait ContextEntitiesExt { /// Count query results and sample a single entity uniformly from them. /// /// Returns `(count, sample)`, where `sample` is `None` iff `count == 0`. - /// To sample from the entire population, pass in the empty query `()`. + /// To sample from the entire population, pass the entity type directly, for example `Person`. fn count_and_sample_entity(&self, rng_id: R, query: Q) -> (usize, Option>) where E: Entity, @@ -206,7 +205,7 @@ pub trait ContextEntitiesExt { /// query's result set has fewer than `requested` entities, the entire result /// set is returned. /// - /// To sample from the entire population, pass in the empty query `()`. + /// To sample from the entire population, pass the entity type directly, for example `Person`. fn sample_entities(&self, rng_id: R, query: Q, n: usize) -> Vec> where E: Entity, @@ -234,7 +233,7 @@ pub trait ContextEntitiesExt { } impl ContextEntitiesExt for Context { - fn add_entity>( + fn add_entity>( &mut self, property_list: PL, ) -> Result, IxaError> { @@ -427,7 +426,7 @@ impl ContextEntitiesExt for Context { ) { // The fast path for indexed queries. - // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `Query::new_query_result`. + // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `QueryInternal::new_query_result`. // The difference is, we access the index set if we find it. if let Some(multi_property_id) = query.multi_property_id() { let property_store = self.entity_store.get_property_store::(); @@ -448,8 +447,8 @@ impl ContextEntitiesExt for Context { // If the property is not indexed, we fall through. } - // Special case the empty query, which creates a set containing the entire population. - if query.type_id() == TypeId::of::<()>() { + // Special case a whole-population query. + if query.is_empty_query() { warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::() for working with the entire population."); callback(EntitySet::from_source(SourceSet::PopulationRange( 0..self.get_entity_count::(), @@ -467,7 +466,7 @@ impl ContextEntitiesExt for Context { fn query_entity_count>(&self, query: Q) -> usize { // The fast path for indexed queries. // - // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `Query::new_query_result`. + // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `QueryInternal::new_query_result`. if let Some(multi_property_id) = query.multi_property_id() { let property_store = self.entity_store.get_property_store::(); let query_parts = query.query_parts(); @@ -489,7 +488,7 @@ impl ContextEntitiesExt for Context { R: RngId + 'static, R::RngType: Rng, { - if query.type_id() == TypeId::of::<()>() { + if query.is_empty_query() { let population = self.get_entity_count::(); return self.sample(rng_id, move |rng| { if population == 0 { @@ -516,7 +515,7 @@ impl ContextEntitiesExt for Context { R: RngId + 'static, R::RngType: Rng, { - if query.type_id() == TypeId::of::<()>() { + if query.is_empty_query() { let population = self.get_entity_count::(); return self.sample(rng_id, move |rng| { if population == 0 { @@ -542,7 +541,7 @@ impl ContextEntitiesExt for Context { R: RngId + 'static, R::RngType: Rng, { - if query.type_id() == TypeId::of::<()>() { + if query.is_empty_query() { let population = self.get_entity_count::(); return self.sample(rng_id, move |rng| { if population == 0 { @@ -591,11 +590,12 @@ mod tests { use std::rc::Rc; use super::*; + use crate::entity::query::QueryInternal; use crate::hashing::IndexSet; use crate::prelude::PropertyChangeEvent; use crate::{ define_derived_property, define_entity, define_multi_property, define_property, define_rng, - impl_property, + impl_property, with, }; define_entity!(Animal); @@ -725,15 +725,22 @@ mod tests { let mut context = Context::new(); let _person1 = context - .add_entity((Age(12), InfectionStatus::Susceptible, Vaccinated(true))) + .add_entity(with!( + Person, + Age(12), + InfectionStatus::Susceptible, + Vaccinated(true) + )) .unwrap(); assert_eq!(context.get_entity_count::(), 1); - let _person2 = context.add_entity((Age(34), Vaccinated(true))).unwrap(); + let _person2 = context + .add_entity(with!(Person, Age(34), Vaccinated(true))) + .unwrap(); assert_eq!(context.get_entity_count::(), 2); // Age is the only required property - let _person3 = context.add_entity((Age(120),)).unwrap(); + let _person3 = context.add_entity(with!(Person, Age(120))).unwrap(); assert_eq!(context.get_entity_count::(), 3); } @@ -765,8 +772,8 @@ mod tests { let existing_value = Age(12); let missing_value = Age(99); - let _ = context.add_entity((existing_value,)).unwrap(); - let _ = context.add_entity((existing_value,)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); (context, existing_value, missing_value) } @@ -783,32 +790,40 @@ mod tests { let (context, existing_value, missing_value) = setup_context_for_index_tests(mode); let mut existing_len = 0; - context.with_query_results((existing_value,), &mut |people_set| { + context.with_query_results(with!(Person, existing_value), &mut |people_set| { existing_len = people_set.into_iter().count(); }); assert_eq!(existing_len, 2, "Wrong length for {mode:?}"); let mut missing_len = 0; - context.with_query_results((missing_value,), &mut |people_set| { + context.with_query_results(with!(Person, missing_value), &mut |people_set| { missing_len = people_set.into_iter().count(); }); assert_eq!(missing_len, 0); - let existing_count = context.query_result_iterator((existing_value,)).count(); + let existing_count = context + .query_result_iterator(with!(Person, existing_value)) + .count(); assert_eq!(existing_count, 2); - let missing_count = context.query_result_iterator((missing_value,)).count(); + let missing_count = context + .query_result_iterator(with!(Person, missing_value)) + .count(); assert_eq!(missing_count, 0); - assert_eq!(context.query_entity_count((existing_value,)), 2); - assert_eq!(context.query_entity_count((missing_value,)), 0); + assert_eq!(context.query_entity_count(with!(Person, existing_value)), 2); + assert_eq!(context.query_entity_count(with!(Person, missing_value)), 0); } } #[test] fn add_an_entity_without_required_properties() { let mut context = Context::new(); - let result = context.add_entity((InfectionStatus::Susceptible, Vaccinated(true))); + let result = context.add_entity(with!( + Person, + InfectionStatus::Susceptible, + Vaccinated(true) + )); assert!(matches!( result, @@ -821,7 +836,7 @@ mod tests { let mut context = Context::new(); // Create a person with required Age property - let person = context.add_entity((Age(25),)).unwrap(); + let person = context.add_entity(with!(Person, Age(25))).unwrap(); // Retrieve and check their values let age: Age = context.get_property(person); @@ -851,7 +866,12 @@ mod tests { // Create a person with explicit property values let person = context - .add_entity((Age(25), InfectionStatus::Recovered, Vaccinated(true))) + .add_entity(with!( + Person, + Age(25), + InfectionStatus::Recovered, + Vaccinated(true) + )) .unwrap(); // Retrieve and check their values @@ -885,17 +905,17 @@ mod tests { // Create entities of different kinds for _ in 0..7 { - let _: PersonId = context.add_entity((Age(25),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(25))).unwrap(); } for _ in 0..5 { - let _: AnimalId = context.add_entity((Legs(2),)).unwrap(); + let _: AnimalId = context.add_entity(with!(Animal, Legs(2))).unwrap(); } assert_eq!(context.get_entity_count::(), 5); assert_eq!(context.get_entity_count::(), 7); - let _: PersonId = context.add_entity((Age(30),)).unwrap(); - let _: AnimalId = context.add_entity((Legs(8),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(30))).unwrap(); + let _: AnimalId = context.add_entity(with!(Animal, Legs(8))).unwrap(); assert_eq!(context.get_entity_count::(), 6); assert_eq!(context.get_entity_count::(), 8); @@ -906,11 +926,11 @@ mod tests { let mut context = Context::new(); context.init_random(42); for age in [10u8, 20, 30] { - let _: PersonId = context.add_entity((Age(age),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap(); } let (count, sampled) = - context.count_and_sample_entity::(EntityContextTestRng, ()); + context.count_and_sample_entity::(EntityContextTestRng, Person); assert_eq!(count, 3); assert!(sampled.is_some()); } @@ -920,10 +940,10 @@ mod tests { let mut context = Context::new(); context.init_random(43); for age in [10u8, 20, 30, 80] { - let _: PersonId = context.add_entity((Age(age),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap(); } - let query = (AgeGroup::Adult,); + let query = with!(Person, AgeGroup::Adult); let expected_count = context.query_entity_count(query); let (count, sampled) = context.count_and_sample_entity(EntityContextTestRng, query); assert_eq!(count, expected_count); @@ -938,13 +958,28 @@ mod tests { let mut context = Context::new(); let expected_high_id: PersonId = context - .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible)) + .add_entity(with!( + Person, + Age(77), + Vaccinated(false), + InfectionStatus::Susceptible + )) .unwrap(); let expected_med_id: PersonId = context - .add_entity((Age(30), Vaccinated(false), InfectionStatus::Susceptible)) + .add_entity(with!( + Person, + Age(30), + Vaccinated(false), + InfectionStatus::Susceptible + )) .unwrap(); let expected_low_id: PersonId = context - .add_entity((Age(3), Vaccinated(true), InfectionStatus::Recovered)) + .add_entity(with!( + Person, + Age(3), + Vaccinated(true), + InfectionStatus::Recovered + )) .unwrap(); let actual_high: RiskLevel = context.get_property(expected_high_id); @@ -987,7 +1022,12 @@ mod tests { // Should not emit change events let expected_high_id: PersonId = context - .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible)) + .add_entity(with!( + Person, + Age(77), + Vaccinated(false), + InfectionStatus::Susceptible + )) .unwrap(); // Should emit change events @@ -1008,8 +1048,8 @@ mod tests { let mut context = Context::new(); context.set_global_property_value(GlobalDummy, 18).unwrap(); - let child = context.add_entity((Age(17),)).unwrap(); - let adult = context.add_entity((Age(19),)).unwrap(); + let child = context.add_entity(with!(Person, Age(17))).unwrap(); + let adult = context.add_entity(with!(Person, Age(19))).unwrap(); let child_computed: MyDerivedProperty = context.get_property(child); assert_eq!(child_computed, MyDerivedProperty(17+18)); @@ -1022,7 +1062,9 @@ mod tests { #[test] fn observe_diamond_property_change() { let mut context = Context::new(); - let person = context.add_entity((Age(17), IsSwimmer(true))).unwrap(); + let person = context + .add_entity(with!(Person, Age(17), IsSwimmer(true))) + .unwrap(); let is_adult_athlete: AdultAthlete = context.get_property(person); assert!(!is_adult_athlete.0); @@ -1071,17 +1113,26 @@ mod tests { let vaccination_status: bool = rng.random_bool(0.5); let age: u8 = rng.random_range(0..100); context - .add_entity((Age(age), infection_status, Vaccinated(vaccination_status))) + .add_entity(with!( + Person, + Age(age), + infection_status, + Vaccinated(vaccination_status) + )) .unwrap(); } context.index_property::(); // Force an index build by running a query. - let _ = context.query_result_iterator((InfectionStatus::Susceptible, Vaccinated(true))); + let _ = context.query_result_iterator(with!( + Person, + InfectionStatus::Susceptible, + Vaccinated(true) + )); // Capture the set given by `with_query_results`. let mut result_entities: IndexSet> = IndexSet::default(); context.with_query_results( - (InfectionStatus::Susceptible, Vaccinated(true)), + with!(Person, InfectionStatus::Susceptible, Vaccinated(true)), &mut |result_set| { result_entities = result_set.into_iter().collect::>(); }, @@ -1119,16 +1170,31 @@ mod tests { fn query_returns_entity_set_and_query_result_iterator_remains_compatible() { let mut context = Context::new(); let p1 = context - .add_entity((Age(21), InfectionStatus::Susceptible, Vaccinated(true))) + .add_entity(with!( + Person, + Age(21), + InfectionStatus::Susceptible, + Vaccinated(true) + )) .unwrap(); let _p2 = context - .add_entity((Age(22), InfectionStatus::Susceptible, Vaccinated(false))) + .add_entity(with!( + Person, + Age(22), + InfectionStatus::Susceptible, + Vaccinated(false) + )) .unwrap(); let p3 = context - .add_entity((Age(23), InfectionStatus::Infected, Vaccinated(true))) + .add_entity(with!( + Person, + Age(23), + InfectionStatus::Infected, + Vaccinated(true) + )) .unwrap(); - let query = (Vaccinated(true),); + let query = with!(Person, Vaccinated(true)); let from_set = context .query::(query) @@ -1150,60 +1216,114 @@ mod tests { context.index_property::(); context.index_property::(); - let person1 = context.add_entity((Age(22),)).unwrap(); - let person2 = context.add_entity((Age(22),)).unwrap(); + let person1 = context.add_entity(with!(Person, Age(22))).unwrap(); + let person2 = context.add_entity(with!(Person, Age(22))).unwrap(); for _ in 0..4 { - let _: PersonId = context.add_entity((Age(22),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap(); } // Check non-derived property index is correctly maintained assert_eq!( - context.query_entity_count((InfectionStatus::Susceptible,)), + context.query_entity_count(with!(Person, InfectionStatus::Susceptible)), 6 ); - assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0); - assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Infected)), + 0 + ); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Recovered)), + 0 + ); context.set_property(person1, InfectionStatus::Infected); assert_eq!( - context.query_entity_count((InfectionStatus::Susceptible,)), + context.query_entity_count(with!(Person, InfectionStatus::Susceptible)), 5 ); - assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 1); - assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Infected)), + 1 + ); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Recovered)), + 0 + ); context.set_property(person1, InfectionStatus::Recovered); assert_eq!( - context.query_entity_count((InfectionStatus::Susceptible,)), + context.query_entity_count(with!(Person, InfectionStatus::Susceptible)), 5 ); - assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0); - assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 1); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Infected)), + 0 + ); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Recovered)), + 1 + ); // Check derived property index is correctly maintained. - assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0); - assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 6); - assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Child)), + 0 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Adult)), + 6 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Senior)), + 0 + ); context.set_property(person2, Age(12)); - assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1); - assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 5); - assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Child)), + 1 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Adult)), + 5 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Senior)), + 0 + ); context.set_property(person1, Age(75)); - assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1); - assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4); - assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 1); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Child)), + 1 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Adult)), + 4 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Senior)), + 1 + ); context.set_property(person2, Age(77)); - assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0); - assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4); - assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 2); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Child)), + 0 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Adult)), + 4 + ); + assert_eq!( + context.query_entity_count(with!(Person, AgeGroup::Senior)), + 2 + ); } #[test] @@ -1213,21 +1333,24 @@ mod tests { // Half will have the default value. for idx in 0..10 { if idx % 2 == 0 { - context.add_entity((Age(22),)).unwrap(); + context.add_entity(with!(Person, Age(22))).unwrap(); } else { context - .add_entity((Age(22), InfectionStatus::Recovered)) + .add_entity(with!(Person, Age(22), InfectionStatus::Recovered)) .unwrap(); } } // The tail also has the default value for _ in 0..10 { - let _: PersonId = context.add_entity((Age(22),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap(); } - assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 5); assert_eq!( - context.query_entity_count((InfectionStatus::Susceptible,)), + context.query_entity_count(with!(Person, InfectionStatus::Recovered)), + 5 + ); + assert_eq!( + context.query_entity_count(with!(Person, InfectionStatus::Susceptible)), 15 ); } @@ -1237,10 +1360,13 @@ mod tests { let mut context = Context::new(); for _ in 0..10 { - let _: PersonId = context.add_entity((Age(22),)).unwrap(); + let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap(); } - assert_eq!(context.query_entity_count((AdultAthlete(false),)), 10); + assert_eq!( + context.query_entity_count(with!(Person, AdultAthlete(false))), + 10 + ); } #[test] @@ -1281,7 +1407,12 @@ mod tests { }); let person = context - .add_entity((Age(10), CounterValue(0), CounterStratum(true))) + .add_entity(with!( + Person, + Age(10), + CounterValue(0), + CounterStratum(true) + )) .unwrap(); context.add_plan(0.1, move |context| { context.set_property(person, CounterValue(1)); @@ -1297,7 +1428,12 @@ mod tests { fn periodic_value_change_counts_report_and_clear() { let mut context = Context::new(); let person = context - .add_entity((Age(10), CounterValue(0), CounterStratum(true))) + .add_entity(with!( + Person, + Age(10), + CounterValue(0), + CounterStratum(true) + )) .unwrap(); let observed = Rc::new(RefCell::new(Vec::::new())); @@ -1326,7 +1462,12 @@ mod tests { context.set_start_time(-2.0); let person = context - .add_entity((Age(10), CounterValue(0), CounterStratum(true))) + .add_entity(with!( + Person, + Age(10), + CounterValue(0), + CounterStratum(true) + )) .unwrap(); let observed_times = Rc::new(RefCell::new(Vec::::new())); diff --git a/src/entity/entity_set/entity_set.rs b/src/entity/entity_set/entity_set.rs index e8a37004..cf80834d 100644 --- a/src/entity/entity_set/entity_set.rs +++ b/src/entity/entity_set/entity_set.rs @@ -316,7 +316,7 @@ mod tests { use super::*; use crate::entity::ContextEntitiesExt; use crate::hashing::IndexSet; - use crate::{define_derived_property, define_entity, define_property, Context}; + use crate::{define_derived_property, define_entity, define_property, with, Context}; define_entity!(Person); define_property!(struct Age(u8), Person); @@ -585,7 +585,7 @@ mod tests { assert_eq!(indexed.try_len(), Some(3)); let mut context = Context::new(); - context.add_entity((Age(10),)).unwrap(); + context.add_entity(with!(Person, Age(10))).unwrap(); let property_source = SourceSet::::new(Age(10), &context).unwrap(); assert!(matches!(property_source, SourceSet::PropertySet(_))); let property_set = EntitySet::::from_source(property_source); @@ -614,11 +614,11 @@ mod tests { #[test] fn clone_preserves_unindexed_concrete_property_query_results() { let mut context = Context::new(); - let p1 = context.add_entity((Age(10),)).unwrap(); - let p2 = context.add_entity((Age(10),)).unwrap(); - let _p3 = context.add_entity((Age(11),)).unwrap(); + let p1 = context.add_entity(with!(Person, Age(10))).unwrap(); + let p2 = context.add_entity(with!(Person, Age(10))).unwrap(); + let _p3 = context.add_entity(with!(Person, Age(11))).unwrap(); - let set = context.query::((Age(10),)); + let set = context.query::(with!(Person, Age(10))); assert_eq!(set.try_len(), None); let cloned = set.clone(); @@ -634,11 +634,11 @@ mod tests { #[test] fn clone_preserves_unindexed_derived_property_query_results() { let mut context = Context::new(); - let _p1 = context.add_entity((Age(64),)).unwrap(); - let p2 = context.add_entity((Age(65),)).unwrap(); - let p3 = context.add_entity((Age(90),)).unwrap(); + let _p1 = context.add_entity(with!(Person, Age(64))).unwrap(); + let p2 = context.add_entity(with!(Person, Age(65))).unwrap(); + let p3 = context.add_entity(with!(Person, Age(90))).unwrap(); - let set = context.query::((Senior(true),)); + let set = context.query::(with!(Person, Senior(true))); assert_eq!(set.try_len(), None); let cloned = set.clone(); diff --git a/src/entity/entity_set/entity_set_iterator.rs b/src/entity/entity_set/entity_set_iterator.rs index 2df72c49..14472572 100644 --- a/src/entity/entity_set/entity_set_iterator.rs +++ b/src/entity/entity_set/entity_set_iterator.rs @@ -456,7 +456,7 @@ mod tests { use crate::entity::entity_set::{EntitySet, SourceSet}; use crate::hashing::IndexSet as FxIndexSet; use crate::prelude::*; - use crate::{define_derived_property, define_property}; + use crate::{define_derived_property, define_property, with}; define_entity!(Person); @@ -505,7 +505,11 @@ mod tests { let mut people = Vec::new(); for i in 0..size { let person = context - .add_entity((ExplicitProp((i % 20) as u8), ExplicitProp2(i % 2 == 0))) + .add_entity(with!( + Person, + ExplicitProp((i % 20) as u8), + ExplicitProp2(i % 2 == 0) + )) .unwrap(); people.push(person); } @@ -521,7 +525,7 @@ mod tests { setup_test_population(&mut context, 100); let results = context - .query_result_iterator((ExplicitProp(5),)) + .query_result_iterator(with!(Person, ExplicitProp(5))) .collect::>(); assert_eq!(results.len(), 5); // 5, 25, 45, 65, 85 @@ -541,7 +545,7 @@ mod tests { setup_test_population(&mut context, 100); let results = context - .query_result_iterator((ExplicitProp(7),)) + .query_result_iterator(with!(Person, ExplicitProp(7))) .collect::>(); assert_eq!(results.len(), 5); // 7, 27, 47, 67, 87 @@ -560,12 +564,12 @@ mod tests { // Create people without setting ConstantProp - they'll use default value 42 for _ in 0..50 { context - .add_entity((ExplicitProp(1), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(1), ExplicitProp2(false))) .unwrap(); } let results = context - .query_result_iterator((ConstantProp(42), ExplicitProp2(false))) + .query_result_iterator(with!(Person, ConstantProp(42), ExplicitProp2(false))) .collect::>(); assert_eq!(results.len(), 50); @@ -585,12 +589,12 @@ mod tests { for _ in 0..50 { context - .add_entity((ExplicitProp(1), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(1), ExplicitProp2(false))) .unwrap(); } let results = context - .query_result_iterator((ConstantProp(42),)) + .query_result_iterator(with!(Person, ConstantProp(42))) .collect::>(); assert_eq!(results.len(), 50); } @@ -603,17 +607,22 @@ mod tests { for i in 0..50 { if i < 10 { context - .add_entity((ExplicitProp(1), ExplicitProp2(false), ConstantProp(99))) + .add_entity(with!( + Person, + ExplicitProp(1), + ExplicitProp2(false), + ConstantProp(99) + )) .unwrap(); } else { context - .add_entity((ExplicitProp(1), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(1), ExplicitProp2(false))) .unwrap(); } } let results = context - .query_result_iterator((ConstantProp(99),)) + .query_result_iterator(with!(Person, ConstantProp(99))) .collect::>(); assert_eq!(results.len(), 10); @@ -634,17 +643,22 @@ mod tests { for i in 0..50 { if i < 10 { context - .add_entity((ExplicitProp(1), ExplicitProp2(false), ConstantProp(99))) + .add_entity(with!( + Person, + ExplicitProp(1), + ExplicitProp2(false), + ConstantProp(99) + )) .unwrap(); } else { context - .add_entity((ExplicitProp(1), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(1), ExplicitProp2(false))) .unwrap(); } } let results = context - .query_result_iterator((ConstantProp(99),)) + .query_result_iterator(with!(Person, ConstantProp(99))) .collect::>(); assert_eq!(results.len(), 10); @@ -657,12 +671,12 @@ mod tests { for i in 0..100 { context - .add_entity((ExplicitProp(i as u8), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(i as u8), ExplicitProp2(false))) .unwrap(); } let results = context - .query_result_iterator((DerivedProp(true),)) + .query_result_iterator(with!(Person, DerivedProp(true))) .collect::>(); // DerivedProp is true when ExplicitProp is even @@ -683,12 +697,12 @@ mod tests { for i in 0..100 { context - .add_entity((ExplicitProp(i as u8), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(i as u8), ExplicitProp2(false))) .unwrap(); } let results = context - .query_result_iterator((DerivedProp(false),)) + .query_result_iterator(with!(Person, DerivedProp(false))) .collect::>(); // DerivedProp is false when ExplicitProp is odd @@ -711,11 +725,15 @@ mod tests { for i in 0..100 { context - .add_entity((ExplicitProp((i % 20) as u8), ExplicitProp2(i % 2 == 0))) + .add_entity(with!( + Person, + ExplicitProp((i % 20) as u8), + ExplicitProp2(i % 2 == 0) + )) .unwrap(); } - let results = context.query_result_iterator(()).collect::>(); + let results = context.query_result_iterator(Person).collect::>(); for person in results { let explicit_prop = context.get_property::(person); let explicit_prop2 = context.get_property::(person); @@ -724,7 +742,7 @@ mod tests { // ExplicitProp2 has only 2 values, so it will be the smaller source let results = context - .query_result_iterator((ExplicitProp(5), ExplicitProp2(false))) + .query_result_iterator(with!(Person, ExplicitProp(5), ExplicitProp2(false))) .collect::>(); // Looking for ExplicitProp=5 AND ExplicitProp2=true @@ -754,7 +772,8 @@ mod tests { for i in 0..100 { if i < 10 { context - .add_entity(( + .add_entity(with!( + Person, ExplicitProp(7), ExplicitProp2(false), ConstantProp2(200), // Non-default for smaller source @@ -762,13 +781,17 @@ mod tests { .unwrap(); } else { context - .add_entity((ExplicitProp((i % 20) as u8), ExplicitProp2(false))) + .add_entity(with!( + Person, + ExplicitProp((i % 20) as u8), + ExplicitProp2(false) + )) .unwrap(); } } let results = context - .query_result_iterator((ExplicitProp(7), ConstantProp2(200))) + .query_result_iterator(with!(Person, ExplicitProp(7), ConstantProp2(200))) .collect::>(); assert_eq!(results.len(), 10); @@ -783,17 +806,21 @@ mod tests { for i in 0..100 { if i < 5 { context - .add_entity((ExplicitProp(99), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(99), ExplicitProp2(false))) .unwrap(); // ConstantProp uses default } else { context - .add_entity((ExplicitProp((i % 20) as u8), ExplicitProp2(false))) + .add_entity(with!( + Person, + ExplicitProp((i % 20) as u8), + ExplicitProp2(false) + )) .unwrap(); } } let results = context - .query_result_iterator((ExplicitProp(99), ConstantProp(42))) + .query_result_iterator(with!(Person, ExplicitProp(99), ConstantProp(42))) .collect::>(); assert_eq!(results.len(), 5); @@ -815,17 +842,22 @@ mod tests { for i in 0..100 { if i < 10 { context - .add_entity((ConstantProp(99), ExplicitProp(0), ExplicitProp2(true))) + .add_entity(with!( + Person, + ConstantProp(99), + ExplicitProp(0), + ExplicitProp2(true) + )) .unwrap(); } else { context - .add_entity((ExplicitProp(0), ExplicitProp2(false))) + .add_entity(with!(Person, ExplicitProp(0), ExplicitProp2(false))) .unwrap(); } } let results = context - .query_result_iterator((ConstantProp(99), ExplicitProp2(true))) + .query_result_iterator(with!(Person, ConstantProp(99), ExplicitProp2(true))) .collect::>(); assert_eq!(results.len(), 10); @@ -839,12 +871,12 @@ mod tests { for i in 0..100 { context - .add_entity((ExplicitProp(i as u8), ExplicitProp2(i < 50))) + .add_entity(with!(Person, ExplicitProp(i as u8), ExplicitProp2(i < 50))) .unwrap(); } let results = context - .query_result_iterator((ExplicitProp2(true), DerivedProp(true))) + .query_result_iterator(with!(Person, ExplicitProp2(true), DerivedProp(true))) .collect::>(); // ExplicitProp2=true for i<50, DerivedProp=true when ExplicitProp is even @@ -861,12 +893,12 @@ mod tests { for i in 0..100 { context - .add_entity((ExplicitProp(i as u8), ExplicitProp2(i < 30))) + .add_entity(with!(Person, ExplicitProp(i as u8), ExplicitProp2(i < 30))) .unwrap(); } let results = context - .query_result_iterator((ExplicitProp2(true), DerivedProp(false))) + .query_result_iterator(with!(Person, ExplicitProp2(true), DerivedProp(false))) .collect::>(); // ExplicitProp2=true for i<30, DerivedProp=false when ExplicitProp is odd @@ -883,7 +915,8 @@ mod tests { for age in 0..100 { context - .add_entity(( + .add_entity(with!( + Person, Age(age), ExplicitProp(age.wrapping_mul(7) % 100), ExplicitProp2(false), @@ -892,7 +925,8 @@ mod tests { } for age in 0..100 { context - .add_entity(( + .add_entity(with!( + Person, Age(age), ExplicitProp(age.wrapping_mul(14) % 100), ExplicitProp2(false), @@ -902,8 +936,8 @@ mod tests { // Since both queries include `Age`, both will attempt to index unindexed entities. This tests that there is // no double borrow error. - let results = context.query_result_iterator((Age(25),)); - let more_results = context.query_result_iterator((Age(25), ExplicitProp(75))); + let results = context.query_result_iterator(with!(Person, Age(25))); + let more_results = context.query_result_iterator(with!(Person, Age(25), ExplicitProp(75))); let collected_results = results.collect::>(); let other_collected_results = more_results.collect::>(); diff --git a/src/entity/entity_set/source_set.rs b/src/entity/entity_set/source_set.rs index 81a85d2e..eed39b45 100644 --- a/src/entity/entity_set/source_set.rs +++ b/src/entity/entity_set/source_set.rs @@ -366,7 +366,7 @@ impl<'a, E: Entity> SourceSet<'a, E> { } /// A constructor for `SourceSet`s during construction of `EntitySet` in - /// `Query::new_query_result()`. Returns `None` if the set is empty. + /// `QueryInternal::new_query_result()`. Returns `None` if the set is empty. /// /// We first look for an index set. If not found, we check if the property is derived. /// For derived properties, we wrap a reference to the `Context`. For nonderived @@ -438,7 +438,7 @@ impl<'a, E: Entity> SourceSet<'a, E> { #[cfg(test)] mod tests { use super::*; - use crate::{define_derived_property, define_entity, define_property}; + use crate::{define_derived_property, define_entity, define_property, with}; define_entity!(Person); define_property!(struct Age(u8), Person, default_const = Age(0)); @@ -491,7 +491,9 @@ mod tests { fn source_set_new_uses_indexed_or_unindexed_backing() { let mut context = Context::new(); for age in [1u8, 2, 2, 3] { - context.add_entity((Age(age), Flag(true))).unwrap(); + context + .add_entity(with!(Person, Age(age), Flag(true))) + .unwrap(); } assert!(matches!( @@ -519,9 +521,15 @@ mod tests { #[test] fn source_set_new_derived_vs_nonderived_backing() { let mut context = Context::new(); - context.add_entity((Age(12), Flag(true))).unwrap(); - context.add_entity((Age(20), Flag(true))).unwrap(); - context.add_entity((Age(44), Flag(false))).unwrap(); + context + .add_entity(with!(Person, Age(12), Flag(true))) + .unwrap(); + context + .add_entity(with!(Person, Age(20), Flag(true))) + .unwrap(); + context + .add_entity(with!(Person, Age(44), Flag(false))) + .unwrap(); let nonderived = SourceSet::::new::(Age(20), &context).unwrap(); assert!(matches!(nonderived, SourceSet::PropertySet(_))); diff --git a/src/entity/entity_store.rs b/src/entity/entity_store.rs index 22405ede..4503c2e6 100644 --- a/src/entity/entity_store.rs +++ b/src/entity/entity_store.rs @@ -302,7 +302,7 @@ mod tests { add_to_entity_registry, get_registered_entity_count, initialize_entity_index, EntityStore, }; use crate::entity::Entity; - use crate::{impl_entity, Context, ContextEntitiesExt, HashMap}; + use crate::{impl_entity, with, Context, ContextEntitiesExt, HashMap}; // Test item types #[derive(Debug, Clone, PartialEq)] pub struct TestItem1 { @@ -705,10 +705,14 @@ mod tests { // Add different numbers of entities for each type // Note: add_entity returns Result, ...>, we unwrap for the test. for _ in 0..5 { - context.add_entity::(()).unwrap(); + context + .add_entity::(with!(TestItem1)) + .unwrap(); } for _ in 0..3 { - context.add_entity::(()).unwrap(); + context + .add_entity::(with!(TestItem2)) + .unwrap(); } // TestItem3 remains at 0 for now @@ -736,7 +740,9 @@ mod tests { // Iterators created now should not see entities added later let snapshot_iter = context.get_entity_iterator::(); - context.add_entity::(()).unwrap(); + context + .add_entity::(with!(TestItem1)) + .unwrap(); assert_eq!(context.get_entity_count::(), 6); assert_eq!(snapshot_iter.count(), 5); // Still sees original population diff --git a/src/entity/events.rs b/src/entity/events.rs index b226e5ab..59ef283d 100644 --- a/src/entity/events.rs +++ b/src/entity/events.rs @@ -178,7 +178,7 @@ mod tests { use std::rc::Rc; use super::*; - use crate::{define_derived_property, define_entity, define_property, Context}; + use crate::{define_derived_property, define_entity, define_property, with, Context}; define_entity!(Person); @@ -229,7 +229,7 @@ mod tests { }); let _ = context - .add_entity::((Age(18), RunningShoes(33), RiskCategory::Low)) + .add_entity::(with!(Person, Age(18), RunningShoes(33), RiskCategory::Low)) .unwrap(); context.execute(); assert!(*flag.borrow()); @@ -259,7 +259,7 @@ mod tests { ); let person_id = context - .add_entity((Age(9), RunningShoes(33), RiskCategory::Low)) + .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low)) .unwrap(); context.set_property(person_id, RiskCategory::High); @@ -280,7 +280,7 @@ mod tests { ); // Does not emit a change event. let person_id = context - .add_entity((Age(9), RunningShoes(33), RiskCategory::Low)) + .add_entity(with!(Person, Age(9), RunningShoes(33), RiskCategory::Low)) .unwrap(); // Emits a change event. context.set_property(person_id, RunningShoes(42)); @@ -292,7 +292,7 @@ mod tests { fn get_entity_property_change_event() { let mut context = Context::new(); let person = context - .add_entity((Age(17), RunningShoes(33), RiskCategory::Low)) + .add_entity(with!(Person, Age(17), RunningShoes(33), RiskCategory::Low)) .unwrap(); let flag = Rc::new(RefCell::new(false)); diff --git a/src/entity/index/full_index.rs b/src/entity/index/full_index.rs index e54db1c5..d5b64d89 100644 --- a/src/entity/index/full_index.rs +++ b/src/entity/index/full_index.rs @@ -53,6 +53,7 @@ impl> FullIndex { mod tests { // Tests in `src/entity/query.rs` also exercise indexing code. use crate::prelude::*; + use crate::with; define_entity!(Person); define_property!(struct Age(u8), Person, default_const = Age(0)); @@ -73,38 +74,42 @@ mod tests { context.index_property::(); context - .add_entity((Age(1u8), Weight(2u8), Height(3u8))) + .add_entity(with!(Person, Age(1u8), Weight(2u8), Height(3u8))) .unwrap(); let mut results_a = Default::default(); - context.with_query_results((Age(1u8), Weight(2u8), Height(3u8)), &mut |results| { - results_a = results.into_iter().collect::>() - }); + context.with_query_results( + with!(Person, Age(1u8), Weight(2u8), Height(3u8)), + &mut |results| results_a = results.into_iter().collect::>(), + ); assert_eq!(results_a.len(), 1); let mut results_b = Default::default(); - context.with_query_results((Weight(2u8), Height(3u8), Age(1u8)), &mut |results| { - results_b = results.into_iter().collect::>() - }); + context.with_query_results( + with!(Person, Weight(2u8), Height(3u8), Age(1u8)), + &mut |results| results_b = results.into_iter().collect::>(), + ); assert_eq!(results_b.len(), 1); assert_eq!(results_a, results_b); println!("Results: {:?}", results_a); context - .add_entity((Weight(1u8), Height(2u8), Age(3u8))) + .add_entity(with!(Person, Weight(1u8), Height(2u8), Age(3u8))) .unwrap(); let mut results_a = Default::default(); - context.with_query_results((Weight(1u8), Height(2u8), Age(3u8)), &mut |results| { - results_a = results.into_iter().collect::>() - }); + context.with_query_results( + with!(Person, Weight(1u8), Height(2u8), Age(3u8)), + &mut |results| results_a = results.into_iter().collect::>(), + ); assert_eq!(results_a.len(), 1); let mut results_b = Default::default(); - context.with_query_results((Age(3u8), Weight(1u8), Height(2u8)), &mut |results| { - results_b = results.into_iter().collect::>() - }); + context.with_query_results( + with!(Person, Age(3u8), Weight(1u8), Height(2u8)), + &mut |results| results_b = results.into_iter().collect::>(), + ); assert_eq!(results_b.len(), 1); assert_eq!(results_a, results_b); diff --git a/src/entity/index/value_count_index.rs b/src/entity/index/value_count_index.rs index 62dec1ba..661f323d 100644 --- a/src/entity/index/value_count_index.rs +++ b/src/entity/index/value_count_index.rs @@ -56,6 +56,7 @@ mod tests { use crate::entity::PropertyIndexType; use crate::hashing::one_shot_128; use crate::prelude::*; + use crate::with; define_entity!(Person); define_property!(struct Age(pub u8), Person, default_const = Age(0)); @@ -77,28 +78,28 @@ mod tests { property_store.set_property_indexed::(PropertyIndexType::ValueCountIndex); context - .add_entity((Age(1u8), Weight(2u8), Height(3u8))) + .add_entity(with!(Person, Age(1u8), Weight(2u8), Height(3u8))) .unwrap(); assert_eq!( - context.query_entity_count((Age(1u8), Weight(2u8), Height(3u8))), + context.query_entity_count(with!(Person, Age(1u8), Weight(2u8), Height(3u8))), 1 ); assert_eq!( - context.query_entity_count((Weight(2u8), Height(3u8), Age(1u8))), + context.query_entity_count(with!(Person, Weight(2u8), Height(3u8), Age(1u8))), 1 ); context - .add_entity((Weight(1u8), Height(2u8), Age(3u8))) + .add_entity(with!(Person, Weight(1u8), Height(2u8), Age(3u8))) .unwrap(); assert_eq!( - context.query_entity_count((Weight(1u8), Height(2u8), Age(3u8))), + context.query_entity_count(with!(Person, Weight(1u8), Height(2u8), Age(3u8))), 1 ); assert_eq!( - context.query_entity_count((Age(3u8), Weight(1u8), Height(2u8))), + context.query_entity_count(with!(Person, Age(3u8), Weight(1u8), Height(2u8))), 1 ); } diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 7cefd1c4..070a90a5 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -52,4 +52,4 @@ pub use entity::*; pub use entity_set::EntitySetIterator; pub use index::PropertyIndexType; pub use query::EntityPropertyTuple; -pub(crate) use query::Query; +pub(crate) use query::QueryInternal; diff --git a/src/entity/property_list.rs b/src/entity/property_list.rs index 2b4b48e8..f3bcc611 100644 --- a/src/entity/property_list.rs +++ b/src/entity/property_list.rs @@ -1,21 +1,21 @@ /*! -A [`PropertyList`] is just a tuple of distinct properties of the same [`Entity`] `E`. It -is used in two distinct places: as an initialization list for a new entity, and as a query. +This module supports two user-facing patterns: -Both use cases have the following two constraints: +1. initializing a new entity with [`ContextEntitiesExt::add_entity`], and +2. specifying strata for value-change counting APIs such as + [`ContextEntitiesExt::track_periodic_value_change_counts`]. -1. The properties are properties of the same entity. -2. The properties are distinct. +For `add_entity`, pass either: -We enforce the first constraint with the type system by only implementing `PropertyList` -for tuples of types implementing `Property` (of length up to some max). Using properties -for mismatched entities will result in a nice compile-time error at the point of use. +- the entity type directly, such as `Person`, to use default property values, or +- [`with!`](crate::with) to provide one or more initial property values, such as + `with!(Person, Age(25), InfectionStatus::Infected)`. -Unfortunately, the second constraint has to be enforced at runtime. We implement `PropertyList::validate()` to do this. +For value-change counting APIs, use tuple types in the generic parameter list, such as +`(InfectionStatus,)` or `(AgeGroup, InfectionStatus)`. -For both use cases, the order in which the properties appear is -unimportant in spite of the Rust language semantics of tuple types. +In both cases, all properties must belong to the same entity, and property values must be distinct. */ @@ -53,6 +53,9 @@ pub trait PropertyList: Copy + 'static { fn get_values_for_entity(context: &Context, entity_id: EntityId) -> Self; } +/// Values accepted by [`ContextEntitiesExt::add_entity`]. +pub trait PropertyInitializationList: PropertyList {} + // The empty tuple is an empty `PropertyList` for every `E: Entity`. impl PropertyList for () { fn validate() -> Result<(), IxaError> { @@ -94,6 +97,8 @@ impl PropertyList for E { } } +impl PropertyInitializationList for E {} + // ToDo(RobertJacobsonCDC): The following is a fundamental limitation in Rust. If downstream code *can* implement a // trait impl that will cause conflicting implementations with some blanket impl, it disallows it, regardless of // whether the conflict actually exists. @@ -112,7 +117,8 @@ impl PropertyList for E { // } // } -// A single `Property` tuple is a `PropertyList` of length 1 +// A single `Property` tuple is a `PropertyList` of length 1. This supports internal tuple +// machinery, but naked tuples are not accepted directly by `add_entity`. impl> PropertyList for (P,) { fn validate() -> Result<(), IxaError> { Ok(()) @@ -179,7 +185,9 @@ macro_rules! impl_property_list { }; } -// Generate impls for tuple lengths 2 through 20. (The 0 and 1 case are implemented above.) +// Generate impls for tuple lengths 2 through 20. These tuple impls remain available for internal +// initialization/query machinery and for type-level strata lists, but not as direct `add_entity` +// inputs. seq!(Z in 2..=20 { impl_property_list!(Z); }); diff --git a/src/entity/property_store.rs b/src/entity/property_store.rs index c03ab264..a299149a 100644 --- a/src/entity/property_store.rs +++ b/src/entity/property_store.rs @@ -418,7 +418,7 @@ mod tests { use super::*; use crate::entity::index::{IndexCountResult, IndexSetResult}; use crate::prelude::*; - use crate::{define_entity, define_property, Context}; + use crate::{define_entity, define_property, with, Context}; define_entity!(Person); @@ -504,8 +504,8 @@ mod tests { let existing_query_parts = [&existing_value as &dyn Any]; let missing_query_parts = [&missing_value as &dyn Any]; - let _ = context.add_entity((existing_value,)).unwrap(); - let _ = context.add_entity((existing_value,)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); let property_store = context.entity_store.get_property_store::(); @@ -543,8 +543,8 @@ mod tests { let existing_query_parts = [&existing_value as &dyn Any]; let missing_query_parts = [&missing_value as &dyn Any]; - let _ = context.add_entity((existing_value,)).unwrap(); - let _ = context.add_entity((existing_value,)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); let property_store = context.entity_store.get_property_store::(); @@ -577,8 +577,8 @@ mod tests { let existing_query_parts = [&existing_value as &dyn Any]; let missing_query_parts = [&missing_value as &dyn Any]; - let _ = context.add_entity((existing_value,)).unwrap(); - let _ = context.add_entity((existing_value,)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); + let _ = context.add_entity(with!(Person, existing_value)).unwrap(); let property_store = context.entity_store.get_property_store::(); diff --git a/src/entity/query/mod.rs b/src/entity/query/mod.rs index c83a7ef0..b5b2deef 100644 --- a/src/entity/query/mod.rs +++ b/src/entity/query/mod.rs @@ -6,7 +6,7 @@ use std::sync::{Mutex, OnceLock}; use crate::entity::entity_set::{EntitySet, EntitySetIterator}; use crate::entity::multi_property::type_ids_to_multi_property_index; -use crate::entity::property_list::PropertyList; +use crate::entity::property_list::{PropertyInitializationList, PropertyList}; use crate::entity::property_store::PropertyStore; use crate::entity::Entity; use crate::hashing::HashMap; @@ -20,15 +20,13 @@ use crate::{Context, IxaError}; /// /// # Example /// ```ignore -/// use ixa::{EntityPropertyTuple, define_entity, define_property}; +/// use ixa::{define_entity, define_property, with}; /// /// define_entity!(Person); /// define_property!(struct Age(u8), Person, default_const = Age(0)); /// -/// // Use the all macro +/// // Build a query for people with Age(42). /// let query = with!(Person, Age(42)); -/// // Under the hood this is: -/// // EntityPropertyTuple::::new((Age(42),)); /// ``` pub struct EntityPropertyTuple { inner: T, @@ -75,7 +73,7 @@ impl EntityPropertyTuple { } } -impl> Query for EntityPropertyTuple { +impl> QueryInternal for EntityPropertyTuple { type QueryParts<'a> = T::QueryParts<'a> where @@ -89,6 +87,10 @@ impl> Query for EntityPropertyTuple { self.inner.multi_property_id() } + fn is_empty_query(&self) -> bool { + self.inner.is_empty_query() + } + fn query_parts(&self) -> Self::QueryParts<'_> { self.inner.query_parts() } @@ -131,13 +133,10 @@ impl> PropertyList for EntityPropertyTuple: Copy + 'static { +impl> PropertyInitializationList for EntityPropertyTuple {} + +/// Internal query machinery. +pub trait QueryInternal: 'static { /// Allocation-free representation of the query parts exposed by this query. type QueryParts<'a>: AsRef<[&'a dyn std::any::Any]> where @@ -164,6 +163,11 @@ pub trait Query: Copy + 'static { *entry } + /// Indicates whether this query matches the entire population for `E`. + fn is_empty_query(&self) -> bool { + false + } + /// Exposes the query parts without allocating. fn query_parts(&self) -> Self::QueryParts<'_>; @@ -182,6 +186,18 @@ pub trait Query: Copy + 'static { fn filter_entities(&self, entities: &mut Vec>, context: &Context); } +/// Values accepted by user-facing query APIs such as +/// [`ContextEntitiesExt::query`](crate::entity::context_extension::ContextEntitiesExt::query) +/// and +/// [`ContextEntitiesExt::sample_entity`](crate::entity::context_extension::ContextEntitiesExt::sample_entity). +/// +/// Use [`with!`](crate::with) to query for specific property values, or pass the entity type +/// directly to work with the entire population. +pub trait Query: QueryInternal {} + +impl> Query for EntityPropertyTuple {} +impl Query for E {} + #[cfg(test)] mod tests { @@ -208,9 +224,11 @@ mod tests { #[test] fn with_query_results() { let mut context = Context::new(); - let _ = context.add_entity((RiskCategory::High,)).unwrap(); + let _ = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -219,7 +237,7 @@ mod tests { fn with_query_results_empty() { let context = Context::new(); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 0); }); } @@ -227,26 +245,36 @@ mod tests { #[test] fn query_entity_count() { let mut context = Context::new(); - let _ = context.add_entity((RiskCategory::High,)).unwrap(); + let _ = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); - assert_eq!(context.query_entity_count((RiskCategory::High,)), 1); + assert_eq!( + context.query_entity_count(with!(Person, RiskCategory::High)), + 1 + ); } #[test] fn query_entity_count_empty() { let context = Context::new(); - assert_eq!(context.query_entity_count((RiskCategory::High,)), 0); + assert_eq!( + context.query_entity_count(with!(Person, RiskCategory::High)), + 0 + ); } #[test] fn with_query_results_macro_index_first() { let mut context = Context::new(); - let _ = context.add_entity((RiskCategory::High,)).unwrap(); + let _ = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); context.index_property::<_, RiskCategory>(); assert!(context.is_property_indexed::()); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -254,9 +282,9 @@ mod tests { #[test] fn with_query_results_macro_index_second() { let mut context = Context::new(); - let _ = context.add_entity((RiskCategory::High,)); + let _ = context.add_entity(with!(Person, RiskCategory::High)); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); assert!(!context.is_property_indexed::()); @@ -264,7 +292,7 @@ mod tests { context.index_property::(); assert!(context.is_property_indexed::()); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -272,22 +300,24 @@ mod tests { #[test] fn with_query_results_macro_change() { let mut context = Context::new(); - let person1 = context.add_entity((RiskCategory::High,)).unwrap(); + let person1 = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); - context.with_query_results((RiskCategory::Low,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| { assert_eq!(people.into_iter().count(), 0); }); context.set_property(person1, RiskCategory::Low); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 0); }); - context.with_query_results((RiskCategory::Low,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -295,10 +325,12 @@ mod tests { #[test] fn with_query_results_index_after_add() { let mut context = Context::new(); - let _ = context.add_entity((RiskCategory::High,)).unwrap(); + let _ = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); context.index_property::(); assert!(context.is_property_indexed::()); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -306,15 +338,19 @@ mod tests { #[test] fn with_query_results_add_after_index() { let mut context = Context::new(); - let _ = context.add_entity((RiskCategory::High,)).unwrap(); + let _ = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); context.index_property::(); assert!(context.is_property_indexed::()); - context.with_query_results((RiskCategory::High,), &mut |people| { + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); - let _ = context.add_entity((RiskCategory::High,)).unwrap(); - context.with_query_results((RiskCategory::High,), &mut |people| { + let _ = context + .add_entity(with!(Person, RiskCategory::High)) + .unwrap(); + context.with_query_results(with!(Person, RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 2); }); } @@ -322,9 +358,11 @@ mod tests { #[test] fn with_query_results_cast_value() { let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); - context.with_query_results((Age(42),), &mut |people| { + context.with_query_results(with!(Person, Age(42)), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -332,11 +370,17 @@ mod tests { #[test] fn with_query_results_intersection() { let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(40), RiskCategory::Low)) + .unwrap(); - context.with_query_results((Age(42), RiskCategory::High), &mut |people| { + context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -344,11 +388,17 @@ mod tests { #[test] fn with_query_results_intersection_non_macro() { let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(40), RiskCategory::Low)) + .unwrap(); - context.with_query_results((Age(42), RiskCategory::High), &mut |people| { + context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -356,12 +406,18 @@ mod tests { #[test] fn with_query_results_intersection_one_indexed() { let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(40), RiskCategory::Low)) + .unwrap(); context.index_property::(); - context.with_query_results((Age(42), RiskCategory::High), &mut |people| { + context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| { assert_eq!(people.into_iter().count(), 1); }); } @@ -371,15 +427,19 @@ mod tests { let mut context = Context::new(); define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65)); - let person = context.add_entity((Age(64), RiskCategory::High)).unwrap(); - context.add_entity((Age(88), RiskCategory::High)).unwrap(); + let person = context + .add_entity(with!(Person, Age(64), RiskCategory::High)) + .unwrap(); + context + .add_entity(with!(Person, Age(88), RiskCategory::High)) + .unwrap(); let mut not_seniors = Vec::new(); - context.with_query_results((Senior(false),), &mut |people| { + context.with_query_results(with!(Person, Senior(false)), &mut |people| { not_seniors = people.to_owned_vec(); }); let mut seniors = Vec::new(); - context.with_query_results((Senior(true),), &mut |people| { + context.with_query_results(with!(Person, Senior(true)), &mut |people| { seniors = people.to_owned_vec(); }); assert_eq!(seniors.len(), 1, "One senior"); @@ -387,10 +447,10 @@ mod tests { context.set_property(person, Age(65)); - context.with_query_results((Senior(false),), &mut |people| { + context.with_query_results(with!(Person, Senior(false)), &mut |people| { not_seniors = people.to_owned_vec() }); - context.with_query_results((Senior(true),), &mut |people| { + context.with_query_results(with!(Person, Senior(true)), &mut |people| { seniors = people.to_owned_vec() }); @@ -404,15 +464,17 @@ mod tests { define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65)); context.index_property::(); - let person = context.add_entity((Age(64), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(88), RiskCategory::Low)); + let person = context + .add_entity(with!(Person, Age(64), RiskCategory::Low)) + .unwrap(); + let _ = context.add_entity(with!(Person, Age(88), RiskCategory::Low)); let mut not_seniors = Vec::new(); - context.with_query_results((Senior(false),), &mut |people| { + context.with_query_results(with!(Person, Senior(false)), &mut |people| { not_seniors = people.to_owned_vec() }); let mut seniors = Vec::new(); - context.with_query_results((Senior(true),), &mut |people| { + context.with_query_results(with!(Person, Senior(true)), &mut |people| { seniors = people.to_owned_vec() }); assert_eq!(seniors.len(), 1, "One senior"); @@ -420,10 +482,10 @@ mod tests { context.set_property(person, Age(65)); - context.with_query_results((Senior(false),), &mut |people| { + context.with_query_results(with!(Person, Senior(false)), &mut |people| { not_seniors = people.to_owned_vec() }); - context.with_query_results((Senior(true),), &mut |people| { + context.with_query_results(with!(Person, Senior(true)), &mut |people| { seniors = people.to_owned_vec() }); @@ -448,64 +510,115 @@ mod tests { ); // add some people - let _ = context.add_entity((Age(64), County(2), Height(120), RiskCategory::Low)); - let _ = context.add_entity((Age(88), County(2), Height(130), RiskCategory::Low)); + let _ = context.add_entity(with!( + Person, + Age(64), + County(2), + Height(120), + RiskCategory::Low + )); + let _ = context.add_entity(with!( + Person, + Age(88), + County(2), + Height(130), + RiskCategory::Low + )); let p2 = context - .add_entity((Age(8), County(1), Height(140), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(8), + County(1), + Height(140), + RiskCategory::Low + )) .unwrap(); let p3 = context - .add_entity((Age(28), County(1), Height(140), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(28), + County(1), + Height(140), + RiskCategory::Low + )) .unwrap(); let p4 = context - .add_entity((Age(28), County(2), Height(160), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(28), + County(2), + Height(160), + RiskCategory::Low + )) .unwrap(); let p5 = context - .add_entity((Age(28), County(2), Height(160), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(28), + County(2), + Height(160), + RiskCategory::Low + )) .unwrap(); // 'regular' derived property - context.with_query_results((Ach(28, 2, 160),), &mut |people| { + context.with_query_results(with!(Person, Ach(28, 2, 160)), &mut |people| { assert!(people.contains(p4)); assert!(people.contains(p5)); assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); }); // multi-property index - context.with_query_results((Age(28), County(2), Height(160)), &mut |people| { - assert!(people.contains(p4)); - assert!(people.contains(p5)); - assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); - }); + context.with_query_results( + with!(Person, Age(28), County(2), Height(160)), + &mut |people| { + assert!(people.contains(p4)); + assert!(people.contains(p5)); + assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); + }, + ); // multi-property index with different order - context.with_query_results((County(2), Height(160), Age(28)), &mut |people| { - assert!(people.contains(p4)); - assert!(people.contains(p5)); - assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); - }); + context.with_query_results( + with!(Person, County(2), Height(160), Age(28)), + &mut |people| { + assert!(people.contains(p4)); + assert!(people.contains(p5)); + assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); + }, + ); // multi-property index with different order - context.with_query_results((Height(160), County(2), Age(28)), &mut |people| { - assert!(people.contains(p4)); - assert!(people.contains(p5)); - assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); - }); + context.with_query_results( + with!(Person, Height(160), County(2), Age(28)), + &mut |people| { + assert!(people.contains(p4)); + assert!(people.contains(p5)); + assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); + }, + ); // multi-property index with different order and different value - context.with_query_results((Height(140), County(1), Age(28)), &mut |people| { - assert!(people.contains(p3)); - assert_eq!(people.into_iter().count(), 1, "Should have 1 matches"); - }); + context.with_query_results( + with!(Person, Height(140), County(1), Age(28)), + &mut |people| { + assert!(people.contains(p3)); + assert_eq!(people.into_iter().count(), 1, "Should have 1 matches"); + }, + ); context.set_property(p2, Age(28)); // multi-property index again after changing the value - context.with_query_results((Height(140), County(1), Age(28)), &mut |people| { - assert!(people.contains(p2)); - assert!(people.contains(p3)); - assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); - }); + context.with_query_results( + with!(Person, Height(140), County(1), Age(28)), + &mut |people| { + assert!(people.contains(p2)); + assert!(people.contains(p3)); + assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); + }, + ); - context.with_query_results((Height(140), County(1)), &mut |people| { + context.with_query_results(with!(Person, Height(140), County(1)), &mut |people| { assert!(people.contains(p2)); assert!(people.contains(p3)); assert_eq!(people.into_iter().count(), 2, "Should have 2 matches"); @@ -516,12 +629,18 @@ mod tests { fn test_match_entity() { let mut context = Context::new(); let person = context - .add_entity((Age(28), County(2), Height(160), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(28), + County(2), + Height(160), + RiskCategory::Low + )) .unwrap(); - assert!(context.match_entity(person, (Age(28), County(2), Height(160)))); - assert!(!context.match_entity(person, (Age(13), County(2), Height(160)))); - assert!(!context.match_entity(person, (Age(28), County(33), Height(160)))); - assert!(!context.match_entity(person, (Age(28), County(2), Height(9)))); + assert!(context.match_entity(person, with!(Person, Age(28), County(2), Height(160)))); + assert!(!context.match_entity(person, with!(Person, Age(13), County(2), Height(160)))); + assert!(!context.match_entity(person, with!(Person, Age(28), County(33), Height(160)))); + assert!(!context.match_entity(person, with!(Person, Age(28), County(2), Height(9)))); } #[test] @@ -531,14 +650,20 @@ mod tests { for idx in 0..10 { let person = context - .add_entity((Age(28), County(idx % 2), Height(160), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(28), + County(idx % 2), + Height(160), + RiskCategory::Low + )) .unwrap(); people.push(person); } context.filter_entities( &mut people, - (Age(28), County(0), Height(160), RiskCategory::Low), + with!(Person, Age(28), County(0), Height(160), RiskCategory::Low), ); let expected = (0..5) @@ -556,12 +681,18 @@ mod tests { for idx in 0..10 { let person = context - .add_entity((Age(28), County(idx % 2), Height(160), RiskCategory::Low)) + .add_entity(with!( + Person, + Age(28), + County(idx % 2), + Height(160), + RiskCategory::Low + )) .unwrap(); people.push(person); } - context.filter_entities(&mut people, (County(0), Age(28))); + context.filter_entities(&mut people, with!(Person, County(0), Age(28))); let expected = (0..5) .map(|idx| PersonId::new(idx * 2)) @@ -574,9 +705,15 @@ mod tests { use super::EntityPropertyTuple; let mut context = Context::new(); - let p1 = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap(); + let p1 = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(30), RiskCategory::High)) + .unwrap(); // Create query using EntityPropertyTuple let query: EntityPropertyTuple = @@ -599,8 +736,12 @@ mod tests { use super::EntityPropertyTuple; let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(30), RiskCategory::Low)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(30), RiskCategory::Low)) + .unwrap(); // Empty query matches all entities let query: EntityPropertyTuple = EntityPropertyTuple::new(()); @@ -613,9 +754,15 @@ mod tests { use super::EntityPropertyTuple; let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(30), RiskCategory::High)) + .unwrap(); // Single property query let query: EntityPropertyTuple = EntityPropertyTuple::new((Age(42),)); @@ -646,8 +793,12 @@ mod tests { use crate::with; let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(30), RiskCategory::Low)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(30), RiskCategory::Low)) + .unwrap(); // with!(Person) should match all Person entities let query = with!(Person); @@ -659,9 +810,15 @@ mod tests { use crate::with; let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(30), RiskCategory::High)) + .unwrap(); // with!(Person, Age(42)) should match entities with Age = 42 let query = with!(Person, Age(42)); @@ -673,9 +830,15 @@ mod tests { use crate::with; let mut context = Context::new(); - let p1 = context.add_entity((Age(42), RiskCategory::High)).unwrap(); - let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap(); - let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap(); + let p1 = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::Low)) + .unwrap(); + let _ = context + .add_entity(with!(Person, Age(30), RiskCategory::High)) + .unwrap(); // with!(Person, Age(42), RiskCategory::High) should match one entity let query = with!(Person, Age(42), RiskCategory::High); @@ -691,7 +854,9 @@ mod tests { use crate::with; let mut context = Context::new(); - let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); + let _ = context + .add_entity(with!(Person, Age(42), RiskCategory::High)) + .unwrap(); // Trailing comma should work let query = with!(Person, Age(42)); diff --git a/src/entity/query/query_impls.rs b/src/entity/query/query_impls.rs index 7ac3b318..89894113 100644 --- a/src/entity/query/query_impls.rs +++ b/src/entity/query/query_impls.rs @@ -5,10 +5,11 @@ use seq_macro::seq; use crate::entity::entity_set::{EntitySet, EntitySetIterator, SourceSet}; use crate::entity::index::IndexSetResult; use crate::entity::property::Property; -use crate::entity::{ContextEntitiesExt, Entity, EntityId, Query}; +use crate::entity::query::QueryInternal; +use crate::entity::{ContextEntitiesExt, Entity, EntityId}; use crate::Context; -impl Query for () { +impl QueryInternal for () { type QueryParts<'a> = [&'a dyn std::any::Any; 0] where @@ -22,6 +23,10 @@ impl Query for () { None } + fn is_empty_query(&self) -> bool { + true + } + fn query_parts(&self) -> Self::QueryParts<'_> { [] } @@ -48,7 +53,7 @@ impl Query for () { // An Entity ZST itself is an empty query matching all entities of that type. // This allows `context.sample_entity(Rng, Person)` instead of `context.sample_entity(Rng, ())`. -impl Query for E { +impl QueryInternal for E { type QueryParts<'a> = [&'a dyn std::any::Any; 0] where @@ -62,6 +67,10 @@ impl Query for E { None } + fn is_empty_query(&self) -> bool { + true + } + fn query_parts(&self) -> Self::QueryParts<'_> { [] } @@ -87,7 +96,7 @@ impl Query for E { } // Implement the query version with one parameter. -impl> Query for (P1,) { +impl> QueryInternal for (P1,) { type QueryParts<'a> = P1::QueryParts<'a> where @@ -193,7 +202,7 @@ macro_rules! impl_query { #( T~N : Property, )* - > Query for ( + > QueryInternal for ( #( T~N, )* @@ -229,9 +238,9 @@ macro_rules! impl_query { // This mirrors the indexed case in `SourceSet<'a, E>::new()`. The difference is, if the // multi-property is unindexed, we fall through to create `SourceSet`s for the components // rather than wrapping a `DerivedPropertySource`. - if let Some(multi_property_id) = >::multi_property_id(self) { + if let Some(multi_property_id) = >::multi_property_id(self) { let property_store = context.entity_store.get_property_store::(); - let query_parts = >::query_parts(self); + let query_parts = >::query_parts(self); let lookup_result = property_store.get_index_set_for_query_parts( multi_property_id, query_parts.as_ref(), @@ -266,9 +275,9 @@ macro_rules! impl_query { fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> { // Constructing the `EntitySetIterator` directly instead of constructing an `EntitySet` // first is a micro-optimization improving tight-loop benchmark performance. - if let Some(multi_property_id) = >::multi_property_id(self) { + if let Some(multi_property_id) = >::multi_property_id(self) { let property_store = context.entity_store.get_property_store::(); - let query_parts = >::query_parts(self); + let query_parts = >::query_parts(self); let lookup_result = property_store.get_index_set_for_query_parts( multi_property_id, query_parts.as_ref(), @@ -311,9 +320,9 @@ macro_rules! impl_query { fn filter_entities(&self, entities: &mut Vec>, context: &Context) { // The fast path: If this query is indexed, we only have to do one pass over the entities. - if let Some(multi_property_id) = >::multi_property_id(self) { + if let Some(multi_property_id) = >::multi_property_id(self) { let property_store = context.entity_store.get_property_store::(); - let query_parts = >::query_parts(self); + let query_parts = >::query_parts(self); let lookup_result = property_store.get_index_set_for_query_parts( multi_property_id, query_parts.as_ref(), diff --git a/src/macros/property_impl.rs b/src/macros/property_impl.rs index 5d14b7d4..9b58bc7f 100644 --- a/src/macros/property_impl.rs +++ b/src/macros/property_impl.rs @@ -1282,8 +1282,9 @@ mod tests { // We define unused properties to test macro implementation. #![allow(dead_code)] - use crate::entity::{PropertyIndexType, Query}; + use crate::entity::{PropertyIndexType, QueryInternal}; use crate::prelude::*; + use crate::with; define_entity!(Person); define_entity!(Group); @@ -1458,16 +1459,16 @@ mod tests { let mut context = Context::new(); context - .add_entity((Name("John"), Age(42), Weight(220.5))) + .add_entity(with!(Person, Name("John"), Age(42), Weight(220.5))) .unwrap(); context - .add_entity((Name("Jane"), Age(22), Weight(180.5))) + .add_entity(with!(Person, Name("Jane"), Age(22), Weight(180.5))) .unwrap(); context - .add_entity((Name("Bob"), Age(32), Weight(190.5))) + .add_entity(with!(Person, Name("Bob"), Age(32), Weight(190.5))) .unwrap(); context - .add_entity((Name("Alice"), Age(22), Weight(170.5))) + .add_entity(with!(Person, Name("Alice"), Age(22), Weight(170.5))) .unwrap(); context.index_property::<_, ProfileNAW>(); @@ -1504,28 +1505,37 @@ mod tests { { let example_query = (Name("Alice"), Age(22), Weight(170.5)); let query_multi_property_id = - <(Name, Age, Weight) as Query>::multi_property_id(&example_query); + <(Name, Age, Weight) as QueryInternal>::multi_property_id(&example_query); assert!(query_multi_property_id.is_some()); assert_eq!(ProfileNAW::index_id(), query_multi_property_id.unwrap()); - let query_parts = Query::query_parts(&example_query); + let query_parts = QueryInternal::query_parts(&example_query); assert_eq!( ProfileNAW::canonical_from_sorted_query_parts(query_parts.as_ref()), Some((Name("Alice"), Age(22), Weight(170.5)).make_canonical()) ); } - context.with_query_results(((Name("John"), Age(42), Weight(220.5)),), &mut |results| { - assert_eq!(results.into_iter().count(), 1); - }); + context.with_query_results( + with!(Person, (Name("John"), Age(42), Weight(220.5))), + &mut |results| { + assert_eq!(results.into_iter().count(), 1); + }, + ); } #[test] fn test_derived_property() { let mut context = Context::new(); - let senior = context.add_entity::((Age(92),)).unwrap(); - let child = context.add_entity::((Age(12),)).unwrap(); - let adult = context.add_entity::((Age(44),)).unwrap(); + let senior = context + .add_entity::(with!(Person, Age(92))) + .unwrap(); + let child = context + .add_entity::(with!(Person, Age(12))) + .unwrap(); + let adult = context + .add_entity::(with!(Person, Age(44))) + .unwrap(); let senior_group: AgeGroup = context.get_property(senior); let child_group: AgeGroup = context.get_property(child); @@ -1559,7 +1569,9 @@ mod tests { #[test] fn test_get_display() { let mut context = Context::new(); - let person = context.add_entity((POu32(Some(42)), Pu32(22))).unwrap(); + let person = context + .add_entity(with!(Person, POu32(Some(42)), Pu32(22))) + .unwrap(); assert_eq!( format!( "{:}", @@ -1574,7 +1586,9 @@ mod tests { ), "Pu32(22)" ); - let person2 = context.add_entity((POu32(None), Pu32(11))).unwrap(); + let person2 = context + .add_entity(with!(Person, POu32(None), Pu32(11))) + .unwrap(); assert_eq!( format!( "{:}", @@ -1589,7 +1603,8 @@ mod tests { let mut context = Context::new(); let some_person = context - .add_entity(( + .add_entity(with!( + Person, POu32(Some(42)), POFloat(Some(3.5)), POu32Custom(Some(7)), @@ -1597,7 +1612,13 @@ mod tests { )) .unwrap(); let none_person = context - .add_entity((POu32(None), POFloat(None), POu32Custom(None), Pu32(2))) + .add_entity(with!( + Person, + POu32(None), + POFloat(None), + POu32Custom(None), + Pu32(2) + )) .unwrap(); assert_eq!( @@ -1632,8 +1653,12 @@ mod tests { fn test_option_derived_property_display_patterns() { let mut context = Context::new(); - let some_person = context.add_entity::((Age(42),)).unwrap(); - let none_person = context.add_entity::((Age(0),)).unwrap(); + let some_person = context + .add_entity::(with!(Person, Age(42))) + .unwrap(); + let none_person = context + .add_entity::(with!(Person, Age(0))) + .unwrap(); assert_eq!( DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(some_person)), diff --git a/src/macros/with.rs b/src/macros/with.rs index 06104c6c..2cff9c44 100644 --- a/src/macros/with.rs +++ b/src/macros/with.rs @@ -1,13 +1,15 @@ -/// Creates a query matching all entities of a given type, optionally filtered by properties. +/// Creates an entity-scoped bundle of property values for queries and entity initialization. /// /// # Examples /// /// ```ignore -/// // Add an entity with default properties -/// let query = with!(Person); -/// context.add_entity(query)?; +/// // Add an entity with selected property values +/// let person = context.add_entity(with!(Person, Age(12), RiskCategory::High))?; /// -/// // An inline query matching a single property +/// // Query the whole population by passing the entity type directly +/// let count = context.query_entity_count(Person); +/// +/// // An inline query matching one property /// let person = context.sample_entity(MyRng, with!(Person, Age(12)))?; /// /// // A query matching multiple properties diff --git a/src/network/mod.rs b/src/network/mod.rs index af629745..86b8b19d 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -529,7 +529,7 @@ mod test_api { use crate::network::ContextNetworkExt; use crate::prelude::*; use crate::random::ContextRandomExt; - use crate::{define_edge_type, define_entity, define_property, define_rng}; + use crate::{define_edge_type, define_entity, define_property, define_rng, with}; define_entity!(Person); @@ -538,8 +538,8 @@ mod test_api { fn setup() -> (Context, PersonId, PersonId) { let mut context = Context::new(); - let person1 = context.add_entity((Age(1),)).unwrap(); - let person2 = context.add_entity((Age(2),)).unwrap(); + let person1 = context.add_entity(with!(Person, Age(1))).unwrap(); + let person2 = context.add_entity(with!(Person, Age(2))).unwrap(); (context, person1, person2) } @@ -638,7 +638,7 @@ mod test_api { #[test] fn get_matching_edges_weight() { let (mut context, person1, person2) = setup(); - let person3 = context.add_entity((Age(3),)).unwrap(); + let person3 = context.add_entity(with!(Person, Age(3))).unwrap(); context .add_edge::(person1, person2, 0.01, EdgeType1(1)) @@ -655,7 +655,7 @@ mod test_api { #[test] fn get_matching_edges_inner() { let (mut context, person1, person2) = setup(); - let person3 = context.add_entity((Age(3),)).unwrap(); + let person3 = context.add_entity(with!(Person, Age(3))).unwrap(); context .add_edge::(person1, person2, 0.01, EdgeType1(1)) @@ -672,7 +672,7 @@ mod test_api { #[test] fn get_matching_edges_person_property() { let (mut context, person1, person2) = setup(); - let person3 = context.add_entity((Age(3),)).unwrap(); + let person3 = context.add_entity(with!(Person, Age(3))).unwrap(); context .add_edge::(person1, person2, 0.01, EdgeType1(1)) @@ -681,7 +681,7 @@ mod test_api { .add_edge::(person1, person3, 0.03, EdgeType1(3)) .unwrap(); let edges = context.get_matching_edges::(person1, |context, edge| { - context.match_entity(edge.neighbor, (Age(3),)) + context.match_entity(edge.neighbor, with!(Person, Age(3))) }); assert_eq!(edges.len(), 1); assert_eq!(edges[0].neighbor, person3); @@ -692,7 +692,7 @@ mod test_api { define_rng!(NetworkTestRng); let (mut context, person1, person2) = setup(); - let person3 = context.add_entity((Age(3),)).unwrap(); + let person3 = context.add_entity(with!(Person, Age(3))).unwrap(); context.init_random(42); context