1use alloc::format;
7use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9use core::any::{type_name, TypeId};
10use core::mem::{align_of, needs_drop, size_of};
11
12use crate::component::{ComponentOptions, StorageKind};
13use crate::world::WorldOwner;
14
15#[derive(Clone, Debug, Eq, PartialEq, Hash)]
17pub struct ComponentId {
18 owner: WorldOwner,
19 index: u32,
20}
21
22#[non_exhaustive]
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub enum RegistrationError {
26 TypeConflict {
28 name: String,
30 existing: String,
32 requested: String,
34 },
35 NameConflict {
37 name: String,
39 existing: String,
41 requested: String,
43 },
44 LayoutConflict {
46 name: String,
48 detail: String,
50 },
51 InvalidTag {
53 name: String,
55 detail: String,
57 },
58 UnsupportedStorage {
60 name: String,
62 detail: String,
64 },
65}
66
67struct ComponentEntry {
68 name: String,
69 type_id: Option<TypeId>,
70 is_tag: bool,
71 storage: StorageKind,
72 size: usize,
73 align: usize,
74}
75
76pub(crate) struct ComponentRegistry {
78 entries: Vec<ComponentEntry>,
79}
80
81impl ComponentId {
82 pub(crate) fn new(owner: WorldOwner, index: u32) -> Self {
83 Self { owner, index }
84 }
85
86 pub fn index(&self) -> usize {
88 self.index as usize
89 }
90
91 pub(crate) fn validate_owner(&self, owner: &WorldOwner) -> Result<(), RegistrationError> {
92 if self.owner.same(owner) {
93 Ok(())
94 } else {
95 Err(RegistrationError::LayoutConflict {
96 name: String::from("<component>"),
97 detail: String::from("component id belongs to a different world"),
98 })
99 }
100 }
101}
102
103impl ComponentRegistry {
104 pub fn new() -> Self {
105 Self {
106 entries: Vec::new(),
107 }
108 }
109
110 pub fn len(&self) -> usize {
111 self.entries.len()
112 }
113
114 pub fn register_typed<T: 'static>(
115 &mut self,
116 owner: &WorldOwner,
117 name: Option<&str>,
118 options: ComponentOptions,
119 ) -> Result<ComponentId, RegistrationError> {
120 let name = name.unwrap_or(type_name::<T>()).to_string();
121 if options.is_tag() {
122 Self::validate_typed_tag::<T>(&name)?;
123 }
124 if options.storage() == StorageKind::Table && options.is_tag() {
125 return Err(RegistrationError::UnsupportedStorage {
126 name,
127 detail: String::from("tag components cannot use table storage"),
128 });
129 }
130 self.register_inner(
131 owner,
132 name,
133 Some(TypeId::of::<T>()),
134 options,
135 size_of::<T>(),
136 align_of::<T>(),
137 )
138 }
139
140 pub fn register_untyped(
141 &mut self,
142 owner: &WorldOwner,
143 name: &str,
144 options: ComponentOptions,
145 ) -> Result<ComponentId, RegistrationError> {
146 if !options.is_tag() {
147 return Err(RegistrationError::LayoutConflict {
148 name: name.to_string(),
149 detail: String::from("untyped registration requires tag options"),
150 });
151 }
152 if options.storage() == StorageKind::Table {
153 return Err(RegistrationError::UnsupportedStorage {
154 name: name.to_string(),
155 detail: String::from("untyped tags cannot use table storage"),
156 });
157 }
158 self.register_inner(owner, name.to_string(), None, options, 0, 1)
159 }
160
161 pub fn storage_kind(&self, id: &ComponentId) -> Option<StorageKind> {
162 self.entries.get(id.index()).map(|entry| entry.storage)
163 }
164
165 #[allow(dead_code)]
166 pub fn is_tag(&self, id: &ComponentId) -> Option<bool> {
167 self.entries.get(id.index()).map(|entry| entry.is_tag)
168 }
169
170 pub(crate) fn entry_is_tag(&self, index: usize) -> bool {
171 self.entries.get(index).is_some_and(|entry| entry.is_tag)
172 }
173
174 pub(crate) fn entry_is_table(&self, index: usize) -> bool {
175 self.entries
176 .get(index)
177 .is_some_and(|entry| entry.storage == StorageKind::Table)
178 }
179
180 pub(crate) fn component_name(&self, id: &ComponentId) -> String {
181 self.entries
182 .get(id.index())
183 .map(|entry| entry.name.clone())
184 .unwrap_or_else(|| String::from("<unknown component>"))
185 }
186
187 pub(crate) fn type_id_for_index(&self, index: usize) -> Option<TypeId> {
188 self.entries.get(index).and_then(|entry| entry.type_id)
189 }
190
191 pub(crate) fn index_of_type(&self, type_id: TypeId) -> Option<usize> {
192 self.entries
193 .iter()
194 .position(|entry| entry.type_id == Some(type_id))
195 }
196
197 pub(crate) fn id_of<T: 'static>(&self, owner: &WorldOwner) -> Option<ComponentId> {
198 let type_id = TypeId::of::<T>();
199 self.entries
200 .iter()
201 .position(|entry| entry.type_id == Some(type_id))
202 .map(|index| ComponentId::new(owner.clone(), index as u32))
203 }
204
205 fn register_inner(
206 &mut self,
207 owner: &WorldOwner,
208 name: String,
209 type_id: Option<TypeId>,
210 options: ComponentOptions,
211 size: usize,
212 align: usize,
213 ) -> Result<ComponentId, RegistrationError> {
214 if let Some(existing) = self.find_exact(type_id, &name, options, size, align) {
215 return Ok(ComponentId::new(owner.clone(), existing as u32));
216 }
217
218 if let Some((index, reason)) = self.find_conflict(type_id, &name, options, size, align) {
219 let entry = &self.entries[index];
220 return Err(match reason {
221 ConflictKind::Type => RegistrationError::TypeConflict {
222 name: name.clone(),
223 existing: entry.name.clone(),
224 requested: name,
225 },
226 ConflictKind::Name => RegistrationError::NameConflict {
227 name: name.clone(),
228 existing: entry.name.clone(),
229 requested: name,
230 },
231 ConflictKind::Layout => RegistrationError::LayoutConflict {
232 name: name.clone(),
233 detail: format!(
234 "existing={}:{}x{} {:?} {:?}; requested={}:{}x{} {:?} {:?}",
235 entry.name,
236 entry.size,
237 entry.align,
238 entry.type_id,
239 entry.storage,
240 name,
241 size,
242 align,
243 type_id,
244 options.storage()
245 ),
246 },
247 });
248 }
249
250 let index = self.entries.len() as u32;
251 self.entries.push(ComponentEntry {
252 name,
253 type_id,
254 is_tag: options.is_tag(),
255 storage: options.storage(),
256 size,
257 align,
258 });
259 Ok(ComponentId::new(owner.clone(), index))
260 }
261
262 fn find_exact(
263 &self,
264 type_id: Option<TypeId>,
265 name: &str,
266 options: ComponentOptions,
267 size: usize,
268 align: usize,
269 ) -> Option<usize> {
270 self.entries.iter().position(|entry| {
271 entry.name == name
272 && entry.type_id == type_id
273 && entry.is_tag == options.is_tag()
274 && entry.storage == options.storage()
275 && entry.size == size
276 && entry.align == align
277 })
278 }
279
280 fn find_conflict(
281 &self,
282 type_id: Option<TypeId>,
283 name: &str,
284 options: ComponentOptions,
285 size: usize,
286 align: usize,
287 ) -> Option<(usize, ConflictKind)> {
288 for (index, entry) in self.entries.iter().enumerate() {
289 if entry.type_id == type_id && type_id.is_some() {
290 if entry.size != size || entry.align != align {
291 return Some((index, ConflictKind::Layout));
292 } else if entry.name != name
293 || entry.is_tag != options.is_tag()
294 || entry.storage != options.storage()
295 {
296 return Some((index, ConflictKind::Type));
297 }
298 } else if entry.name == name
299 && (entry.type_id != type_id
300 || entry.is_tag != options.is_tag()
301 || entry.storage != options.storage()
302 || entry.size != size
303 || entry.align != align)
304 {
305 return Some((index, ConflictKind::Name));
306 }
307 }
308 None
309 }
310
311 #[cfg(test)]
312 pub(crate) fn find_conflict_for_test(
313 &self,
314 type_id: Option<TypeId>,
315 name: &str,
316 options: ComponentOptions,
317 size: usize,
318 align: usize,
319 ) -> bool {
320 self.find_conflict(type_id, name, options, size, align)
321 .is_some()
322 }
323
324 fn validate_typed_tag<T: 'static>(name: &str) -> Result<(), RegistrationError> {
325 if size_of::<T>() != 0 || needs_drop::<T>() {
326 return Err(RegistrationError::InvalidTag {
327 name: name.to_string(),
328 detail: String::from("typed tag components must be zero-sized and non-dropping"),
329 });
330 }
331 Ok(())
332 }
333}
334
335enum ConflictKind {
336 Type,
337 Name,
338 Layout,
339}
340
341impl Default for ComponentRegistry {
342 fn default() -> Self {
343 Self::new()
344 }
345}
346
347#[cfg(feature = "std")]
348impl core::fmt::Display for RegistrationError {
349 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
350 match self {
351 Self::TypeConflict {
352 name,
353 existing,
354 requested,
355 } => write!(
356 f,
357 "component type conflict for {name}: existing={existing}, requested={requested}"
358 ),
359 Self::NameConflict {
360 name,
361 existing,
362 requested,
363 } => write!(
364 f,
365 "component name conflict for {name}: existing={existing}, requested={requested}"
366 ),
367 Self::LayoutConflict { name, detail } => {
368 write!(f, "component layout conflict for {name}: {detail}")
369 }
370 Self::InvalidTag { name, detail } => {
371 write!(f, "invalid tag component {name}: {detail}")
372 }
373 Self::UnsupportedStorage { name, detail } => {
374 write!(f, "unsupported storage for {name}: {detail}")
375 }
376 }
377 }
378}
379
380#[cfg(feature = "std")]
381impl std::error::Error for RegistrationError {}
382
383#[cfg(test)]
384mod tests;
385
386#[cfg(test)]
387mod default_tests {
388 use super::ComponentRegistry;
389
390 #[test]
391 fn default_registry_is_empty() {
392 assert_eq!(ComponentRegistry::default().len(), 0);
393 }
394}