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::PartialPropertyChangeEventBox;
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, 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 #[must_use]
251 pub fn get<P: Property<E>>(&self) -> &PropertyValueStoreCore<E, P> {
252 let index = P::id();
253 let property_value_store =
254 self.items
255 .get(index)
256 .unwrap_or_else(||
257 panic!(
258 "No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
259 index,
260 P::name()
261 )
262 );
263 let property_value_store: &PropertyValueStoreCore<E, P> = property_value_store
264 .as_any()
265 .downcast_ref::<PropertyValueStoreCore<E, P>>()
266 .unwrap_or_else(||
267 {
268 panic!(
269 "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.",
270 index,
271 (**property_value_store).type_id(),
272 TypeId::of::<PropertyValueStoreCore<E, P>>()
273 )
274 }
275 );
276 property_value_store
277 }
278
279 #[must_use]
281 pub fn get_mut<P: Property<E>>(&mut self) -> &mut PropertyValueStoreCore<E, P> {
282 let index = P::id();
283 let property_value_store =
284 self.items
285 .get_mut(index)
286 .unwrap_or_else(||
287 panic!(
288 "No registered property found with index = {:?} while trying to get property {}. You must use the `define_property!` macro to create a registered property.",
289 index,
290 P::name()
291 )
292 );
293 let type_id = (**property_value_store).type_id(); let property_value_store: &mut PropertyValueStoreCore<E, P> = property_value_store
295 .as_any_mut()
296 .downcast_mut::<PropertyValueStoreCore<E, P>>()
297 .unwrap_or_else(||
298 {
299 panic!(
300 "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.",
301 index,
302 type_id,
303 TypeId::of::<PropertyValueStoreCore<E, P>>()
304 )
305 }
306 );
307 property_value_store
308 }
309
310 pub(crate) fn create_partial_property_change(
313 &self,
314 property_index: usize,
315 entity_id: EntityId<E>,
316 context: &Context,
317 ) -> PartialPropertyChangeEventBox {
318 let property_value_store = self.items
319 .get(property_index)
320 .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
321
322 property_value_store.create_partial_property_change(entity_id, context)
323 }
324
325 pub(crate) fn should_create_partial_property_change(
327 &self,
328 property_index: usize,
329 context: &Context,
330 ) -> bool {
331 let property_value_store = self.items
332 .get(property_index)
333 .unwrap_or_else(|| panic!("No registered property found with index = {property_index:?}. You must use the `define_property!` macro to create a registered property."));
334
335 property_value_store.should_create_partial_change(context)
336 }
337
338 #[cfg(test)]
344 pub fn is_property_indexed<P: Property<E>>(&self) -> bool {
345 self.items
346 .get(P::index_id())
347 .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()))
348 .index_type()
349 != PropertyIndexType::Unindexed
350 }
351
352 pub fn set_property_indexed<P: Property<E>>(&mut self, index_type: PropertyIndexType) {
357 let property_value_store = self.items
358 .get_mut(P::index_id())
359 .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()));
360 property_value_store.set_indexed(index_type);
361 }
362
363 pub fn create_value_change_counter<PL, P>(&mut self) -> usize
367 where
368 PL: PropertyList<E> + Eq + std::hash::Hash,
369 P: Property<E> + Eq + std::hash::Hash,
370 {
371 let property_value_store = self.get_mut::<P>();
372 property_value_store.add_value_change_counter(Box::new(StratifiedValueChangeCounter::<
373 E,
374 PL,
375 P,
376 >::new()))
377 }
378
379 pub fn index_unindexed_entities_for_property_id(
382 &mut self,
383 context: &Context,
384 property_id: usize,
385 ) {
386 self.items[property_id].index_unindexed_entities(context)
387 }
388
389 pub fn index_unindexed_entities_for_all_properties(&mut self, context: &Context) {
391 for store in &mut self.items {
392 store.index_unindexed_entities(context);
393 }
394 }
395
396 pub fn get_index_set_for_query_parts(
397 &self,
398 property_id: usize,
399 query_parts: &[&dyn Any],
400 ) -> IndexSetResult<'_, E> {
401 self.items[property_id].get_index_set_for_query_parts(query_parts)
402 }
403
404 pub fn get_index_count_for_query_parts(
405 &self,
406 property_id: usize,
407 query_parts: &[&dyn Any],
408 ) -> IndexCountResult {
409 self.items[property_id].get_index_count_for_query_parts(query_parts)
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 #![allow(dead_code)]
416 use std::any::Any;
417
418 use super::*;
419 use crate::entity::index::{IndexCountResult, IndexSetResult};
420 use crate::prelude::*;
421 use crate::{define_entity, define_property, with, Context};
422
423 define_entity!(Person);
424
425 define_property!(struct Age(u8), Person);
426 define_property!(
427 enum InfectionStatus {
428 Susceptible,
429 Infected,
430 Recovered,
431 },
432 Person,
433 default_const = InfectionStatus::Susceptible
434 );
435 define_property!(struct Vaccinated(bool), Person, default_const = Vaccinated(false));
436
437 #[test]
438 fn test_get_property_store() {
439 let mut property_store = PropertyStore::new();
440
441 {
442 let ages: &mut PropertyValueStoreCore<_, Age> = property_store.get_mut();
443 ages.set(EntityId::<Person>::new(0), Age(12));
444 ages.set(EntityId::<Person>::new(1), Age(33));
445 ages.set(EntityId::<Person>::new(2), Age(44));
446
447 let infection_statuses: &mut PropertyValueStoreCore<_, InfectionStatus> =
448 property_store.get_mut();
449 infection_statuses.set(EntityId::<Person>::new(0), InfectionStatus::Susceptible);
450 infection_statuses.set(EntityId::<Person>::new(1), InfectionStatus::Susceptible);
451 infection_statuses.set(EntityId::<Person>::new(2), InfectionStatus::Infected);
452
453 let vaccine_status: &mut PropertyValueStoreCore<_, Vaccinated> =
454 property_store.get_mut();
455 vaccine_status.set(EntityId::<Person>::new(0), Vaccinated(true));
456 vaccine_status.set(EntityId::<Person>::new(1), Vaccinated(false));
457 vaccine_status.set(EntityId::<Person>::new(2), Vaccinated(true));
458 }
459
460 {
462 let ages: &PropertyValueStoreCore<_, Age> = property_store.get();
463 assert_eq!(ages.get(EntityId::<Person>::new(0)), Age(12));
464 assert_eq!(ages.get(EntityId::<Person>::new(1)), Age(33));
465 assert_eq!(ages.get(EntityId::<Person>::new(2)), Age(44));
466
467 let infection_statuses: &PropertyValueStoreCore<_, InfectionStatus> =
468 property_store.get();
469 assert_eq!(
470 infection_statuses.get(EntityId::<Person>::new(0)),
471 InfectionStatus::Susceptible
472 );
473 assert_eq!(
474 infection_statuses.get(EntityId::<Person>::new(1)),
475 InfectionStatus::Susceptible
476 );
477 assert_eq!(
478 infection_statuses.get(EntityId::<Person>::new(2)),
479 InfectionStatus::Infected
480 );
481
482 let vaccine_status: &PropertyValueStoreCore<_, Vaccinated> = property_store.get();
483 assert_eq!(
484 vaccine_status.get(EntityId::<Person>::new(0)),
485 Vaccinated(true)
486 );
487 assert_eq!(
488 vaccine_status.get(EntityId::<Person>::new(1)),
489 Vaccinated(false)
490 );
491 assert_eq!(
492 vaccine_status.get(EntityId::<Person>::new(2)),
493 Vaccinated(true)
494 );
495 }
496 }
497
498 #[test]
499 fn test_index_query_results_for_property_store() {
500 let mut context = Context::new();
501 context.index_property::<Person, Age>();
502
503 let existing_value = Age(12);
504 let missing_value = Age(99);
505 let existing_query_parts = [&existing_value as &dyn Any];
506 let missing_query_parts = [&missing_value as &dyn Any];
507
508 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
509 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
510
511 let property_store = context.entity_store.get_property_store::<Person>();
512
513 assert_eq!(
515 property_store.get_index_count_for_query_parts(Age::index_id(), &missing_query_parts,),
516 IndexCountResult::Count(0)
517 );
518 assert_eq!(
519 property_store.get_index_count_for_query_parts(Age::index_id(), &existing_query_parts,),
520 IndexCountResult::Count(2)
521 );
522
523 assert!(matches!(
525 property_store.get_index_set_for_query_parts(Age::index_id(), &missing_query_parts,),
526 IndexSetResult::Empty
527 ));
528 assert!(matches!(
529 property_store.get_index_set_for_query_parts(
530 Age::index_id(),
531 &existing_query_parts,
532 ),
533 IndexSetResult::Set(set) if set.len() == 2
534 ));
535 }
536
537 #[test]
538 fn test_index_query_results_for_property_store_value_count_index() {
539 let mut context = Context::new();
540 context.index_property_counts::<Person, Age>();
541
542 let existing_value = Age(12);
543 let missing_value = Age(99);
544 let existing_query_parts = [&existing_value as &dyn Any];
545 let missing_query_parts = [&missing_value as &dyn Any];
546
547 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
548 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
549
550 let property_store = context.entity_store.get_property_store::<Person>();
551
552 assert_eq!(
554 property_store.get_index_count_for_query_parts(Age::index_id(), &missing_query_parts,),
555 IndexCountResult::Count(0)
556 );
557 assert_eq!(
558 property_store.get_index_count_for_query_parts(Age::index_id(), &existing_query_parts,),
559 IndexCountResult::Count(2)
560 );
561
562 assert!(matches!(
564 property_store.get_index_set_for_query_parts(Age::index_id(), &missing_query_parts,),
565 IndexSetResult::Unsupported
566 ));
567 assert!(matches!(
568 property_store.get_index_set_for_query_parts(Age::index_id(), &existing_query_parts,),
569 IndexSetResult::Unsupported
570 ));
571 }
572
573 #[test]
574 fn test_index_query_results_for_property_store_unindexed() {
575 let mut context = Context::new();
576 let existing_value = Age(12);
577 let missing_value = Age(99);
578 let existing_query_parts = [&existing_value as &dyn Any];
579 let missing_query_parts = [&missing_value as &dyn Any];
580
581 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
582 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
583
584 let property_store = context.entity_store.get_property_store::<Person>();
585
586 assert_eq!(
588 property_store.get_index_count_for_query_parts(Age::index_id(), &missing_query_parts,),
589 IndexCountResult::Unsupported
590 );
591 assert_eq!(
592 property_store.get_index_count_for_query_parts(Age::index_id(), &existing_query_parts,),
593 IndexCountResult::Unsupported
594 );
595
596 assert!(matches!(
598 property_store.get_index_set_for_query_parts(Age::index_id(), &missing_query_parts,),
599 IndexSetResult::Unsupported
600 ));
601 assert!(matches!(
602 property_store.get_index_set_for_query_parts(Age::index_id(), &existing_query_parts,),
603 IndexSetResult::Unsupported
604 ));
605 }
606}