Skip to main content

cu29_runtime/
resource.rs

1//! Resource descriptors and utilities to hand resources to tasks and bridges.
2//! User view: in `copperconfig.ron`, map the binding names your tasks/bridges
3//! expect to the resources exported by your board bundle. Exclusive things
4//! (like a serial port) should be bound once; shared things (like a telemetry
5//! bus `Arc`) can be bound to multiple consumers.
6//!
7//! ```ron
8//! (
9//!     resources: [ ( id: "board", provider: "board_crate::BoardBundle" ) ],
10//!     bridges: [
11//!         ( id: "crsf", type: "cu_crsf::CrsfBridge<SerialPort, SerialError>",
12//!           resources: { serial: "board.uart0" }
13//!         ),
14//!     ],
15//!     tasks: [
16//!         ( id: "telemetry", type: "app::TelemetryTask",
17//!           resources: { bus: "board.telemetry_bus" }
18//!         ),
19//!     ],
20//! )
21//! ```
22//!
23//! Writing your own task/bridge? Add a small `Resources` struct and implement
24//! `ResourceBindings` to pull the names you declared:
25//! ```rust,ignore
26//! pub struct TelemetryResources<'r> { pub bus: Borrowed<'r, TelemetryBus> }
27//! impl<'r> ResourceBindings<'r> for TelemetryResources<'r> {
28//!     type Binding = Binding;
29//!     fn from_bindings(mgr: &'r mut ResourceManager, map: Option<&ResourceBindingMap<Self::Binding>>) -> CuResult<Self> {
30//!         let key = map.expect("bus binding").get(Binding::Bus).expect("bus").typed();
31//!         Ok(Self { bus: mgr.borrow(key)? })
32//!     }
33//! }
34//! pub fn new(_cfg: Option<&ComponentConfig>, res: TelemetryResources<'_>) -> CuResult<Self> {
35//!     Ok(Self { bus: res.bus })
36//! }
37//! ```
38//! Or use the `resources!` macro. `Shared<T>` bindings clone the registered
39//! `Arc<T>` so tasks can keep a shared handle without borrowing from the
40//! manager for their full lifetime. `Borrowed<T>` bindings borrow from the
41//! manager directly.
42//! Otherwise, use config to point to the right board resource and you're done.
43
44use crate::config::ComponentConfig;
45use core::any::Any;
46use core::fmt;
47use core::marker::PhantomData;
48use cu29_traits::{CuError, CuResult};
49
50use alloc::boxed::Box;
51use alloc::format;
52use alloc::sync::Arc;
53use alloc::vec::Vec;
54
55/// Lightweight wrapper used when a task needs to take ownership of a resource.
56pub struct Owned<T>(pub T);
57
58/// Wrapper used when a task needs to borrow a resource that remains managed by
59/// the `ResourceManager`.
60pub struct Borrowed<'r, T>(pub &'r T);
61
62/// A resource can be exclusive (most common case) or shared.
63enum ResourceEntry {
64    Owned(Box<dyn Any + Send + Sync>),
65    Shared(Arc<dyn Any + Send + Sync>),
66}
67
68impl ResourceEntry {
69    fn as_shared<T: 'static + Send + Sync>(&self) -> Option<&T> {
70        match self {
71            ResourceEntry::Shared(arc) => arc.downcast_ref::<T>(),
72            ResourceEntry::Owned(boxed) => boxed.downcast_ref::<T>(),
73        }
74    }
75
76    fn as_shared_arc<T: 'static + Send + Sync>(&self) -> Option<Arc<T>> {
77        match self {
78            ResourceEntry::Shared(arc) => Arc::downcast::<T>(arc.clone()).ok(),
79            ResourceEntry::Owned(_) => None,
80        }
81    }
82
83    fn into_owned<T: 'static + Send + Sync>(self) -> Option<T> {
84        match self {
85            ResourceEntry::Owned(boxed) => boxed.downcast::<T>().map(|b| *b).ok(),
86            ResourceEntry::Shared(_) => None,
87        }
88    }
89}
90
91/// Typed identifier for a resource entry.
92#[derive(Copy, Clone, Eq, PartialEq)]
93pub struct ResourceKey<T = ()> {
94    bundle: BundleIndex,
95    index: usize,
96    _boo: PhantomData<fn() -> T>,
97}
98
99impl<T> ResourceKey<T> {
100    pub const fn new(bundle: BundleIndex, index: usize) -> Self {
101        Self {
102            bundle,
103            index,
104            _boo: PhantomData,
105        }
106    }
107
108    pub const fn bundle(&self) -> BundleIndex {
109        self.bundle
110    }
111
112    pub const fn index(&self) -> usize {
113        self.index
114    }
115
116    /// Reinterpret this key as pointing to a concrete resource type.
117    pub fn typed<U>(self) -> ResourceKey<U> {
118        ResourceKey {
119            bundle: self.bundle,
120            index: self.index,
121            _boo: PhantomData,
122        }
123    }
124}
125
126impl<T> fmt::Debug for ResourceKey<T> {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.debug_struct("ResourceKey")
129            .field("bundle", &self.bundle.index())
130            .field("index", &self.index)
131            .finish()
132    }
133}
134
135/// Index identifying a resource bundle in the active mission.
136#[derive(Copy, Clone, Debug, Eq, PartialEq)]
137pub struct BundleIndex(usize);
138
139impl BundleIndex {
140    pub const fn new(index: usize) -> Self {
141        Self(index)
142    }
143
144    pub const fn index(self) -> usize {
145        self.0
146    }
147
148    pub fn key<T, I: ResourceId>(self, id: I) -> ResourceKey<T> {
149        ResourceKey::new(self, id.index())
150    }
151}
152
153/// Trait implemented by resource id enums generated by `bundle_resources!`.
154pub trait ResourceId: Copy + Eq {
155    const COUNT: usize;
156    fn index(self) -> usize;
157}
158
159/// Trait implemented by bundle providers to declare their resource id enum.
160pub trait ResourceBundleDecl {
161    type Id: ResourceId;
162}
163
164/// Optional name metadata for resource bundles.
165///
166/// Bundles created via `bundle_resources!` implement this automatically. The
167/// derive macro uses these canonical slot names to resolve `bundle.resource`
168/// bindings without guessing enum variant casing from config strings.
169pub trait NamedResourceBundleDecl: ResourceBundleDecl {
170    const NAMES: &'static [&'static str];
171}
172
173const fn str_eq(left: &str, right: &str) -> bool {
174    let left = left.as_bytes();
175    let right = right.as_bytes();
176    if left.len() != right.len() {
177        return false;
178    }
179
180    let mut idx = 0;
181    while idx < left.len() {
182        if left[idx] != right[idx] {
183            return false;
184        }
185        idx += 1;
186    }
187
188    true
189}
190
191/// Resolve a bundle slot name to its resource index.
192///
193/// This is a `const fn` so generated resource binding tables can stay static.
194#[doc(hidden)]
195pub const fn resource_index_by_name<B: NamedResourceBundleDecl>(name: &str) -> usize {
196    let mut idx = 0;
197    while idx < B::NAMES.len() {
198        if str_eq(B::NAMES[idx], name) {
199            return idx;
200        }
201        idx += 1;
202    }
203
204    panic!("resource slot name not declared by bundle");
205}
206
207/// Static mapping between user-defined binding ids and resource keys.
208#[derive(Clone, Copy)]
209pub struct ResourceBindingMap<B: Copy + Eq + 'static> {
210    entries: &'static [(B, ResourceKey)],
211}
212
213impl<B: Copy + Eq + 'static> ResourceBindingMap<B> {
214    pub const fn new(entries: &'static [(B, ResourceKey)]) -> Self {
215        Self { entries }
216    }
217
218    pub fn get(&self, binding: B) -> Option<ResourceKey> {
219        self.entries
220            .iter()
221            .find(|(entry_id, _)| *entry_id == binding)
222            .map(|(_, key)| *key)
223    }
224}
225
226/// Manages the concrete resources available to tasks and bridges.
227pub struct ResourceManager {
228    bundles: Box<[BundleEntries]>,
229}
230
231struct BundleEntries {
232    entries: Box<[Option<ResourceEntry>]>,
233}
234
235impl ResourceManager {
236    /// Creates a new manager sized for the number of resources generated for
237    /// each bundle in the current mission.
238    pub fn new(bundle_sizes: &[usize]) -> Self {
239        let bundles = bundle_sizes
240            .iter()
241            .map(|size| {
242                let mut entries = Vec::with_capacity(*size);
243                entries.resize_with(*size, || None);
244                BundleEntries {
245                    entries: entries.into_boxed_slice(),
246                }
247            })
248            .collect::<Vec<_>>();
249        Self {
250            bundles: bundles.into_boxed_slice(),
251        }
252    }
253
254    fn entry_mut<T>(&mut self, key: ResourceKey<T>) -> CuResult<&mut Option<ResourceEntry>> {
255        let bundle = self
256            .bundles
257            .get_mut(key.bundle.index())
258            .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
259        bundle
260            .entries
261            .get_mut(key.index)
262            .ok_or_else(|| CuError::from("Resource index out of range"))
263    }
264
265    fn entry<T>(&self, key: ResourceKey<T>) -> CuResult<&ResourceEntry> {
266        let bundle = self
267            .bundles
268            .get(key.bundle.index())
269            .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
270        bundle
271            .entries
272            .get(key.index)
273            .and_then(|opt| opt.as_ref())
274            .ok_or_else(|| CuError::from("Resource not found"))
275    }
276
277    fn take_entry<T>(&mut self, key: ResourceKey<T>) -> CuResult<ResourceEntry> {
278        let bundle = self
279            .bundles
280            .get_mut(key.bundle.index())
281            .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
282        let entry = bundle
283            .entries
284            .get_mut(key.index)
285            .and_then(|opt| opt.take())
286            .ok_or_else(|| CuError::from("Resource not found"))?;
287        Ok(entry)
288    }
289
290    /// Register an owned resource in the slot identified by `key`.
291    pub fn add_owned<T: 'static + Send + Sync>(
292        &mut self,
293        key: ResourceKey<T>,
294        value: T,
295    ) -> CuResult<()> {
296        let entry = self.entry_mut(key)?;
297        if entry.is_some() {
298            return Err(CuError::from("Resource already registered"));
299        }
300        *entry = Some(ResourceEntry::Owned(Box::new(value)));
301        Ok(())
302    }
303
304    /// Register a shared (borrowed) resource. Callers keep an `Arc` while tasks
305    /// receive references.
306    pub fn add_shared<T: 'static + Send + Sync>(
307        &mut self,
308        key: ResourceKey<T>,
309        value: Arc<T>,
310    ) -> CuResult<()> {
311        let entry = self.entry_mut(key)?;
312        if entry.is_some() {
313            return Err(CuError::from("Resource already registered"));
314        }
315        *entry = Some(ResourceEntry::Shared(value as Arc<dyn Any + Send + Sync>));
316        Ok(())
317    }
318
319    /// Borrow a shared resource by key.
320    pub fn borrow<'r, T: 'static + Send + Sync>(
321        &'r self,
322        key: ResourceKey<T>,
323    ) -> CuResult<Borrowed<'r, T>> {
324        let entry = self.entry(key)?;
325        entry.as_shared::<T>().map(Borrowed).ok_or_else(|| {
326            CuError::from(format!(
327                "Borrowing Resource has unexpected type, expected '{}'",
328                core::any::type_name::<T>()
329            ))
330        })
331    }
332
333    /// Borrow a shared `Arc`-backed resource by key, cloning the `Arc` for the caller.
334    pub fn borrow_shared_arc<T: 'static + Send + Sync>(
335        &self,
336        key: ResourceKey<T>,
337    ) -> CuResult<Arc<T>> {
338        let entry = self.entry(key)?;
339        entry.as_shared_arc::<T>().ok_or_else(|| {
340            CuError::from(format!(
341                "Borrow Shared Resource '{}' has unexpected type",
342                core::any::type_name::<T>()
343            ))
344        })
345    }
346
347    /// Take ownership of a resource by key.
348    pub fn take<T: 'static + Send + Sync>(&mut self, key: ResourceKey<T>) -> CuResult<Owned<T>> {
349        let entry = self.take_entry(key)?;
350        entry.into_owned::<T>().map(Owned).ok_or_else(|| {
351            CuError::from(format!(
352                "Resource {} is not owned or has unexpected type",
353                core::any::type_name::<T>()
354            ))
355        })
356    }
357
358    /// Insert a prebuilt bundle by running a caller-supplied function. This is
359    /// the escape hatch for resources that must be constructed in application
360    /// code (for example, owning handles to embedded peripherals).
361    pub fn add_bundle_prebuilt(
362        &mut self,
363        builder: impl FnOnce(&mut ResourceManager) -> CuResult<()>,
364    ) -> CuResult<()> {
365        builder(self)
366    }
367}
368
369/// Trait implemented by resource binding structs passed to task/bridge
370/// constructors. Implementors pull the concrete resources they need from the
371/// `ResourceManager`, using the symbolic mapping provided in the Copper config
372/// (`resources: { name: "bundle.resource" }`).
373pub trait ResourceBindings<'r>: Sized {
374    type Binding: Copy + Eq + 'static;
375
376    fn from_bindings(
377        manager: &'r mut ResourceManager,
378        mapping: Option<&ResourceBindingMap<Self::Binding>>,
379    ) -> CuResult<Self>;
380}
381
382impl<'r> ResourceBindings<'r> for () {
383    type Binding = ();
384
385    fn from_bindings(
386        _manager: &'r mut ResourceManager,
387        _mapping: Option<&ResourceBindingMap<Self::Binding>>,
388    ) -> CuResult<Self> {
389        Ok(())
390    }
391}
392
393/// Bundle providers implement this trait to populate the `ResourceManager` with
394/// concrete resources for a given bundle id.
395pub trait ResourceBundle: ResourceBundleDecl + Sized {
396    fn build(
397        bundle: BundleContext<Self>,
398        config: Option<&ComponentConfig>,
399        manager: &mut ResourceManager,
400    ) -> CuResult<()>;
401}
402
403/// Context passed to bundle providers when building resources.
404pub struct BundleContext<B: ResourceBundleDecl> {
405    bundle_index: BundleIndex,
406    bundle_id: &'static str,
407    _boo: PhantomData<B>,
408}
409
410impl<B: ResourceBundleDecl> BundleContext<B> {
411    pub const fn new(bundle_index: BundleIndex, bundle_id: &'static str) -> Self {
412        Self {
413            bundle_index,
414            bundle_id,
415            _boo: PhantomData,
416        }
417    }
418
419    pub const fn bundle_id(&self) -> &'static str {
420        self.bundle_id
421    }
422
423    pub const fn bundle_index(&self) -> BundleIndex {
424        self.bundle_index
425    }
426
427    pub fn key<T>(&self, id: B::Id) -> ResourceKey<T> {
428        ResourceKey::new(self.bundle_index, id.index())
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    #[derive(Copy, Clone, Eq, PartialEq)]
437    #[repr(usize)]
438    enum DummyBundleId {
439        Uart0,
440        I2c1,
441    }
442
443    impl ResourceId for DummyBundleId {
444        const COUNT: usize = 2;
445
446        fn index(self) -> usize {
447            self as usize
448        }
449    }
450
451    struct DummyBundle;
452
453    impl ResourceBundleDecl for DummyBundle {
454        type Id = DummyBundleId;
455    }
456
457    impl NamedResourceBundleDecl for DummyBundle {
458        const NAMES: &'static [&'static str] = &["uart0", "i2c1"];
459    }
460
461    #[test]
462    fn resource_index_by_name_matches_declared_slot_name() {
463        assert_eq!(DummyBundleId::Uart0.index(), 0);
464        assert_eq!(DummyBundleId::I2c1.index(), 1);
465        assert_eq!(resource_index_by_name::<DummyBundle>("uart0"), 0);
466        assert_eq!(resource_index_by_name::<DummyBundle>("i2c1"), 1);
467    }
468}