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