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