ixa/entity/
multi_property.rs1#![allow(dead_code)]
2use std::any::TypeId;
31use std::sync::{LazyLock, Mutex};
32
33#[cfg(feature = "profiling")]
34use crate::entity::property_store::registered_property_name;
35use crate::entity::Entity;
36use crate::hashing::{one_shot_128, HashMap, HashValueType};
37use crate::warn;
38
39#[derive(Clone, Copy)]
41struct MultiPropertyId {
42 pub id: usize,
43 pub name: &'static str,
44}
45
46#[cfg(feature = "profiling")]
51#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
52pub(crate) struct QueryIdentityId(usize);
53
54#[cfg(feature = "profiling")]
55impl QueryIdentityId {
56 pub(crate) const fn from_raw(raw: usize) -> Self {
57 Self(raw)
58 }
59}
60
61#[derive(Clone, Copy, Default)]
62struct QueryShapeRecord {
63 representative: Option<MultiPropertyId>,
64 #[cfg(feature = "profiling")]
65 identity: Option<QueryIdentityId>,
66}
67
68#[derive(Default)]
69struct QueryShapeRegistry {
70 shapes: HashMap<(usize, HashValueType), QueryShapeRecord>,
71 #[cfg(feature = "profiling")]
72 labels: Vec<&'static str>,
73}
74
75enum PreMainDiagnostic {
76 Warning(String),
77}
78
79static QUERY_SHAPE_REGISTRY: LazyLock<Mutex<QueryShapeRegistry>> =
85 LazyLock::new(|| Mutex::new(QueryShapeRegistry::default()));
86
87static MULTI_PROPERTY_TYPE_ID_MAP: LazyLock<Mutex<HashMap<(usize, TypeId), MultiPropertyId>>> =
93 LazyLock::new(|| Mutex::new(HashMap::default()));
94
95static PRE_MAIN_DIAGNOSTICS: LazyLock<Mutex<Vec<PreMainDiagnostic>>> =
101 LazyLock::new(|| Mutex::new(Vec::new()));
102
103fn query_shape_key(entity_id: usize, type_ids: &[TypeId]) -> (usize, HashValueType) {
104 (entity_id, one_shot_128(&type_ids))
105}
106
107#[must_use]
109pub fn type_ids_to_multi_property_id(entity_id: usize, type_ids: &[TypeId]) -> Option<usize> {
110 let key = query_shape_key(entity_id, type_ids);
111 QUERY_SHAPE_REGISTRY
112 .lock()
113 .unwrap()
114 .shapes
115 .get(&key)
116 .and_then(|record| record.representative)
117 .map(|representative| representative.id)
118}
119
120pub(crate) fn multi_property_id_for_property_type_id(
123 entity_id: usize,
124 property_type_id: TypeId,
125) -> Option<(usize, &'static str)> {
126 MULTI_PROPERTY_TYPE_ID_MAP
127 .lock()
128 .unwrap()
129 .get(&(entity_id, property_type_id))
130 .map(|value| (value.id, value.name))
131}
132
133pub fn register_type_ids_to_multi_property_id(
138 entity_id: usize,
139 type_ids: &[TypeId],
140 property_type_id: TypeId,
141 id: usize,
142 name: &'static str,
143) -> Option<(usize, &'static str)> {
144 let key = query_shape_key(entity_id, type_ids);
145 let value = MultiPropertyId { id, name };
146 let existing = {
147 let mut registry = QUERY_SHAPE_REGISTRY.lock().unwrap();
148 let record = registry.shapes.entry(key).or_default();
149 match record.representative {
150 Some(existing) => Some(existing),
151 None => {
152 record.representative = Some(value);
153 None
154 }
155 }
156 };
157
158 let representative = existing.unwrap_or(value);
159 MULTI_PROPERTY_TYPE_ID_MAP
160 .lock()
161 .unwrap()
162 .entry((entity_id, property_type_id))
163 .or_insert(representative);
164
165 existing.map(|existing| (existing.id, existing.name))
166}
167
168#[cfg(feature = "profiling")]
169fn build_query_identity_label<E: Entity>(type_ids: &[TypeId]) -> String {
170 if type_ids.is_empty() {
171 return format!("{}: All", E::name());
172 }
173
174 let mut names = type_ids
175 .iter()
176 .map(|&type_id| registered_property_name(E::id(), type_id))
177 .collect::<Vec<_>>();
178 names.sort_unstable();
179
180 format!("{}: ({})", E::name(), names.join(", "))
181}
182
183#[cfg(feature = "profiling")]
185pub(crate) fn intern_query_identity<E: Entity>(type_ids: &[TypeId]) -> QueryIdentityId {
186 let key = query_shape_key(E::id(), type_ids);
187
188 {
189 let registry = QUERY_SHAPE_REGISTRY.lock().unwrap();
190 if let Some(identity) = registry.shapes.get(&key).and_then(|record| record.identity) {
191 return identity;
192 }
193 }
194
195 let label = build_query_identity_label::<E>(type_ids);
198
199 let mut registry = QUERY_SHAPE_REGISTRY.lock().unwrap();
200 if let Some(identity) = registry.shapes.get(&key).and_then(|record| record.identity) {
201 return identity;
202 }
203
204 let identity = QueryIdentityId(registry.labels.len());
205 registry.labels.push(Box::leak(label.into_boxed_str()));
206 registry.shapes.entry(key).or_default().identity = Some(identity);
207 identity
208}
209
210#[doc(hidden)]
215pub fn intern_query_identity_raw<E: Entity>(type_ids: &[TypeId]) -> usize {
216 #[cfg(feature = "profiling")]
217 {
218 intern_query_identity::<E>(type_ids).0
219 }
220
221 #[cfg(not(feature = "profiling"))]
222 {
223 let _ = type_ids;
224 panic!("query profiling identities require the profiling feature")
225 }
226}
227
228#[cfg(feature = "profiling")]
230pub(crate) fn query_identity_label(identity: QueryIdentityId) -> &'static str {
231 QUERY_SHAPE_REGISTRY.lock().unwrap().labels[identity.0]
232}
233
234#[cfg(all(test, feature = "profiling"))]
235pub(crate) fn test_query_identity(label: &'static str) -> QueryIdentityId {
236 let mut registry = QUERY_SHAPE_REGISTRY.lock().unwrap();
237 let identity = QueryIdentityId(registry.labels.len());
238 registry.labels.push(label);
239 identity
240}
241
242#[doc(hidden)]
243pub fn record_pre_main_warning(message: String) {
244 PRE_MAIN_DIAGNOSTICS
245 .lock()
246 .unwrap()
247 .push(PreMainDiagnostic::Warning(message));
248}
249
250pub(crate) fn emit_pre_main_diagnostics() {
251 for diagnostic in PRE_MAIN_DIAGNOSTICS.lock().unwrap().drain(..) {
252 match diagnostic {
253 PreMainDiagnostic::Warning(message) => warn!("{message}"),
254 }
255 }
256}
257
258const fn make_indices<const N: usize>() -> [usize; N] {
265 let mut arr = [0; N];
266 let mut i = 0;
267 while i < N {
268 arr[i] = i;
269 i += 1;
270 }
271 arr
272}
273
274#[must_use]
280pub fn static_sorted_indices<T: Ord, const N: usize>(keys: &[T; N]) -> [usize; N] {
281 let mut indices = make_indices::<N>();
282 indices.sort_unstable_by_key(|&i| &keys[i]);
283 indices
284}
285
286pub fn static_apply_reordering<T: Copy, const N: usize>(values: &mut [T; N], indices: &[usize; N]) {
293 let tmp_values: [T; N] = *values;
294 for (old_index, new_index) in indices.iter().enumerate() {
295 values[old_index] = tmp_values[*new_index];
296 }
297}
298
299#[must_use]
304pub fn static_inverse_indices<const N: usize>(indices: &[usize; N]) -> [usize; N] {
305 let mut inverse = [0; N];
306 for (sorted_position, &original_position) in indices.iter().enumerate() {
307 inverse[original_position] = sorted_position;
308 }
309 inverse
310}
311
312pub fn static_reorder_by_keys<T: Ord + Copy, U: Copy, const N: usize>(
317 keys: &[T; N],
318 values: &mut [U; N],
319) {
320 let indices: [usize; N] = static_sorted_indices(keys);
321 static_apply_reordering(values, &indices);
322}