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//! Otherwise, use config to point to the right board resource and you're done.
39
40use 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::sync::Arc;
48use alloc::vec::Vec;
49
50/// Lightweight wrapper used when a task needs to take ownership of a resource.
51pub struct Owned<T>(pub T);
52
53/// Wrapper used when a task needs to borrow a resource that remains managed by
54/// the `ResourceManager`.
55pub struct Borrowed<'r, T>(pub &'r T);
56
57/// A resource can be exclusive (most common case) or shared.
58enum ResourceEntry {
59    Owned(Box<dyn Any + Send + Sync>),
60    Shared(Arc<dyn Any + Send + Sync>),
61}
62
63impl ResourceEntry {
64    fn as_shared<T: 'static + Send + Sync>(&self) -> Option<&T> {
65        match self {
66            ResourceEntry::Shared(arc) => arc.downcast_ref::<T>(),
67            ResourceEntry::Owned(boxed) => boxed.downcast_ref::<T>(),
68        }
69    }
70
71    #[cfg(feature = "std")]
72    fn as_shared_arc<T: 'static + Send + Sync>(&self) -> Option<Arc<T>> {
73        match self {
74            ResourceEntry::Shared(arc) => Arc::downcast::<T>(arc.clone()).ok(),
75            ResourceEntry::Owned(_) => None,
76        }
77    }
78
79    fn into_owned<T: 'static + Send + Sync>(self) -> Option<T> {
80        match self {
81            ResourceEntry::Owned(boxed) => boxed.downcast::<T>().map(|b| *b).ok(),
82            ResourceEntry::Shared(_) => None,
83        }
84    }
85}
86
87/// Typed identifier for a resource entry.
88#[derive(Copy, Clone, Eq, PartialEq)]
89pub struct ResourceKey<T = ()> {
90    bundle: BundleIndex,
91    index: usize,
92    _boo: PhantomData<fn() -> T>,
93}
94
95impl<T> ResourceKey<T> {
96    pub const fn new(bundle: BundleIndex, index: usize) -> Self {
97        Self {
98            bundle,
99            index,
100            _boo: PhantomData,
101        }
102    }
103
104    pub const fn bundle(&self) -> BundleIndex {
105        self.bundle
106    }
107
108    pub const fn index(&self) -> usize {
109        self.index
110    }
111
112    /// Reinterpret this key as pointing to a concrete resource type.
113    pub fn typed<U>(self) -> ResourceKey<U> {
114        ResourceKey {
115            bundle: self.bundle,
116            index: self.index,
117            _boo: PhantomData,
118        }
119    }
120}
121
122impl<T> fmt::Debug for ResourceKey<T> {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.debug_struct("ResourceKey")
125            .field("bundle", &self.bundle.index())
126            .field("index", &self.index)
127            .finish()
128    }
129}
130
131/// Index identifying a resource bundle in the active mission.
132#[derive(Copy, Clone, Debug, Eq, PartialEq)]
133pub struct BundleIndex(usize);
134
135impl BundleIndex {
136    pub const fn new(index: usize) -> Self {
137        Self(index)
138    }
139
140    pub const fn index(self) -> usize {
141        self.0
142    }
143
144    pub fn key<T, I: ResourceId>(self, id: I) -> ResourceKey<T> {
145        ResourceKey::new(self, id.index())
146    }
147}
148
149/// Trait implemented by resource id enums generated by `bundle_resources!`.
150pub trait ResourceId: Copy + Eq {
151    const COUNT: usize;
152    fn index(self) -> usize;
153}
154
155/// Trait implemented by bundle providers to declare their resource id enum.
156pub trait ResourceBundleDecl {
157    type Id: ResourceId;
158}
159
160/// Static mapping between user-defined binding ids and resource keys.
161#[derive(Clone, Copy)]
162pub struct ResourceBindingMap<B: Copy + Eq + 'static> {
163    entries: &'static [(B, ResourceKey)],
164}
165
166impl<B: Copy + Eq + 'static> ResourceBindingMap<B> {
167    pub const fn new(entries: &'static [(B, ResourceKey)]) -> Self {
168        Self { entries }
169    }
170
171    pub fn get(&self, binding: B) -> Option<ResourceKey> {
172        self.entries
173            .iter()
174            .find(|(entry_id, _)| *entry_id == binding)
175            .map(|(_, key)| *key)
176    }
177}
178
179/// Manages the concrete resources available to tasks and bridges.
180pub struct ResourceManager {
181    bundles: Box<[BundleEntries]>,
182}
183
184struct BundleEntries {
185    entries: Box<[Option<ResourceEntry>]>,
186}
187
188impl ResourceManager {
189    /// Creates a new manager sized for the number of resources generated for
190    /// each bundle in the current mission.
191    pub fn new(bundle_sizes: &[usize]) -> Self {
192        let bundles = bundle_sizes
193            .iter()
194            .map(|size| {
195                let mut entries = Vec::with_capacity(*size);
196                entries.resize_with(*size, || None);
197                BundleEntries {
198                    entries: entries.into_boxed_slice(),
199                }
200            })
201            .collect::<Vec<_>>();
202        Self {
203            bundles: bundles.into_boxed_slice(),
204        }
205    }
206
207    fn entry_mut<T>(&mut self, key: ResourceKey<T>) -> CuResult<&mut Option<ResourceEntry>> {
208        let bundle = self
209            .bundles
210            .get_mut(key.bundle.index())
211            .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
212        bundle
213            .entries
214            .get_mut(key.index)
215            .ok_or_else(|| CuError::from("Resource index out of range"))
216    }
217
218    fn entry<T>(&self, key: ResourceKey<T>) -> CuResult<&ResourceEntry> {
219        let bundle = self
220            .bundles
221            .get(key.bundle.index())
222            .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
223        bundle
224            .entries
225            .get(key.index)
226            .and_then(|opt| opt.as_ref())
227            .ok_or_else(|| CuError::from("Resource not found"))
228    }
229
230    fn take_entry<T>(&mut self, key: ResourceKey<T>) -> CuResult<ResourceEntry> {
231        let bundle = self
232            .bundles
233            .get_mut(key.bundle.index())
234            .ok_or_else(|| CuError::from("Resource bundle index out of range"))?;
235        let entry = bundle
236            .entries
237            .get_mut(key.index)
238            .and_then(|opt| opt.take())
239            .ok_or_else(|| CuError::from("Resource not found"))?;
240        Ok(entry)
241    }
242
243    /// Register an owned resource in the slot identified by `key`.
244    pub fn add_owned<T: 'static + Send + Sync>(
245        &mut self,
246        key: ResourceKey<T>,
247        value: T,
248    ) -> CuResult<()> {
249        let entry = self.entry_mut(key)?;
250        if entry.is_some() {
251            return Err(CuError::from("Resource already registered"));
252        }
253        *entry = Some(ResourceEntry::Owned(Box::new(value)));
254        Ok(())
255    }
256
257    /// Register a shared (borrowed) resource. Callers keep an `Arc` while tasks
258    /// receive references.
259    pub fn add_shared<T: 'static + Send + Sync>(
260        &mut self,
261        key: ResourceKey<T>,
262        value: Arc<T>,
263    ) -> CuResult<()> {
264        let entry = self.entry_mut(key)?;
265        if entry.is_some() {
266            return Err(CuError::from("Resource already registered"));
267        }
268        *entry = Some(ResourceEntry::Shared(value as Arc<dyn Any + Send + Sync>));
269        Ok(())
270    }
271
272    /// Borrow a shared resource by key.
273    pub fn borrow<'r, T: 'static + Send + Sync>(
274        &'r self,
275        key: ResourceKey<T>,
276    ) -> CuResult<Borrowed<'r, T>> {
277        let entry = self.entry(key)?;
278        entry
279            .as_shared::<T>()
280            .map(Borrowed)
281            .ok_or_else(|| CuError::from("Resource has unexpected type"))
282    }
283
284    /// Borrow a shared `Arc`-backed resource by key, cloning the `Arc` for the caller.
285    #[cfg(feature = "std")]
286    pub fn borrow_shared_arc<T: 'static + Send + Sync>(
287        &self,
288        key: ResourceKey<T>,
289    ) -> CuResult<Arc<T>> {
290        let entry = self.entry(key)?;
291        entry
292            .as_shared_arc::<T>()
293            .ok_or_else(|| CuError::from("Resource has unexpected type"))
294    }
295
296    /// Take ownership of a resource by key.
297    pub fn take<T: 'static + Send + Sync>(&mut self, key: ResourceKey<T>) -> CuResult<Owned<T>> {
298        let entry = self.take_entry(key)?;
299        entry
300            .into_owned::<T>()
301            .map(Owned)
302            .ok_or_else(|| CuError::from("Resource is not owned or has unexpected type"))
303    }
304
305    /// Insert a prebuilt bundle by running a caller-supplied function. This is
306    /// the escape hatch for resources that must be constructed in application
307    /// code (for example, owning handles to embedded peripherals).
308    pub fn add_bundle_prebuilt(
309        &mut self,
310        builder: impl FnOnce(&mut ResourceManager) -> CuResult<()>,
311    ) -> CuResult<()> {
312        builder(self)
313    }
314}
315
316/// Trait implemented by resource binding structs passed to task/bridge
317/// constructors. Implementors pull the concrete resources they need from the
318/// `ResourceManager`, using the symbolic mapping provided in the Copper config
319/// (`resources: { name: "bundle.resource" }`).
320pub trait ResourceBindings<'r>: Sized {
321    type Binding: Copy + Eq + 'static;
322
323    fn from_bindings(
324        manager: &'r mut ResourceManager,
325        mapping: Option<&ResourceBindingMap<Self::Binding>>,
326    ) -> CuResult<Self>;
327}
328
329impl<'r> ResourceBindings<'r> for () {
330    type Binding = ();
331
332    fn from_bindings(
333        _manager: &'r mut ResourceManager,
334        _mapping: Option<&ResourceBindingMap<Self::Binding>>,
335    ) -> CuResult<Self> {
336        Ok(())
337    }
338}
339
340/// Bundle providers implement this trait to populate the `ResourceManager` with
341/// concrete resources for a given bundle id.
342pub trait ResourceBundle: ResourceBundleDecl + Sized {
343    fn build(
344        bundle: BundleContext<Self>,
345        config: Option<&ComponentConfig>,
346        manager: &mut ResourceManager,
347    ) -> CuResult<()>;
348}
349
350/// Context passed to bundle providers when building resources.
351pub struct BundleContext<B: ResourceBundleDecl> {
352    bundle_index: BundleIndex,
353    bundle_id: &'static str,
354    _boo: PhantomData<B>,
355}
356
357impl<B: ResourceBundleDecl> BundleContext<B> {
358    pub const fn new(bundle_index: BundleIndex, bundle_id: &'static str) -> Self {
359        Self {
360            bundle_index,
361            bundle_id,
362            _boo: PhantomData,
363        }
364    }
365
366    pub const fn bundle_id(&self) -> &'static str {
367        self.bundle_id
368    }
369
370    pub const fn bundle_index(&self) -> BundleIndex {
371        self.bundle_index
372    }
373
374    pub fn key<T>(&self, id: B::Id) -> ResourceKey<T> {
375        ResourceKey::new(self.bundle_index, id.index())
376    }
377}
378
379#[cfg(feature = "std")]
380pub struct ThreadPoolBundle;
381
382#[cfg(feature = "std")]
383#[derive(Copy, Clone, Debug, Eq, PartialEq)]
384#[repr(usize)]
385pub enum ThreadPoolId {
386    BgThreads,
387}
388
389#[cfg(feature = "std")]
390impl ResourceId for ThreadPoolId {
391    const COUNT: usize = 1;
392
393    fn index(self) -> usize {
394        self as usize
395    }
396}
397
398#[cfg(feature = "std")]
399impl ResourceBundleDecl for ThreadPoolBundle {
400    type Id = ThreadPoolId;
401}
402
403#[cfg(feature = "std")]
404impl ResourceBundle for ThreadPoolBundle {
405    fn build(
406        bundle: BundleContext<Self>,
407        config: Option<&ComponentConfig>,
408        manager: &mut ResourceManager,
409    ) -> CuResult<()> {
410        use rayon::ThreadPoolBuilder;
411
412        const DEFAULT_THREADS: usize = 2;
413        let threads: usize = config
414            .and_then(|cfg| cfg.get::<u64>("threads"))
415            .map(|v| v as usize)
416            .unwrap_or(DEFAULT_THREADS);
417
418        let pool = ThreadPoolBuilder::new()
419            .num_threads(threads)
420            .build()
421            .map_err(|e| CuError::from(format!("Failed to build threadpool: {e}")))?;
422
423        let key = bundle.key::<rayon::ThreadPool>(ThreadPoolId::BgThreads);
424        manager.add_shared(key, Arc::new(pool))?;
425        Ok(())
426    }
427}