diff --git a/docs/book/models/disease_model/Cargo.toml b/docs/book/models/disease_model/Cargo.toml index 44967585..52056d96 100644 --- a/docs/book/models/disease_model/Cargo.toml +++ b/docs/book/models/disease_model/Cargo.toml @@ -8,6 +8,7 @@ publish = false # Do not publish to the Crates.io registry [dependencies] csv = "1.3.1" -ixa = { git = "https://github.com/CDCgov/ixa", branch = "main" } +# TODO: revert to git dependency before merging +ixa = { path = "../../../.." } rand_distr = "0.5.1" serde = { version = "1.0.217", features = ["derive"] } diff --git a/docs/book/models/disease_model/src/people.rs b/docs/book/models/disease_model/src/people.rs index ef8f0df1..b159906f 100644 --- a/docs/book/models/disease_model/src/people.rs +++ b/docs/book/models/disease_model/src/people.rs @@ -24,7 +24,7 @@ define_property!( pub fn init(context: &mut Context) { trace!("Initializing people"); for _ in 0..POPULATION { - let _: PersonId = context.add_entity(()).expect("failed to add person"); + let _ = context.add_entity(Person).expect("failed to add person"); } } // ANCHOR_END: init diff --git a/docs/book/models/disease_model/src/transmission_manager.rs b/docs/book/models/disease_model/src/transmission_manager.rs index 78b01dcc..b6646e60 100644 --- a/docs/book/models/disease_model/src/transmission_manager.rs +++ b/docs/book/models/disease_model/src/transmission_manager.rs @@ -3,7 +3,7 @@ use ixa::prelude::*; use ixa::trace; use rand_distr::Exp; -use crate::people::{InfectionStatus, PersonId}; +use crate::people::{InfectionStatus, Person}; use crate::{FORCE_OF_INFECTION, POPULATION}; define_rng!(TransmissionRng); @@ -12,7 +12,7 @@ define_rng!(TransmissionRng); // ANCHOR: attempt_infection fn attempt_infection(context: &mut Context) { trace!("Attempting infection"); - let person_to_infect: PersonId = context.sample_entity(TransmissionRng, ()).unwrap(); + let person_to_infect = context.sample_entity(TransmissionRng, Person).unwrap(); let person_status: InfectionStatus = context.get_property(person_to_infect); if person_status == InfectionStatus::S { diff --git a/docs/book/src/appendix_rust/turbo-fish.md b/docs/book/src/appendix_rust/turbo-fish.md index 2a12e661..bb42c784 100644 --- a/docs/book/src/appendix_rust/turbo-fish.md +++ b/docs/book/src/appendix_rust/turbo-fish.md @@ -6,132 +6,7 @@ once. A nice feature of Rust is that the compiler can often infer the generic types at the point the function is used instead of relying on the programmer to specify the types explicitly. -## An example with `Context::add_entity()` - -Suppose we want to initialize a population: - -```rust -define_entity!(Person); -define_property!( - // The type of the property - enum InfectionStatus {S,I,R}, - // The entity the property is associated with - Person, - // The property's default value for newly created `Person` entities - default_const = InfectionStatus::S -); - -/// Populates the "world" with people. -pub fn init(context: &mut Context) { - for _ in 0..1000 { - context.add_entity((InfectionStatus::S, )).expect("failed to add person"); - } -} -``` - -During the initialization of our population, we explicitly told ixa to create a -new _susceptible_ person, that is, with the `InfectionStatus::S` property value. -However, when we defined the `InfectionStatus` property with the -`define_property!` macro, we specified a default initial value with -`default_const = InfectionStatus::S`. Since `InfectionStatus` has a default -value, we don't need to supply a value for it when calling -`context.add_entity(...)`. But remember, the compiler infers _which_ entity to -create based on the property values we supply, and if we don't supply _any_ -property values, we need another way to specify the entity to create. - -The `Context::add_entity` function is actually a whole family of functions -`Context::add_entity\`, one function for each -concrete `Entity` type `E` and `PropertyList` type `PL`. When we call -`context.add_entity(...)` in our code with a tuple of properties (the -initialization list), the Rust compiler looks at the initialization list and -uses it to infer the concrete types `E` and `PL`. But when the initialization -list is `PL=()` (the empty list), the compiler doesn't know what the `Entity` -type `E` should be. The Rust language allows us to tell it explicitly using the -"turbo fish" notation: - -```rust -context.add_entity::(()).expect("failed to add person"); -``` - -Actually, the compiler _always_ already knows the `PropertyList` type `PL`, so -we can use the "wildcard" `_` (underscore) to tell the compiler to infer that -type itself: - -```rust -context.add_entity::(()).expect("failed to add person"); -``` - -There is another way to give the compiler enough information to infer the -`Entity` type, namely by specifying the return type we are expecting. In our -case, we just throw the returned `PersonId` away, but suppose we want to refer -to the newly created person. We could write: - -```rust -let person_id: PersonId = context.add_entity(()).expect("failed to add person"); -``` - -The `Entity` type `E` must be `Person`, because that is the only way -`Context::add_entity\` can return a `PersonId` (a -type alias for `EntityId\`). You can use this trick even if you never -use the returned `PersonId`, but in such a case it's best practice to signal -this intent by using the special "don't care" variable `_` (underscore): - -```rust -let _: PersonId = context.add_entity(()).expect("failed to add person"); -``` - -You do not have to learn the rules for when specifying the types using turbo -fish notation is required. The compiler will let you know. For `add_entity`, -always specifying the returned type means you'll never have to worry about turbo -fish notation. - -## Preferred Idiom for `Context::sample_entity()` - -The `Context::sample_entity()` method especially deserves discussion, because we -often want to immediately use the returned value. If we try to use the standard -Rust idiom to express this, we have to specify the types using turbo fish, which -is awkward and ugly: - -```rust -// Sample from the entire population by supplying the "empty" query. The last two `_`s are for the query type and -// RNG type, both of which the compiler can infer. -if let Some(person_id) = context.sample_entity::(TransmissionRng, ()) { - // Do something with `person_id`... -} -``` - -Since we are sampling from the entire population, if `sample_entity` returns -`None`, then the population is empty, and we clearly have a bug in our code, in -which case the best thing to do is to crash the program and fix the bug. Thus, -instead of the `if let Some(...) =` construct, it's actually better to just call -`unwrap` on the returned value in this case. Here is a much more readable and -simple way to write the code: - -```rust -// Sample from the entire population by supplying the "empty" query. The compiler infers which entity to sample -// from the type of the variable we assign to. -let person_id: PersonId = context.sample_entity(()).unwrap(); -// Do something with `person_id`... -``` - -If you really want to check for the `None` case in your code, assign the return -value to a variable of type `Option\` instead of immediately -unwrapping the `PersonId` value. Then you can use `if let Some(...) =` or a -`match` statement at your preference: - -```rust -let maybe_person_id: Option = context.sample_entity(()); -match maybe_person_id { - Some(person_id) => { - // Do something with `person_id` - } - None => { - // Handle the empty population case - } -} -``` - -## Other Examples +## Examples The compiler's ability to infer the types of generic functions means that for most of the common functions the types do not need to be specified with turbo @@ -146,14 +21,13 @@ let status: InfectionStatus = context.get_property(person_id); context.set_property(other_person_id, status); ``` -As with `Context::add_entity`, the generic types for querying and sampling -methods can usually be inferred by the compiler except when the "empty" query is -provided: +The generic types for querying and sampling +methods can usually be inferred by the compiler: ```rust // A silly example, but no turbo fish is required. context.with_query_results( - (Age(30), Alive(true)), + with!(Person, Age(30), Alive(true)), |people_set| println("{:?}", people_set) ); ``` diff --git a/docs/book/src/first_model/people.md b/docs/book/src/first_model/people.md index 26949f6a..e38baa6d 100644 --- a/docs/book/src/first_model/people.md +++ b/docs/book/src/first_model/people.md @@ -57,29 +57,25 @@ pub fn init(context: &mut Context) { trace!("Initializing people"); for _ in 0..100 { - let _: PersonId = context.add_entity(()).expect("failed to add person"); + let _ = context.add_entity(Person).expect("failed to add person"); } } ``` -The `context.add_entity()` method call might look a little odd, because we are -not giving `context` any data to insert, but that is because our one and only -`Property` was defined to have a default value of `InfectionStatus::S` -(susceptible)—so `context.add_entity()` doesn't need any information to create a -new person. Another odd thing is the `.expect("failed to add person")` method -call. In more complicated scenarios adding a person can fail. We could intercept -that failure if we wanted, but in this simple case we will just let the program -crash with a message about the reason: "failed to add person". - -Finally, the `Context::add_entity` method returns an entity ID wrapped in a -`Result`, which the `expect` method unwraps. We can use this ID if we need to -refer to this newly created person. Since we don't need it, we assign the value -to the special "don't care" variable `_` (underscore), which just throws the -value away. Why assign it to anything, though? So that the compiler can infer -that it is a `Person` we are creating, as opposed to some other entity we may -have defined. If we just omitted the `let _: PersonId =` part completely, we -would need to explicitly specify the entity type using -[turbo fish notation](../appendix_rust/turbo-fish.md). +We use `Person` here to represent a new entity with all default property values– +our one and only `Property` was defined to have a default value of +`InfectionStatus::S` (susceptible), so no additional information is needed. + +The `.expect("failed to add person")` method call handles the case where adding +a person could fail. We could intercept that failure if we wanted, but in this +simple case we will just let the program crash with a message about the reason: +"failed to add person". + +The `Context::add_entity` method returns an entity ID wrapped in a `Result`, +which the `expect` method unwraps. We can use this ID if we need to refer to +this newly created person. Since we don't need it, we assign the value to the +special "don't care" variable `_` (underscore), which just throws the value +away. ## Constants diff --git a/docs/book/src/first_model/transmission.md b/docs/book/src/first_model/transmission.md index 0f3e25d0..c2fcdca5 100644 --- a/docs/book/src/first_model/transmission.md +++ b/docs/book/src/first_model/transmission.md @@ -88,12 +88,14 @@ The function `attempt_infection()` needs to do the following: Read through this implementation and make sure you understand how it accomplishes the three tasks above. A few observations: -- The method call `context.sample_entity(TransmissionRng, ())` takes the name of - a random number source and a query and returns an `Option\`, which - can have the value of `Some(PersonId)` or `None`. In this case, we give it the - "empty query" `()`, which means we want to sample from the entire population. - 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`. +- The method call `context.sample_entity(TransmissionRng, Person)` takes the + name of a random number source and a query and returns an `Option\`, + 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 + 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 does is schedule the next attempt at infection. - The time at which the next attempt is scheduled is sampled randomly from the diff --git a/docs/book/src/migration_guide.md b/docs/book/src/migration_guide.md index d581af8b..7d5b7aa9 100644 --- a/docs/book/src/migration_guide.md +++ b/docs/book/src/migration_guide.md @@ -126,50 +126,33 @@ These macros automatically create a type alias of the form ### Adding a new entity (e.g. a new person) -Adding a new entity with multiple property values: +Adding a new entity with only default property values by passing the entity type +directly: ```rust -// Assuming the `Person` entity is defined somewhere. -// Add a new entity (a person in this case) to an existing `Context` instance we have access to. -let person_id = context.add_entity((Age(25), InfectionStatus::Infected)).unwrap(); +let person_id = context.add_entity(Person).unwrap(); ``` -Observe: - -- The compiler is smart enough to know that we are adding a new `Person` entity - because we supplied a list of property values that are properties for a - `Person`. -- The `add_entity` function takes a "property list", which is just a tuple of - 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. -- A single-property tuple uses the syntax `(Age(25), )`. Notice the awkward - trailing comma, which lets the compiler know the parentheses are defining a - tuple rather than functioning as grouping an expression. +(This example assumes there are no required properties, that is, that every +property has a default value.) -Adding a new entity with just one property value: +Adding a new entity with property values using the `with!` macro: ```rust -// Assuming the `Person` entity is defined somewhere. -// Add a new entity (a person in this case) to an existing `Context` instance we have access to. -let person_id = context.add_entity((Age(25), )).unwrap(); +let person_id = context.add_entity(with!(Person, Age(25), InfectionStatus::Infected)).unwrap(); ``` -Adding a new entity with only default property values: +The `with!` macro takes the entity type as its first argument, followed by any +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. -```rust -// If you specify the `EntityId\` return type, the compiler uses it to infer which entity to add. -// This is a good practice and avoids the special "turbo fish" syntax. -let person_id: PersonId = context.add_entity(()).unwrap(); +Adding a new entity with just one property value: -// If we don't specify the `EntityId\` type, we have to explicitly tell the compiler *which* entity -// type we are adding, as there is nothing from which to infer the entity type. -let person_id = context.add_entity::(()).unwrap(); +```rust +let person_id = context.add_entity(with!(Person, Age(25))).unwrap(); ``` -(These two examples assume there are no required properties, that is, that every -property has a default value.) - ### Getting a property value for an entity ```rust diff --git a/docs/book/src/topics/indexing.md b/docs/book/src/topics/indexing.md index 9fb82578..78acfffd 100644 --- a/docs/book/src/topics/indexing.md +++ b/docs/book/src/topics/indexing.md @@ -183,7 +183,8 @@ Suppose we have the properties `AgeGroup` and `InfectionStatus`, and we want to speed up queries of these two properties: ```rust -let age_and_status = context.query_result_iterator((AgeGroup(30), InfectionStatus::Susceptible)); // Bottleneck +let query = with!(Person, AgeGroup(30), InfectionStatus::Susceptible); +let age_and_status = context.query_result_iterator(query); // Bottleneck ``` We could index `AgeGroup` and `InfectionStatus` individually, but in this case diff --git a/examples/basic-infection/src/people.rs b/examples/basic-infection/src/people.rs index 61488301..5374bd14 100644 --- a/examples/basic-infection/src/people.rs +++ b/examples/basic-infection/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 _ = context.add_entity(Person).unwrap(); } } diff --git a/examples/basic-infection/src/transmission_manager.rs b/examples/basic-infection/src/transmission_manager.rs index e98338ea..bbbb7409 100644 --- a/examples/basic-infection/src/transmission_manager.rs +++ b/examples/basic-infection/src/transmission_manager.rs @@ -2,7 +2,7 @@ use ixa::prelude::*; use ixa::trace; use rand_distr::Exp; -use crate::people::{InfectionStatus, Person, PersonId}; +use crate::people::{InfectionStatus, Person}; use crate::{FOI, MAX_TIME}; define_rng!(TransmissionRng); @@ -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(); + let person_to_infect = context.sample_entity(TransmissionRng, Person).unwrap(); 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 = 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/examples/births-deaths/src/infection_manager.rs b/examples/births-deaths/src/infection_manager.rs index 8019823b..82fc4c38 100644 --- a/examples/births-deaths/src/infection_manager.rs +++ b/examples/births-deaths/src/infection_manager.rs @@ -141,7 +141,7 @@ mod test { let population_size: usize = 10; for index in 0..population_size { - let person = context.add_entity((Age(0),)).unwrap(); + let person = context.add_entity(with!(Person, Age(0))).unwrap(); context.add_plan(1.0, move |context| { context.set_property(person, InfectionStatus::I); @@ -168,7 +168,7 @@ mod test { context.init_random(42); init(&mut context); - let person = context.add_entity((Age(0),)).unwrap(); + let person = context.add_entity(with!(Person, Age(0))).unwrap(); context.add_plan(1.1, move |context| { cancel_recovery_plans(context, person); }); diff --git a/examples/births-deaths/src/population_manager.rs b/examples/births-deaths/src/population_manager.rs index 34ffe163..1b2351c4 100644 --- a/examples/births-deaths/src/population_manager.rs +++ b/examples/births-deaths/src/population_manager.rs @@ -72,7 +72,7 @@ fn schedule_birth(context: &mut Context) { .get_global_property_value(Parameters) .unwrap() .clone(); - let person = context.add_entity((Age(0),)).unwrap(); + let person = context.add_entity(with!(Person, Age(0))).unwrap(); context.add_plan(context.get_current_time() + 365.0, move |context| { schedule_aging(context, person); }); @@ -90,7 +90,7 @@ fn schedule_death(context: &mut Context) { .unwrap() .clone(); - if let Some(person) = context.sample_entity(PeopleRng, (Alive(true),)) { + if let Some(person) = context.sample_entity(PeopleRng, with!(Person, Alive(true))) { context.set_property(person, Alive(false)); let next_death_event = context.get_current_time() @@ -110,7 +110,7 @@ pub fn init(context: &mut Context) { for _ in 0..parameters.population { let age: u8 = context.sample_range(PeopleRng, 0..MAX_AGE); - let person = context.add_entity((Age(age),)).unwrap(); + let person = context.add_entity(with!(Person, Age(age))).unwrap(); let birthday = context.sample_distr(PeopleRng, Uniform::new(0.0, 365.0).unwrap()); context.add_plan(365.0 + birthday, move |context| { schedule_aging(context, person); @@ -146,22 +146,22 @@ mod test { fn test_birth_death() { let mut context = Context::new(); - let person1 = context.add_entity((Age(10),)).unwrap(); + let person1 = context.add_entity(with!(Person, Age(10))).unwrap(); let person2 = Rc::>>::new(RefCell::new(None)); let person2_clone = Rc::clone(&person2); context.add_plan(380.0, move |context| { - *person2_clone.borrow_mut() = Some(context.add_entity((Age(0),)).unwrap()); + *person2_clone.borrow_mut() = Some(context.add_entity(with!(Person, Age(0))).unwrap()); }); context.add_plan(400.0, move |context| { context.set_property(person1, Alive(false)); }); context.add_plan(390.0, |context| { - let pop = context.query_entity_count((Alive(true),)); + let pop = context.query_entity_count(with!(Person, Alive(true))); assert_eq!(pop, 2); }); context.add_plan(401.0, |context| { - let pop = context.query_entity_count((Alive(true),)); + let pop = context.query_entity_count(with!(Person, Alive(true))); assert_eq!(pop, 1); }); context.execute(); @@ -221,7 +221,7 @@ mod test { .set_global_property_value(Parameters, p_values.clone()) .unwrap(); context.init_random(p_values.seed); - let _person = context.add_entity((Age(0),)).unwrap(); + let _person = context.add_entity(with!(Person, Age(0))).unwrap(); schedule_death(&mut context); } @@ -238,7 +238,7 @@ mod test { ]; let mut people = Vec::::new(); for age in &age_vec { - people.push(context.add_entity((Age(*age),)).unwrap()); + people.push(context.add_entity(with!(Person, Age(*age))).unwrap()); } for i in 0..people.len() { diff --git a/examples/births-deaths/src/transmission_manager.rs b/examples/births-deaths/src/transmission_manager.rs index d1359fdb..9d6794fd 100644 --- a/examples/births-deaths/src/transmission_manager.rs +++ b/examples/births-deaths/src/transmission_manager.rs @@ -2,14 +2,15 @@ use ixa::prelude::*; use rand_distr::Exp; use crate::parameters_loader::Foi; -use crate::population_manager::{AgeGroupRisk, Alive, InfectionStatus}; +use crate::population_manager::{AgeGroupRisk, Alive, InfectionStatus, Person}; use crate::Parameters; define_rng!(TransmissionRng1); //Attempt infection for specific age group risk (meaning different forces of infection) fn attempt_infection(context: &mut Context, age_group: AgeGroupRisk) { - let population_size: usize = context.query_entity_count((Alive(true), age_group)); + let query = with!(Person, Alive(true), age_group); + let population_size: usize = context.query_entity_count(query); let parameters = context .get_global_property_value(Parameters) .unwrap() @@ -20,9 +21,7 @@ fn attempt_infection(context: &mut Context, age_group: AgeGroupRisk) { .get(&age_group) .unwrap(); if population_size > 0 { - let person_to_infect = context - .sample_entity(TransmissionRng1, (Alive(true), age_group)) - .unwrap(); + let person_to_infect = context.sample_entity(TransmissionRng1, query).unwrap(); let person_status: InfectionStatus = context.get_property(person_to_infect); diff --git a/examples/network-hhmodel/incidence_report.rs b/examples/network-hhmodel/incidence_report.rs index a20d4a58..874d9232 100644 --- a/examples/network-hhmodel/incidence_report.rs +++ b/examples/network-hhmodel/incidence_report.rs @@ -155,7 +155,7 @@ mod test { }, ); - let to_infect: Vec = vec![context.sample_entity(MainRng, ()).unwrap()]; + let to_infect: Vec = vec![context.sample_entity(MainRng, Person).unwrap()]; #[allow(clippy::vec_init_then_push)] seir::init(&mut context, &to_infect); diff --git a/examples/network-hhmodel/main.rs b/examples/network-hhmodel/main.rs index a4e566d4..fee887dc 100644 --- a/examples/network-hhmodel/main.rs +++ b/examples/network-hhmodel/main.rs @@ -40,7 +40,7 @@ fn initialize(context: &mut Context) { incidence_report::init(context).unwrap(); // Initialize infected person with InfectedBy value equal to their own PersonId - let to_infect: Vec = vec![context.sample_entity(MainRng, ()).unwrap()]; + let to_infect: Vec = vec![context.sample_entity(MainRng, Person).unwrap()]; #[allow(clippy::vec_init_then_push)] seir::init(context, &to_infect); diff --git a/src/entity/context_extension.rs b/src/entity/context_extension.rs index c4b32120..161985fd 100644 --- a/src/entity/context_extension.rs +++ b/src/entity/context_extension.rs @@ -485,6 +485,14 @@ mod tests { assert_eq!(context.get_entity_count::(), 3); } + #[test] + fn add_entity_with_zst() { + let mut context = Context::new(); + let animal = context.add_entity(Animal).unwrap(); + assert_eq!(context.get_entity_count::(), 1); + assert_eq!(context.get_property::(animal), Legs(4)); + } + // Helper for index tests #[derive(Copy, Clone)] enum IndexMode { diff --git a/src/entity/property_list.rs b/src/entity/property_list.rs index 045c72aa..a86f34f8 100644 --- a/src/entity/property_list.rs +++ b/src/entity/property_list.rs @@ -58,6 +58,20 @@ impl PropertyList for () { } } +// An Entity ZST itself is an empty `PropertyList` for that entity. +// This allows `context.add_entity(Person)` instead of `context.add_entity(())`. +impl PropertyList for E { + fn validate() -> Result<(), IxaError> { + Ok(()) + } + fn contains_properties(property_type_ids: &[TypeId]) -> bool { + property_type_ids.is_empty() + } + fn set_values_for_entity(&self, _entity_id: EntityId, _property_store: &PropertyStore) { + // No values to assign. + } +} + // 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. diff --git a/src/entity/query/mod.rs b/src/entity/query/mod.rs index 260aac3b..c61e2e3d 100644 --- a/src/entity/query/mod.rs +++ b/src/entity/query/mod.rs @@ -15,7 +15,7 @@ use crate::{Context, IxaError}; /// A newtype wrapper that associates a tuple of property values with an entity type. /// -/// This is not meant to be used directly, but rather as a backing for the all! macro/ +/// This is not meant to be used directly, but rather as a backing for the with! macro/ /// a replacement for the query tuple. /// /// # Example @@ -26,7 +26,7 @@ use crate::{Context, IxaError}; /// define_property!(struct Age(u8), Person, default_const = Age(0)); /// /// // Use the all macro -/// let query = all!(Person, Age(42)); +/// let query = with!(Person, Age(42)); /// // Under the hood this is: /// // EntityPropertyTuple::::new((Age(42),)); /// ``` @@ -627,42 +627,42 @@ mod tests { #[test] fn all_macro_no_properties() { - use crate::all; + 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(); - // all!(Person) should match all Person entities - let query = all!(Person); + // with!(Person) should match all Person entities + let query = with!(Person); assert_eq!(context.query_entity_count(query), 2); } #[test] fn all_macro_single_property() { - use crate::all; + 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(); - // all!(Person, Age(42)) should match entities with Age = 42 - let query = all!(Person, Age(42)); + // with!(Person, Age(42)) should match entities with Age = 42 + let query = with!(Person, Age(42)); assert_eq!(context.query_entity_count(query), 2); } #[test] fn all_macro_multiple_properties() { - use crate::all; + 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(); - // all!(Person, Age(42), RiskCategory::High) should match one entity - let query = all!(Person, Age(42), RiskCategory::High); + // with!(Person, Age(42), RiskCategory::High) should match one entity + let query = with!(Person, Age(42), RiskCategory::High); assert_eq!(context.query_entity_count(query), 1); context.with_query_results(query, &mut |people| { @@ -672,16 +672,16 @@ mod tests { #[test] fn all_macro_with_trailing_comma() { - use crate::all; + use crate::with; let mut context = Context::new(); let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap(); // Trailing comma should work - let query = all!(Person, Age(42),); + let query = with!(Person, Age(42)); assert_eq!(context.query_entity_count(query), 1); - let query = all!(Person, Age(42), RiskCategory::High,); + let query = with!(Person, Age(42), RiskCategory::High); assert_eq!(context.query_entity_count(query), 1); } @@ -713,12 +713,12 @@ mod tests { #[test] fn all_macro_as_property_list_for_add_entity() { - use crate::all; + use crate::with; let mut context = Context::new(); - // Use all! macro result to add an entity - let props = all!(Person, Age(42), RiskCategory::High); + // Use with! macro result to add an entity + let props = with!(Person, Age(42), RiskCategory::High); let person = context.add_entity(props).unwrap(); // Verify the entity was created with the correct properties diff --git a/src/entity/query/query_impls.rs b/src/entity/query/query_impls.rs index 0ad5fd62..0d8a7787 100644 --- a/src/entity/query/query_impls.rs +++ b/src/entity/query/query_impls.rs @@ -43,6 +43,40 @@ 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 { + fn get_query(&self) -> Vec<(usize, HashValueType)> { + Vec::new() + } + + fn get_type_ids(&self) -> Vec { + Vec::new() + } + + fn multi_property_id(&self) -> Option { + None + } + + fn multi_property_value_hash(&self) -> HashValueType { + let empty: &[u128] = &[]; + one_shot_128(&empty) + } + + fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> { + let population_iterator = context.get_entity_iterator::(); + EntitySetIterator::from_population_iterator(population_iterator) + } + + fn match_entity(&self, _entity_id: EntityId, _context: &Context) -> bool { + true + } + + fn filter_entities(&self, _entities: &mut Vec>, _context: &Context) { + // Nothing to do. + } +} + // Implement the query version with one parameter. impl> Query for (P1,) { fn get_query(&self) -> Vec<(usize, HashValueType)> { diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 17815366..f1c3ddc1 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -1,4 +1,3 @@ -mod all; mod assert_almost_eq; mod define_data_plugin; mod define_global_property; @@ -7,3 +6,4 @@ mod define_rng; mod edge_impl; mod entity_impl; mod property_impl; +mod with; diff --git a/src/macros/all.rs b/src/macros/with.rs similarity index 71% rename from src/macros/all.rs rename to src/macros/with.rs index 8ae6c242..06104c6c 100644 --- a/src/macros/all.rs +++ b/src/macros/with.rs @@ -4,18 +4,18 @@ /// /// ```ignore /// // Add an entity with default properties -/// let query = all!(Person); +/// let query = with!(Person); /// context.add_entity(query)?; /// /// // An inline query matching a single property -/// let person = context.sample_entity(MyRng, all!(Person, Age(12)))?; +/// let person = context.sample_entity(MyRng, with!(Person, Age(12)))?; /// /// // A query matching multiple properties -/// let query = all!(Person, Age(12), RiskCategory::High); +/// let query = with!(Person, Age(12), RiskCategory::High); /// let count = context.count_entities(query); /// ``` #[macro_export] -macro_rules! all { +macro_rules! with { // No properties - generates empty tuple query ($entity:ty) => { $crate::EntityPropertyTuple::<$entity, _>::new(()) @@ -48,9 +48,9 @@ mod tests { fn all_macro_with_add_entity() { let mut context = Context::new(); - // Use all! macro to add an entity + // Use with! macro to add an entity let person = context - .add_entity(all!(TestPerson, Age(42), Risk::High)) + .add_entity(with!(TestPerson, Age(42), Risk::High)) .unwrap(); // Verify properties were set correctly @@ -65,17 +65,18 @@ mod tests { // Add some entities let p1 = context - .add_entity(all!(TestPerson, Age(30), Risk::High)) + .add_entity(with!(TestPerson, Age(30), Risk::High)) .unwrap(); let _ = context - .add_entity(all!(TestPerson, Age(30), Risk::Low)) + .add_entity(with!(TestPerson, Age(30), Risk::Low)) .unwrap(); let _ = context - .add_entity(all!(TestPerson, Age(25), Risk::High)) + .add_entity(with!(TestPerson, Age(25), Risk::High)) .unwrap(); // Sample from entities matching the query - let sampled = context.sample_entity(AllMacroTestRng, all!(TestPerson, Age(30), Risk::High)); + let sampled = + context.sample_entity(AllMacroTestRng, with!(TestPerson, Age(30), Risk::High)); assert_eq!(sampled, Some(p1)); } @@ -86,15 +87,16 @@ mod tests { // Add some entities that don't match the query let _ = context - .add_entity(all!(TestPerson, Age(30), Risk::Low)) + .add_entity(with!(TestPerson, Age(30), Risk::Low)) .unwrap(); // Sample should return None when no entities match - let sampled = context.sample_entity(AllMacroTestRng, all!(TestPerson, Age(30), Risk::High)); + let sampled = + context.sample_entity(AllMacroTestRng, with!(TestPerson, Age(30), Risk::High)); assert_eq!(sampled, None); } - // Demonstrate that `all!` can disambiguate entities in otherwise ambiguous cases. + // Demonstrate that `with!` can disambiguate entities in otherwise ambiguous cases. use crate::entity::EntityId; define_entity!(TestMammal); define_entity!(TestAvian); @@ -106,17 +108,17 @@ mod tests { let mut context = Context::new(); context - .add_entity(all!(TestAvian, IsBipedal(true))) + .add_entity(with!(TestAvian, IsBipedal(true))) .unwrap(); context - .add_entity(all!(TestMammal, IsBipedal(true))) + .add_entity(with!(TestMammal, IsBipedal(true))) .unwrap(); - let result = context.query_result_iterator(all!(TestMammal, IsBipedal(true))); + let result = context.query_result_iterator(with!(TestMammal, IsBipedal(true))); assert_eq!(result.count(), 1); let sampled_id = context - .sample_entity(AllMacroTestRng, all!(TestAvian, IsBipedal(true))) + .sample_entity(AllMacroTestRng, with!(TestAvian, IsBipedal(true))) .unwrap(); let expected_id: EntityId = EntityId::new(0); assert_eq!(sampled_id, expected_id); diff --git a/src/prelude.rs b/src/prelude.rs index 6e11b6cc..7e202a85 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -11,5 +11,5 @@ pub use crate::report::ContextReportExt; pub use crate::{ define_data_plugin, define_derived_property, define_edge_type, define_entity, define_global_property, define_multi_property, define_property, define_report, define_rng, - impl_edge_type, impl_entity, impl_property, PluginContext, + impl_edge_type, impl_entity, impl_property, with, PluginContext, };