use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{LazyLock, Mutex, OnceLock};
use crate::entity::entity::Entity;
use crate::entity::entity_store::register_property_with_entity;
use crate::entity::events::PartialPropertyChangeEventBox;
use crate::entity::index::{IndexCountResult, IndexSetResult};
use crate::entity::property::Property;
use crate::entity::property_list::PropertyList;
use crate::entity::property_value_store::PropertyValueStore;
use crate::entity::property_value_store_core::PropertyValueStoreCore;
use crate::entity::value_change_counter::StratifiedValueChangeCounter;
use crate::entity::{EntityId, PropertyIndexType};
use crate::Context;
static NEXT_PROPERTY_ID: LazyLock<Mutex<HashMap<usize, usize>>> =
LazyLock::new(|| Mutex::new(HashMap::default()));
#[derive(Default)]
pub(super) struct PropertyMetadata<E: Entity> {
pub dependents: Vec<usize>,
#[allow(clippy::type_complexity)]
pub value_store_constructor: Option<fn() -> Box<dyn PropertyValueStore<E>>>,
}
#[allow(clippy::type_complexity)]
static PROPERTY_METADATA_BUILDER: LazyLock<
Mutex<HashMap<(usize, usize), Box<dyn Any + Send + Sync>>>,
> = LazyLock::new(|| Mutex::new(HashMap::default()));
#[allow(clippy::type_complexity)]
static PROPERTY_METADATA: OnceLock<HashMap<(usize, usize), Box<dyn Any + Send + Sync>>> =
OnceLock::new();
fn property_metadata() -> &'static HashMap<(usize, usize), Box<dyn Any + Send + Sync>> {
PROPERTY_METADATA.get_or_init(|| {
let mut builder = PROPERTY_METADATA_BUILDER.lock().unwrap();
std::mem::take(&mut *builder)
})
}
#[must_use]
pub(super) fn get_property_dependents_static<E: Entity>(property_index: usize) -> &'static [usize] {
let map = property_metadata();
let property_metadata = map
.get(&(E::id(), property_index))
.unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
let property_metadata: &PropertyMetadata<E> = property_metadata.downcast_ref().unwrap_or_else(
|| panic!(
"Property type at index {:?} does not match registered property type. You must use the `define_property!` macro to create a registered property.",
property_index
)
);
property_metadata.dependents.as_slice()
}
pub fn add_to_property_registry<E: Entity, P: Property<E>>() {
let property_index = P::id();
register_property_with_entity(
<E as Entity>::type_id(),
<P as Property<E>>::type_id(),
P::is_required(),
);
let mut property_metadata = PROPERTY_METADATA_BUILDER.lock().unwrap();
if PROPERTY_METADATA.get().is_some() {
panic!(
"`add_to_property_registry()` called after property metadata was frozen; registration must occur during startup/ctors."
);
}
{
let metadata = property_metadata
.entry((E::id(), property_index))
.or_insert_with(|| Box::new(PropertyMetadata::<E>::default()));
let metadata: &mut PropertyMetadata<E> = metadata.downcast_mut().unwrap();
metadata
.value_store_constructor
.get_or_insert(PropertyValueStoreCore::<E, P>::new_boxed);
}
for dependency in P::non_derived_dependencies() {
let dependency_meta = property_metadata
.entry((E::id(), dependency))
.or_insert_with(|| Box::new(PropertyMetadata::<E>::default()));
let dependency_meta: &mut PropertyMetadata<E> = dependency_meta.downcast_mut().unwrap();
dependency_meta.dependents.push(property_index);
}
}
pub fn get_registered_property_count<E: Entity>() -> usize {
let map = NEXT_PROPERTY_ID.lock().unwrap();
*map.get(&E::id()).unwrap_or(&0)
}
pub fn initialize_property_id<E: Entity>(property_id: &AtomicUsize) -> usize {
let mut guard = NEXT_PROPERTY_ID.lock().unwrap();
let candidate = guard.entry(E::id()).or_insert_with(|| 0);
match property_id.compare_exchange(usize::MAX, *candidate, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => {
*candidate += 1;
*candidate - 1
}
Err(existing) => {
existing
}
}
}
pub struct PropertyStore<E: Entity> {
items: Vec<Box<dyn PropertyValueStore<E>>>,
}
impl<E: Entity> Default for PropertyStore<E> {
fn default() -> Self {
PropertyStore::new()
}
}
impl<E: Entity> PropertyStore<E> {
pub fn new() -> Self {
let num_items = get_registered_property_count::<E>();
let property_metadata = property_metadata();
let items = (0..num_items)
.map(|idx| {
let metadata = property_metadata
.get(&(E::id(), idx))
.unwrap_or_else(|| panic!("No property metadata entry for index {idx}"))
.downcast_ref::<PropertyMetadata<E>>()
.unwrap_or_else(|| {
panic!(
"Property metadata entry for index {idx} does not match expected type"
)
});
let constructor = metadata
.value_store_constructor
.unwrap_or_else(|| panic!("No PropertyValueStore constructor for index {idx}"));
constructor()
})
.collect();
Self { items }
}
#[must_use]
pub fn get<P: Property<E>>(&self) -> &PropertyValueStoreCore<E, P> {
let index = P::id();
let property_value_store =
self.items
.get(index)
.unwrap_or_else(||
panic!(
"No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
index,
P::name()
)
);
let property_value_store: &PropertyValueStoreCore<E, P> = property_value_store
.as_any()
.downcast_ref::<PropertyValueStoreCore<E, P>>()
.unwrap_or_else(||
{
panic!(
"Property type at index {:?} does not match registered property type. Found type_id {:?} while getting type_id {:?}. You must use the `define_property!` macro to create a registered property.",
index,
(**property_value_store).type_id(),
TypeId::of::<PropertyValueStoreCore<E, P>>()
)
}
);
property_value_store
}
#[must_use]
pub fn get_mut<P: Property<E>>(&mut self) -> &mut PropertyValueStoreCore<E, P> {
let index = P::id();
let property_value_store =
self.items
.get_mut(index)
.unwrap_or_else(||
panic!(
"No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
index,
P::name()
)
);
let type_id = (**property_value_store).type_id(); let property_value_store: &mut PropertyValueStoreCore<E, P> = property_value_store
.as_any_mut()
.downcast_mut::<PropertyValueStoreCore<E, P>>()
.unwrap_or_else(||
{
panic!(
"Property type at index {:?} does not match registered property type. Found type_id {:?} while getting type_id {:?}. You must use the `define_property!` macro to create a registered property.",
index,
type_id,
TypeId::of::<PropertyValueStoreCore<E, P>>()
)
}
);
property_value_store
}
pub(crate) fn create_partial_property_change(
&self,
property_index: usize,
entity_id: EntityId<E>,
context: &Context,
) -> PartialPropertyChangeEventBox {
let property_value_store = self.items
.get(property_index)
.unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
property_value_store.create_partial_property_change(entity_id, context)
}
pub(crate) fn should_create_partial_property_change(
&self,
property_index: usize,
context: &Context,
) -> bool {
let property_value_store = self.items
.get(property_index)
.unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
property_value_store.should_create_partial_change(context)
}
#[cfg(test)]
pub fn is_property_indexed<P: Property<E>>(&self) -> bool {
self.items
.get(P::index_id())
.unwrap_or_else(|| panic!("No registered property {} found with index = {:?}. You must use the `define_property!` macro to create a registered property.", P::name(), P::index_id()))
.index_type()
!= PropertyIndexType::Unindexed
}
pub fn set_property_indexed<P: Property<E>>(&mut self, index_type: PropertyIndexType) {
let property_value_store = self.items
.get_mut(P::index_id())
.unwrap_or_else(|| panic!("No registered property {} found with index = {:?}. You must use the `define_property!` macro to create a registered property.", P::name(), P::index_id()));
property_value_store.set_indexed(index_type);
}
pub fn create_value_change_counter<PL, P>(&mut self) -> usize
where
PL: PropertyList<E> + Eq + std::hash::Hash,
P: Property<E> + Eq + std::hash::Hash,
{
let property_value_store = self.get_mut::<P>();
property_value_store.add_value_change_counter(Box::new(StratifiedValueChangeCounter::<
E,
PL,
P,
>::new()))
}
pub fn index_unindexed_entities_for_property_id(
&mut self,
context: &Context,
property_id: usize,
) {
self.items[property_id].index_unindexed_entities(context)
}
pub fn index_unindexed_entities_for_all_properties(&mut self, context: &Context) {
for store in &mut self.items {
store.index_unindexed_entities(context);
}
}
pub fn get_index_set_for_query_parts(
&self,
property_id: usize,
query_parts: &[&dyn Any],
) -> IndexSetResult<'_, E> {
self.items[property_id].get_index_set_for_query_parts(query_parts)
}
pub fn get_index_count_for_query_parts(
&self,
property_id: usize,
query_parts: &[&dyn Any],
) -> IndexCountResult {
self.items[property_id].get_index_count_for_query_parts(query_parts)
}
}
#[cfg(test)]
mod tests {
#![allow(dead_code)]
use std::any::Any;
use super::*;
use crate::entity::index::{IndexCountResult, IndexSetResult};
use crate::prelude::*;
use crate::{define_entity, define_property, with, Context};
define_entity!(Person);
define_property!(struct Age(u8), Person);
define_property!(
enum InfectionStatus {
Susceptible,
Infected,
Recovered,
},
Person,
default_const = InfectionStatus::Susceptible
);
define_property!(struct Vaccinated(bool), Person, default_const = Vaccinated(false));
#[test]
fn test_get_property_store() {
let mut property_store = PropertyStore::new();
{
let ages: &mut PropertyValueStoreCore<_, Age> = property_store.get_mut();
ages.set(EntityId::<Person>::new(0), Age(12));
ages.set(EntityId::<Person>::new(1), Age(33));
ages.set(EntityId::<Person>::new(2), Age(44));
let infection_statuses: &mut PropertyValueStoreCore<_, InfectionStatus> =
property_store.get_mut();
infection_statuses.set(EntityId::<Person>::new(0), InfectionStatus::Susceptible);
infection_statuses.set(EntityId::<Person>::new(1), InfectionStatus::Susceptible);
infection_statuses.set(EntityId::<Person>::new(2), InfectionStatus::Infected);
let vaccine_status: &mut PropertyValueStoreCore<_, Vaccinated> =
property_store.get_mut();
vaccine_status.set(EntityId::<Person>::new(0), Vaccinated(true));
vaccine_status.set(EntityId::<Person>::new(1), Vaccinated(false));
vaccine_status.set(EntityId::<Person>::new(2), Vaccinated(true));
}
{
let ages: &PropertyValueStoreCore<_, Age> = property_store.get();
assert_eq!(ages.get(EntityId::<Person>::new(0)), Age(12));
assert_eq!(ages.get(EntityId::<Person>::new(1)), Age(33));
assert_eq!(ages.get(EntityId::<Person>::new(2)), Age(44));
let infection_statuses: &PropertyValueStoreCore<_, InfectionStatus> =
property_store.get();
assert_eq!(
infection_statuses.get(EntityId::<Person>::new(0)),
InfectionStatus::Susceptible
);
assert_eq!(
infection_statuses.get(EntityId::<Person>::new(1)),
InfectionStatus::Susceptible
);
assert_eq!(
infection_statuses.get(EntityId::<Person>::new(2)),
InfectionStatus::Infected
);
let vaccine_status: &PropertyValueStoreCore<_, Vaccinated> = property_store.get();
assert_eq!(
vaccine_status.get(EntityId::<Person>::new(0)),
Vaccinated(true)
);
assert_eq!(
vaccine_status.get(EntityId::<Person>::new(1)),
Vaccinated(false)
);
assert_eq!(
vaccine_status.get(EntityId::<Person>::new(2)),
Vaccinated(true)
);
}
}
#[test]
fn test_index_query_results_for_property_store() {
let mut context = Context::new();
context.index_property::<Person, Age>();
let existing_value = Age(12);
let missing_value = Age(99);
let existing_query_parts = [&existing_value as &dyn Any];
let missing_query_parts = [&missing_value as &dyn Any];
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::<Person>();
assert_eq!(
property_store.get_index_count_for_query_parts(Age::index_id(), &missing_query_parts,),
IndexCountResult::Count(0)
);
assert_eq!(
property_store.get_index_count_for_query_parts(Age::index_id(), &existing_query_parts,),
IndexCountResult::Count(2)
);
assert!(matches!(
property_store.get_index_set_for_query_parts(Age::index_id(), &missing_query_parts,),
IndexSetResult::Empty
));
assert!(matches!(
property_store.get_index_set_for_query_parts(
Age::index_id(),
&existing_query_parts,
),
IndexSetResult::Set(set) if set.len() == 2
));
}
#[test]
fn test_index_query_results_for_property_store_value_count_index() {
let mut context = Context::new();
context.index_property_counts::<Person, Age>();
let existing_value = Age(12);
let missing_value = Age(99);
let existing_query_parts = [&existing_value as &dyn Any];
let missing_query_parts = [&missing_value as &dyn Any];
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::<Person>();
assert_eq!(
property_store.get_index_count_for_query_parts(Age::index_id(), &missing_query_parts,),
IndexCountResult::Count(0)
);
assert_eq!(
property_store.get_index_count_for_query_parts(Age::index_id(), &existing_query_parts,),
IndexCountResult::Count(2)
);
assert!(matches!(
property_store.get_index_set_for_query_parts(Age::index_id(), &missing_query_parts,),
IndexSetResult::Unsupported
));
assert!(matches!(
property_store.get_index_set_for_query_parts(Age::index_id(), &existing_query_parts,),
IndexSetResult::Unsupported
));
}
#[test]
fn test_index_query_results_for_property_store_unindexed() {
let mut context = Context::new();
let existing_value = Age(12);
let missing_value = Age(99);
let existing_query_parts = [&existing_value as &dyn Any];
let missing_query_parts = [&missing_value as &dyn Any];
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::<Person>();
assert_eq!(
property_store.get_index_count_for_query_parts(Age::index_id(), &missing_query_parts,),
IndexCountResult::Unsupported
);
assert_eq!(
property_store.get_index_count_for_query_parts(Age::index_id(), &existing_query_parts,),
IndexCountResult::Unsupported
);
assert!(matches!(
property_store.get_index_set_for_query_parts(Age::index_id(), &missing_query_parts,),
IndexSetResult::Unsupported
));
assert!(matches!(
property_store.get_index_set_for_query_parts(Age::index_id(), &existing_query_parts,),
IndexSetResult::Unsupported
));
}
}