1use std::any::{Any, TypeId};
32use std::collections::HashMap;
33use std::sync::atomic::{AtomicUsize, Ordering};
34use std::sync::{LazyLock, Mutex, OnceLock};
35
36use crate::entity::entity::Entity;
37use crate::entity::entity_store::register_property_with_entity;
38use crate::entity::events::PartialPropertyChangeEvent;
39use crate::entity::index::{IndexCountResult, IndexSetResult};
40use crate::entity::property::Property;
41use crate::entity::property_list::PropertyList;
42use crate::entity::property_value_store::PropertyValueStore;
43use crate::entity::property_value_store_core::PropertyValueStoreCore;
44use crate::entity::value_change_counter::StratifiedValueChangeCounter;
45use crate::entity::{EntityId, HashValueType, PropertyIndexType};
46use crate::Context;
47
48static NEXT_PROPERTY_ID: LazyLock<Mutex<HashMap<usize, usize>>> =
56 LazyLock::new(|| Mutex::new(HashMap::default()));
57
58#[derive(Default)]
63pub(super) struct PropertyMetadata<E: Entity> {
64 pub dependents: Vec<usize>,
68 #[allow(clippy::type_complexity)]
73 pub value_store_constructor: Option<fn() -> Box<dyn PropertyValueStore<E>>>,
74}
75
76#[allow(clippy::type_complexity)]
80static PROPERTY_METADATA_BUILDER: LazyLock<
81 Mutex<HashMap<(usize, usize), Box<dyn Any + Send + Sync>>>,
82> = LazyLock::new(|| Mutex::new(HashMap::default()));
83
84#[allow(clippy::type_complexity)]
89static PROPERTY_METADATA: OnceLock<HashMap<(usize, usize), Box<dyn Any + Send + Sync>>> =
90 OnceLock::new();
91
92fn property_metadata() -> &'static HashMap<(usize, usize), Box<dyn Any + Send + Sync>> {
94 PROPERTY_METADATA.get_or_init(|| {
95 let mut builder = PROPERTY_METADATA_BUILDER.lock().unwrap();
96 std::mem::take(&mut *builder)
97 })
98}
99
100#[must_use]
106pub(super) fn get_property_dependents_static<E: Entity>(property_index: usize) -> &'static [usize] {
107 let map = property_metadata();
108 let property_metadata = map
109 .get(&(E::id(), property_index))
110 .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
111 let property_metadata: &PropertyMetadata<E> = property_metadata.downcast_ref().unwrap_or_else(
112 || panic!(
113 "Property type at index {:?} does not match registered property type. You must use the `define_property!` macro to create a registered property.",
114 property_index
115 )
116 );
117
118 property_metadata.dependents.as_slice()
119}
120
121pub fn add_to_property_registry<E: Entity, P: Property<E>>() {
125 let property_index = P::id();
127
128 register_property_with_entity(
130 <E as Entity>::type_id(),
131 <P as Property<E>>::type_id(),
132 P::is_required(),
133 );
134
135 let mut property_metadata = PROPERTY_METADATA_BUILDER.lock().unwrap();
136 if PROPERTY_METADATA.get().is_some() {
137 panic!(
138 "`add_to_property_registry()` called after property metadata was frozen; registration must occur during startup/ctors."
139 );
140 }
141
142 {
144 let metadata = property_metadata
145 .entry((E::id(), property_index))
146 .or_insert_with(|| Box::new(PropertyMetadata::<E>::default()));
147 let metadata: &mut PropertyMetadata<E> = metadata.downcast_mut().unwrap();
148 metadata
149 .value_store_constructor
150 .get_or_insert(PropertyValueStoreCore::<E, P>::new_boxed);
151 }
152
153 for dependency in P::non_derived_dependencies() {
155 let dependency_meta = property_metadata
157 .entry((E::id(), dependency))
158 .or_insert_with(|| Box::new(PropertyMetadata::<E>::default()));
159 let dependency_meta: &mut PropertyMetadata<E> = dependency_meta.downcast_mut().unwrap();
160 dependency_meta.dependents.push(property_index);
161 }
162}
163
164pub fn get_registered_property_count<E: Entity>() -> usize {
166 let map = NEXT_PROPERTY_ID.lock().unwrap();
167 *map.get(&E::id()).unwrap_or(&0)
168}
169
170pub fn initialize_property_id<E: Entity>(property_id: &AtomicUsize) -> usize {
183 let mut guard = NEXT_PROPERTY_ID.lock().unwrap();
185 let candidate = guard.entry(E::id()).or_insert_with(|| 0);
186
187 match property_id.compare_exchange(usize::MAX, *candidate, Ordering::AcqRel, Ordering::Acquire)
194 {
195 Ok(_) => {
196 *candidate += 1;
198 *candidate - 1
199 }
200 Err(existing) => {
201 existing
204 }
205 }
206}
207
208pub struct PropertyStore<E: Entity> {
210 items: Vec<Box<dyn PropertyValueStore<E>>>,
212}
213
214impl<E: Entity> Default for PropertyStore<E> {
215 fn default() -> Self {
216 PropertyStore::new()
217 }
218}
219
220impl<E: Entity> PropertyStore<E> {
221 pub fn new() -> Self {
223 let num_items = get_registered_property_count::<E>();
224 let property_metadata = property_metadata();
226
227 let items = (0..num_items)
229 .map(|idx| {
230 let metadata = property_metadata
231 .get(&(E::id(), idx))
232 .unwrap_or_else(|| panic!("No property metadata entry for index {idx}"))
233 .downcast_ref::<PropertyMetadata<E>>()
234 .unwrap_or_else(|| {
235 panic!(
236 "Property metadata entry for index {idx} does not match expected type"
237 )
238 });
239 let constructor = metadata
240 .value_store_constructor
241 .unwrap_or_else(|| panic!("No PropertyValueStore constructor for index {idx}"));
242 constructor()
243 })
244 .collect();
245
246 Self { items }
247 }
248
249 #[cfg(test)]
251 pub(crate) fn get_with_id(&self, property_id: usize) -> &dyn PropertyValueStore<E> {
252 self.items[property_id].as_ref()
253 }
254
255 #[must_use]
257 pub fn get<P: Property<E>>(&self) -> &PropertyValueStoreCore<E, P> {
258 let index = P::id();
259 let property_value_store =
260 self.items
261 .get(index)
262 .unwrap_or_else(||
263 panic!(
264 "No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
265 index,
266 P::name()
267 )
268 );
269 let property_value_store: &PropertyValueStoreCore<E, P> = property_value_store
270 .as_any()
271 .downcast_ref::<PropertyValueStoreCore<E, P>>()
272 .unwrap_or_else(||
273 {
274 panic!(
275 "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.",
276 index,
277 (**property_value_store).type_id(),
278 TypeId::of::<PropertyValueStoreCore<E, P>>()
279 )
280 }
281 );
282 property_value_store
283 }
284
285 #[must_use]
287 pub fn get_mut<P: Property<E>>(&mut self) -> &mut PropertyValueStoreCore<E, P> {
288 let index = P::id();
289 let property_value_store =
290 self.items
291 .get_mut(index)
292 .unwrap_or_else(||
293 panic!(
294 "No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
295 index,
296 P::name()
297 )
298 );
299 let type_id = (**property_value_store).type_id(); let property_value_store: &mut PropertyValueStoreCore<E, P> = property_value_store
301 .as_any_mut()
302 .downcast_mut::<PropertyValueStoreCore<E, P>>()
303 .unwrap_or_else(||
304 {
305 panic!(
306 "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.",
307 index,
308 type_id,
309 TypeId::of::<PropertyValueStoreCore<E, P>>()
310 )
311 }
312 );
313 property_value_store
314 }
315
316 pub(crate) fn create_partial_property_change(
319 &self,
320 property_index: usize,
321 entity_id: EntityId<E>,
322 context: &Context,
323 ) -> Box<dyn PartialPropertyChangeEvent> {
324 let property_value_store = self.items
325 .get(property_index)
326 .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
327
328 property_value_store.create_partial_property_change(entity_id, context)
329 }
330
331 #[cfg(test)]
337 pub fn is_property_indexed<P: Property<E>>(&self) -> bool {
338 self.items
339 .get(P::index_id())
340 .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()))
341 .index_type()
342 != PropertyIndexType::Unindexed
343 }
344
345 pub fn set_property_indexed<P: Property<E>>(&mut self, index_type: PropertyIndexType) {
350 let property_value_store = self.items
351 .get_mut(P::index_id())
352 .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()));
353 property_value_store.set_indexed(index_type);
354 }
355
356 pub fn create_value_change_counter<PL, P>(&mut self) -> usize
360 where
361 PL: PropertyList<E> + Eq + std::hash::Hash,
362 P: Property<E> + Eq + std::hash::Hash,
363 {
364 let property_value_store = self.get_mut::<P>();
365 property_value_store.add_value_change_counter(Box::new(StratifiedValueChangeCounter::<
366 E,
367 PL,
368 P,
369 >::new()))
370 }
371
372 pub fn index_unindexed_entities_for_property_id(
375 &mut self,
376 context: &Context,
377 property_id: usize,
378 ) {
379 self.items[property_id].index_unindexed_entities(context)
380 }
381
382 pub fn index_unindexed_entities_for_all_properties(&mut self, context: &Context) {
384 for store in &mut self.items {
385 store.index_unindexed_entities(context);
386 }
387 }
388
389 pub fn get_index_set_with_hash_for_property_id(
390 &self,
391 property_id: usize,
392 hash: HashValueType,
393 ) -> IndexSetResult<'_, E> {
394 self.items[property_id].get_index_set_with_hash_result(hash)
395 }
396
397 pub fn get_index_count_with_hash_for_property_id(
398 &self,
399 property_id: usize,
400 hash: HashValueType,
401 ) -> IndexCountResult {
402 self.items[property_id].get_index_count_with_hash_result(hash)
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 #![allow(dead_code)]
409 use super::*;
410 use crate::entity::index::{IndexCountResult, IndexSetResult};
411 use crate::prelude::*;
412 use crate::{define_entity, define_property, Context};
413
414 define_entity!(Person);
415
416 define_property!(struct Age(u8), Person);
417 define_property!(
418 enum InfectionStatus {
419 Susceptible,
420 Infected,
421 Recovered,
422 },
423 Person,
424 default_const = InfectionStatus::Susceptible
425 );
426 define_property!(struct Vaccinated(bool), Person, default_const = Vaccinated(false));
427
428 #[test]
429 fn test_get_property_store() {
430 let property_store = PropertyStore::new();
431
432 {
433 let ages: &PropertyValueStoreCore<_, Age> = property_store.get();
434 ages.set(EntityId::<Person>::new(0), Age(12));
435 ages.set(EntityId::<Person>::new(1), Age(33));
436 ages.set(EntityId::<Person>::new(2), Age(44));
437
438 let infection_statuses: &PropertyValueStoreCore<_, InfectionStatus> =
439 property_store.get();
440 infection_statuses.set(EntityId::<Person>::new(0), InfectionStatus::Susceptible);
441 infection_statuses.set(EntityId::<Person>::new(1), InfectionStatus::Susceptible);
442 infection_statuses.set(EntityId::<Person>::new(2), InfectionStatus::Infected);
443
444 let vaccine_status: &PropertyValueStoreCore<_, Vaccinated> = property_store.get();
445 vaccine_status.set(EntityId::<Person>::new(0), Vaccinated(true));
446 vaccine_status.set(EntityId::<Person>::new(1), Vaccinated(false));
447 vaccine_status.set(EntityId::<Person>::new(2), Vaccinated(true));
448 }
449
450 {
452 let ages: &PropertyValueStoreCore<_, Age> = property_store.get();
453 assert_eq!(ages.get(EntityId::<Person>::new(0)), Age(12));
454 assert_eq!(ages.get(EntityId::<Person>::new(1)), Age(33));
455 assert_eq!(ages.get(EntityId::<Person>::new(2)), Age(44));
456
457 let infection_statuses: &PropertyValueStoreCore<_, InfectionStatus> =
458 property_store.get();
459 assert_eq!(
460 infection_statuses.get(EntityId::<Person>::new(0)),
461 InfectionStatus::Susceptible
462 );
463 assert_eq!(
464 infection_statuses.get(EntityId::<Person>::new(1)),
465 InfectionStatus::Susceptible
466 );
467 assert_eq!(
468 infection_statuses.get(EntityId::<Person>::new(2)),
469 InfectionStatus::Infected
470 );
471
472 let vaccine_status: &PropertyValueStoreCore<_, Vaccinated> = property_store.get();
473 assert_eq!(
474 vaccine_status.get(EntityId::<Person>::new(0)),
475 Vaccinated(true)
476 );
477 assert_eq!(
478 vaccine_status.get(EntityId::<Person>::new(1)),
479 Vaccinated(false)
480 );
481 assert_eq!(
482 vaccine_status.get(EntityId::<Person>::new(2)),
483 Vaccinated(true)
484 );
485 }
486 }
487
488 #[test]
489 fn test_index_query_results_for_property_store() {
490 let mut context = Context::new();
491 context.index_property::<Person, Age>();
492
493 let existing_value = Age(12);
494 let missing_value = Age(99);
495 let existing_hash = <Age as Property<Person>>::hash_property_value(&existing_value);
496 let missing_hash = <Age as Property<Person>>::hash_property_value(&missing_value);
497
498 let _ = context.add_entity((existing_value,)).unwrap();
499 let _ = context.add_entity((existing_value,)).unwrap();
500
501 let property_store = context.entity_store.get_property_store::<Person>();
502
503 assert_eq!(
505 property_store
506 .get_index_count_with_hash_for_property_id(Age::index_id(), missing_hash,),
507 IndexCountResult::Count(0)
508 );
509 assert_eq!(
510 property_store
511 .get_index_count_with_hash_for_property_id(Age::index_id(), existing_hash,),
512 IndexCountResult::Count(2)
513 );
514
515 assert!(matches!(
517 property_store.get_index_set_with_hash_for_property_id(Age::index_id(), missing_hash,),
518 IndexSetResult::Empty
519 ));
520 assert!(matches!(
521 property_store.get_index_set_with_hash_for_property_id(
522 Age::index_id(),
523 existing_hash,
524 ),
525 IndexSetResult::Set(set) if set.len() == 2
526 ));
527 }
528
529 #[test]
530 fn test_index_query_results_for_property_store_value_count_index() {
531 let mut context = Context::new();
532 context.index_property_counts::<Person, Age>();
533
534 let existing_value = Age(12);
535 let missing_value = Age(99);
536 let existing_hash = <Age as Property<Person>>::hash_property_value(&existing_value);
537 let missing_hash = <Age as Property<Person>>::hash_property_value(&missing_value);
538
539 let _ = context.add_entity((existing_value,)).unwrap();
540 let _ = context.add_entity((existing_value,)).unwrap();
541
542 let property_store = context.entity_store.get_property_store::<Person>();
543
544 assert_eq!(
546 property_store
547 .get_index_count_with_hash_for_property_id(Age::index_id(), missing_hash,),
548 IndexCountResult::Count(0)
549 );
550 assert_eq!(
551 property_store
552 .get_index_count_with_hash_for_property_id(Age::index_id(), existing_hash,),
553 IndexCountResult::Count(2)
554 );
555
556 assert!(matches!(
558 property_store.get_index_set_with_hash_for_property_id(Age::index_id(), missing_hash,),
559 IndexSetResult::Unsupported
560 ));
561 assert!(matches!(
562 property_store.get_index_set_with_hash_for_property_id(Age::index_id(), existing_hash,),
563 IndexSetResult::Unsupported
564 ));
565 }
566
567 #[test]
568 fn test_index_query_results_for_property_store_unindexed() {
569 let mut context = Context::new();
570 let existing_value = Age(12);
571 let missing_value = Age(99);
572 let existing_hash = <Age as Property<Person>>::hash_property_value(&existing_value);
573 let missing_hash = <Age as Property<Person>>::hash_property_value(&missing_value);
574
575 let _ = context.add_entity((existing_value,)).unwrap();
576 let _ = context.add_entity((existing_value,)).unwrap();
577
578 let property_store = context.entity_store.get_property_store::<Person>();
579
580 assert_eq!(
582 property_store
583 .get_index_count_with_hash_for_property_id(Age::index_id(), missing_hash,),
584 IndexCountResult::Unsupported
585 );
586 assert_eq!(
587 property_store
588 .get_index_count_with_hash_for_property_id(Age::index_id(), existing_hash,),
589 IndexCountResult::Unsupported
590 );
591
592 assert!(matches!(
594 property_store.get_index_set_with_hash_for_property_id(Age::index_id(), missing_hash,),
595 IndexSetResult::Unsupported
596 ));
597 assert!(matches!(
598 property_store.get_index_set_with_hash_for_property_id(Age::index_id(), existing_hash,),
599 IndexSetResult::Unsupported
600 ));
601 }
602}