1use crate::config::ComponentConfig;
49use core::any::Any;
50use core::fmt;
51use core::marker::PhantomData;
52use cu29_traits::{CuError, CuResult};
53
54use alloc::boxed::Box;
55use alloc::format;
56use alloc::sync::Arc;
57use alloc::vec::Vec;
58
59pub struct Owned<T>(pub T);
61
62pub struct Borrowed<'r, T>(pub &'r T);
65
66enum ResourceEntry {
68 Owned(Box<dyn Any + Send + Sync>),
69 Shared(Arc<dyn Any + Send + Sync>),
70}
71
72impl ResourceEntry {
73 fn as_shared<T: 'static + Send + Sync>(&self) -> Option<&T> {
74 match self {
75 ResourceEntry::Shared(arc) => arc.downcast_ref::<T>(),
76 ResourceEntry::Owned(boxed) => boxed.downcast_ref::<T>(),
77 }
78 }
79
80 fn as_shared_arc<T: 'static + Send + Sync>(&self) -> Option<Arc<T>> {
81 match self {
82 ResourceEntry::Shared(arc) => Arc::downcast::<T>(arc.clone()).ok(),
83 ResourceEntry::Owned(_) => None,
84 }
85 }
86
87 fn into_owned<T: 'static + Send + Sync>(self) -> Option<T> {
88 match self {
89 ResourceEntry::Owned(boxed) => boxed.downcast::<T>().map(|b| *b).ok(),
90 ResourceEntry::Shared(_) => None,
91 }
92 }
93}
94
95#[derive(Copy, Clone, Eq, PartialEq)]
97pub struct ResourceKey<T = ()> {
98 bundle: BundleIndex,
99 index: usize,
100 _boo: PhantomData<fn() -> T>,
101}
102
103impl<T> ResourceKey<T> {
104 pub const fn new(bundle: BundleIndex, index: usize) -> Self {
105 Self {
106 bundle,
107 index,
108 _boo: PhantomData,
109 }
110 }
111
112 pub const fn bundle(&self) -> BundleIndex {
113 self.bundle
114 }
115
116 pub const fn index(&self) -> usize {
117 self.index
118 }
119
120 pub fn typed<U>(self) -> ResourceKey<U> {
122 ResourceKey {
123 bundle: self.bundle,
124 index: self.index,
125 _boo: PhantomData,
126 }
127 }
128}
129
130impl<T> fmt::Debug for ResourceKey<T> {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 f.debug_struct("ResourceKey")
133 .field("bundle", &self.bundle.index())
134 .field("index", &self.index)
135 .finish()
136 }
137}
138
139#[derive(Copy, Clone, Debug, Eq, PartialEq)]
141pub struct BundleIndex(usize);
142
143impl BundleIndex {
144 pub const fn new(index: usize) -> Self {
145 Self(index)
146 }
147
148 pub const fn index(self) -> usize {
149 self.0
150 }
151
152 pub fn key<T, I: ResourceId>(self, id: I) -> ResourceKey<T> {
153 ResourceKey::new(self, id.index())
154 }
155}
156
157pub trait ResourceId: Copy + Eq {
159 const COUNT: usize;
160 fn index(self) -> usize;
161}
162
163pub trait ResourceBundleDecl {
165 type Id: ResourceId;
166}
167
168pub trait NamedResourceBundleDecl: ResourceBundleDecl {
174 const NAMES: &'static [&'static str];
175}
176
177const fn str_eq(left: &str, right: &str) -> bool {
178 let left = left.as_bytes();
179 let right = right.as_bytes();
180 if left.len() != right.len() {
181 return false;
182 }
183
184 let mut idx = 0;
185 while idx < left.len() {
186 if left[idx] != right[idx] {
187 return false;
188 }
189 idx += 1;
190 }
191
192 true
193}
194
195#[doc(hidden)]
199pub const fn resource_index_by_name<B: NamedResourceBundleDecl>(name: &str) -> usize {
200 let mut idx = 0;
201 while idx < B::NAMES.len() {
202 if str_eq(B::NAMES[idx], name) {
203 return idx;
204 }
205 idx += 1;
206 }
207
208 panic!("resource slot name not declared by bundle");
209}
210
211#[derive(Clone, Copy)]
213pub struct ResourceBindingMap<B: Copy + Eq + 'static> {
214 entries: &'static [(B, ResourceKey)],
215}
216
217impl<B: Copy + Eq + 'static> ResourceBindingMap<B> {
218 pub const fn new(entries: &'static [(B, ResourceKey)]) -> Self {
219 Self { entries }
220 }
221
222 pub fn get(&self, binding: B) -> Option<ResourceKey> {
223 self.entries
224 .iter()
225 .find(|(entry_id, _)| *entry_id == binding)
226 .map(|(_, key)| *key)
227 }
228}
229
230pub struct ResourceManager {
232 bundles: Box<[BundleEntries]>,
233}
234
235struct BundleEntries {
236 entries: Box<[Option<ResourceEntry>]>,
237}
238
239impl ResourceManager {
240 pub fn new(bundle_sizes: &[usize]) -> Self {
243 let bundles = bundle_sizes
244 .iter()
245 .map(|size| {
246 let mut entries = Vec::with_capacity(*size);
247 entries.resize_with(*size, || None);
248 BundleEntries {
249 entries: entries.into_boxed_slice(),
250 }
251 })
252 .collect::<Vec<_>>();
253 Self {
254 bundles: bundles.into_boxed_slice(),
255 }
256 }
257
258 fn entry_mut<T>(&mut self, key: ResourceKey<T>) -> CuResult<&mut Option<ResourceEntry>> {
259 let bundle = self
260 .bundles
261 .get_mut(key.bundle.index())
262 .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
263 bundle
264 .entries
265 .get_mut(key.index)
266 .ok_or_else(|| CuError::from("Resource index out of range"))
267 }
268
269 fn entry<T>(&self, key: ResourceKey<T>) -> CuResult<&ResourceEntry> {
270 let bundle = self
271 .bundles
272 .get(key.bundle.index())
273 .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
274 bundle
275 .entries
276 .get(key.index)
277 .and_then(|opt| opt.as_ref())
278 .ok_or_else(|| CuError::from("Resource not found"))
279 }
280
281 fn take_entry<T>(&mut self, key: ResourceKey<T>) -> CuResult<ResourceEntry> {
282 let bundle = self
283 .bundles
284 .get_mut(key.bundle.index())
285 .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
286 let entry = bundle
287 .entries
288 .get_mut(key.index)
289 .and_then(|opt| opt.take())
290 .ok_or_else(|| CuError::from("Resource not found"))?;
291 Ok(entry)
292 }
293
294 pub fn add_owned<T: 'static + Send + Sync>(
296 &mut self,
297 key: ResourceKey<T>,
298 value: T,
299 ) -> CuResult<()> {
300 let entry = self.entry_mut(key)?;
301 if entry.is_some() {
302 return Err(CuError::from("Resource already registered"));
303 }
304 *entry = Some(ResourceEntry::Owned(Box::new(value)));
305 Ok(())
306 }
307
308 pub fn add_shared<T: 'static + Send + Sync>(
311 &mut self,
312 key: ResourceKey<T>,
313 value: Arc<T>,
314 ) -> CuResult<()> {
315 let entry = self.entry_mut(key)?;
316 if entry.is_some() {
317 return Err(CuError::from("Resource already registered"));
318 }
319 *entry = Some(ResourceEntry::Shared(value as Arc<dyn Any + Send + Sync>));
320 Ok(())
321 }
322
323 pub fn borrow<'r, T: 'static + Send + Sync>(
325 &'r self,
326 key: ResourceKey<T>,
327 ) -> CuResult<Borrowed<'r, T>> {
328 let entry = self.entry(key)?;
329 entry.as_shared::<T>().map(Borrowed).ok_or_else(|| {
330 CuError::from(format!(
331 "Borrowing Resource has unexpected type, expected '{}'",
332 core::any::type_name::<T>()
333 ))
334 })
335 }
336
337 pub fn borrow_shared_arc<T: 'static + Send + Sync>(
339 &self,
340 key: ResourceKey<T>,
341 ) -> CuResult<Arc<T>> {
342 let entry = self.entry(key)?;
343 entry.as_shared_arc::<T>().ok_or_else(|| {
344 CuError::from(format!(
345 "Borrow Shared Resource '{}' has unexpected type",
346 core::any::type_name::<T>()
347 ))
348 })
349 }
350
351 pub fn take<T: 'static + Send + Sync>(&mut self, key: ResourceKey<T>) -> CuResult<Owned<T>> {
353 let entry = self.take_entry(key)?;
354 entry.into_owned::<T>().map(Owned).ok_or_else(|| {
355 CuError::from(format!(
356 "Resource {} is not owned or has unexpected type",
357 core::any::type_name::<T>()
358 ))
359 })
360 }
361
362 pub fn add_bundle_prebuilt(
366 &mut self,
367 builder: impl FnOnce(&mut ResourceManager) -> CuResult<()>,
368 ) -> CuResult<()> {
369 builder(self)
370 }
371}
372
373pub trait ResourceBindings<'r>: Sized {
378 type Binding: Copy + Eq + 'static;
379
380 const NAMES: &'static [&'static str] = &[];
382
383 fn from_keys(_manager: &'r mut ResourceManager, _keys: &[ResourceKey]) -> CuResult<Self> {
385 Err(CuError::from(
386 "Resource bindings do not support bundle inputs",
387 ))
388 }
389
390 fn from_bindings(
391 manager: &'r mut ResourceManager,
392 mapping: Option<&ResourceBindingMap<Self::Binding>>,
393 ) -> CuResult<Self>;
394}
395
396impl<'r> ResourceBindings<'r> for () {
397 type Binding = ();
398
399 fn from_keys(_manager: &'r mut ResourceManager, keys: &[ResourceKey]) -> CuResult<Self> {
400 if !keys.is_empty() {
401 return Err(CuError::from("Unexpected resource inputs"));
402 }
403 Ok(())
404 }
405
406 fn from_bindings(
407 _manager: &'r mut ResourceManager,
408 _mapping: Option<&ResourceBindingMap<Self::Binding>>,
409 ) -> CuResult<Self> {
410 Ok(())
411 }
412}
413
414pub trait ResourceBundle: ResourceBundleDecl + Sized {
417 const INPUT_NAMES: &'static [&'static str] = &[];
419
420 fn build(
421 bundle: BundleContext<Self>,
422 config: Option<&ComponentConfig>,
423 manager: &mut ResourceManager,
424 ) -> CuResult<()>;
425}
426
427pub struct BundleContext<B: ResourceBundleDecl> {
429 bundle_index: BundleIndex,
430 bundle_id: &'static str,
431 input_keys: &'static [ResourceKey],
432 _boo: PhantomData<B>,
433}
434
435impl<B: ResourceBundleDecl> BundleContext<B> {
436 pub const fn new(bundle_index: BundleIndex, bundle_id: &'static str) -> Self {
437 Self {
438 bundle_index,
439 bundle_id,
440 input_keys: &[],
441 _boo: PhantomData,
442 }
443 }
444
445 pub const fn bundle_id(&self) -> &'static str {
446 self.bundle_id
447 }
448
449 pub const fn bundle_index(&self) -> BundleIndex {
450 self.bundle_index
451 }
452
453 pub fn key<T>(&self, id: B::Id) -> ResourceKey<T> {
454 ResourceKey::new(self.bundle_index, id.index())
455 }
456
457 pub const fn with_input_keys(mut self, keys: &'static [ResourceKey]) -> Self {
459 self.input_keys = keys;
460 self
461 }
462
463 pub fn inputs<'r, R: ResourceBindings<'r>>(
466 &self,
467 manager: &'r mut ResourceManager,
468 ) -> CuResult<R>
469 where
470 B: ResourceBundle,
471 {
472 if B::INPUT_NAMES != R::NAMES || self.input_keys.len() != R::NAMES.len() {
473 return Err(CuError::from("Resource bundle input declaration mismatch"));
474 }
475 R::from_keys(manager, self.input_keys)
476 }
477}
478
479#[doc(hidden)]
482pub const fn resource_input_keys<const N: usize>(
483 names: &[&str],
484 entries: &[(&str, ResourceKey)],
485) -> [ResourceKey; N] {
486 assert!(
487 names.len() == N && entries.len() == N,
488 "missing resource bundle inputs"
489 );
490 let mut keys = [ResourceKey::new(BundleIndex::new(0), 0); N];
491 let mut seen = [false; N];
492 let mut entry = 0;
493 while entry < entries.len() {
494 let mut index = 0;
495 while index < N && !str_eq(names[index], entries[entry].0) {
496 index += 1;
497 }
498 assert!(index < N, "unknown resource bundle input");
499 assert!(!seen[index], "duplicate resource bundle input");
500 keys[index] = entries[entry].1;
501 seen[index] = true;
502 entry += 1;
503 }
504 keys
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[derive(Copy, Clone, Eq, PartialEq)]
512 #[repr(usize)]
513 enum DummyBundleId {
514 Uart0,
515 I2c1,
516 }
517
518 impl ResourceId for DummyBundleId {
519 const COUNT: usize = 2;
520
521 fn index(self) -> usize {
522 self as usize
523 }
524 }
525
526 struct DummyBundle;
527
528 impl ResourceBundleDecl for DummyBundle {
529 type Id = DummyBundleId;
530 }
531
532 impl NamedResourceBundleDecl for DummyBundle {
533 const NAMES: &'static [&'static str] = &["uart0", "i2c1"];
534 }
535
536 #[test]
537 fn resource_index_by_name_matches_declared_slot_name() {
538 assert_eq!(DummyBundleId::Uart0.index(), 0);
539 assert_eq!(DummyBundleId::I2c1.index(), 1);
540 assert_eq!(resource_index_by_name::<DummyBundle>("uart0"), 0);
541 assert_eq!(resource_index_by_name::<DummyBundle>("i2c1"), 1);
542 }
543}