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