Skip to main content

xpanse_api/
registry.rs

1//! Exclusive capability registry shared by drivers and apps.
2//!
3//! Drivers register one or more logical capabilities for each physical
4//! resource. Apps lease a capability by concrete Rust type. Leasing any
5//! capability removes its complete physical group, preventing aliases for the
6//! same hardware being used concurrently.
7//!
8//! # Example
9//!
10//! ```ignore
11//! use xpanse_api::{
12//!     metadata::{ModuleDetectResistor, ModuleID, ModuleSlot},
13//!     registry::{Registry, ResourceOrigin},
14//! };
15//!
16//! struct Fire;
17//! struct Confirm;
18//!
19//! let module_id = ModuleID {
20//!     md0: ModuleDetectResistor::R1K5,
21//!     md1: ModuleDetectResistor::R1K6,
22//! };
23//! let mut registry = Registry::new();
24//! registry
25//!     .register_group(ModuleSlot::FrontLeft, module_id, (Fire, Confirm))
26//!     .unwrap();
27//!
28//! let fire = registry.take_resource::<Fire>().unwrap();
29//! assert_eq!(fire.origin(), ResourceOrigin::Module);
30//! assert_eq!(fire.slot(), Some(ModuleSlot::FrontLeft));
31//!
32//! // Confirm aliases the same physical control, so it is unavailable too.
33//! assert!(!registry.has::<Confirm>());
34//! registry.return_resource(fire);
35//! assert!(registry.has::<Confirm>());
36//! ```
37use alloc::boxed::Box;
38use alloc::collections::BTreeMap;
39use alloc::{vec, vec::Vec};
40use core::any::{Any, TypeId};
41use core::marker::PhantomData;
42use core::sync::atomic::{AtomicU32, Ordering};
43
44use crate::metadata::{ModuleID, ModuleSlot};
45
46type CapabilityMap = BTreeMap<TypeId, Box<dyn Any + Send>>;
47
48static NEXT_REGISTRY_ID: AtomicU32 = AtomicU32::new(0);
49
50/// Identifies one physical resource group.
51#[derive(Clone, Copy, Debug, PartialEq, Eq, defmt::Format)]
52pub struct ResourceId {
53    kind: ResourceIdKind,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, defmt::Format)]
57enum ResourceIdKind {
58    ModuleLocal { slot: ModuleSlot, local_id: u16 },
59    RegistryAllocated(u32),
60}
61
62impl ResourceId {
63    /// Creates an id scoped to a module slot.
64    pub const fn module_local(slot: ModuleSlot, local_id: u16) -> Self {
65        Self {
66            kind: ResourceIdKind::ModuleLocal { slot, local_id },
67        }
68    }
69
70    /// Returns the module-local `(slot, local_id)` pair, or `None` for a
71    /// registry-allocated id.
72    pub const fn module_local_parts(self) -> Option<(ModuleSlot, u16)> {
73        match self.kind {
74            ResourceIdKind::ModuleLocal { slot, local_id } => Some((slot, local_id)),
75            ResourceIdKind::RegistryAllocated(_) => None,
76        }
77    }
78
79    const fn registry_allocated(id: u32) -> Self {
80        Self {
81            kind: ResourceIdKind::RegistryAllocated(id),
82        }
83    }
84}
85
86/// Describes where a resource group originated.
87#[derive(Clone, Copy, Debug, PartialEq, Eq, defmt::Format)]
88pub enum ResourceOrigin {
89    /// Resource provided by the base platform rather than an expansion module.
90    Platform,
91    /// Resource provided by a detected expansion module.
92    Module,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq, defmt::Format)]
96enum ResourceMetadata {
97    PlatformAllocated {
98        allocated_id: u32,
99    },
100    ModuleAllocated {
101        allocated_id: u32,
102        slot: ModuleSlot,
103        module_id: ModuleID,
104    },
105    ModuleLocal {
106        local_id: u16,
107        slot: ModuleSlot,
108        module_id: ModuleID,
109    },
110}
111
112impl ResourceMetadata {
113    const fn id(self) -> ResourceId {
114        match self {
115            Self::PlatformAllocated { allocated_id }
116            | Self::ModuleAllocated { allocated_id, .. } => {
117                ResourceId::registry_allocated(allocated_id)
118            }
119            Self::ModuleLocal { slot, local_id, .. } => ResourceId::module_local(slot, local_id),
120        }
121    }
122
123    const fn origin(self) -> ResourceOrigin {
124        match self {
125            Self::PlatformAllocated { .. } => ResourceOrigin::Platform,
126            Self::ModuleAllocated { .. } | Self::ModuleLocal { .. } => ResourceOrigin::Module,
127        }
128    }
129
130    const fn slot(self) -> Option<ModuleSlot> {
131        match self {
132            Self::PlatformAllocated { .. } => None,
133            Self::ModuleAllocated { slot, .. } | Self::ModuleLocal { slot, .. } => Some(slot),
134        }
135    }
136
137    const fn module_id(self) -> Option<ModuleID> {
138        match self {
139            Self::PlatformAllocated { .. } => None,
140            Self::ModuleAllocated { module_id, .. } | Self::ModuleLocal { module_id, .. } => {
141                Some(module_id)
142            }
143        }
144    }
145}
146
147struct ResourceGroup {
148    metadata: ResourceMetadata,
149    capabilities: CapabilityMap,
150}
151
152struct ResourceGroupSlot {
153    id: ResourceId,
154    available: Option<ResourceGroup>,
155}
156
157/// Exclusive ownership of one physical resource group through capability `T`.
158///
159/// While this lease is outside the registry, every other capability in the
160/// same group is unavailable. Return it with [`Registry::return_resource`] to
161/// restore the complete group.
162#[must_use = "dropping a resource lease permanently removes its complete group"]
163pub struct ResourceLease<T> {
164    registry_id: u32,
165    group: ResourceGroup,
166    resource_type: PhantomData<T>,
167}
168
169impl<T: 'static> ResourceLease<T> {
170    /// Returns the identifier of the leased physical resource group.
171    pub const fn id(&self) -> ResourceId {
172        self.group.metadata.id()
173    }
174
175    /// Returns whether the resource was registered by the platform or a module.
176    pub const fn origin(&self) -> ResourceOrigin {
177        self.group.metadata.origin()
178    }
179
180    /// Returns the source module's slot, or `None` for platform resources.
181    pub const fn slot(&self) -> Option<ModuleSlot> {
182        self.group.metadata.slot()
183    }
184
185    /// Returns the source module's ID, or `None` for platform resources.
186    pub const fn module_id(&self) -> Option<ModuleID> {
187        self.group.metadata.module_id()
188    }
189
190    /// Borrows the capability selected for this lease.
191    pub fn resource(&self) -> &T {
192        self.group
193            .capabilities
194            .get(&TypeId::of::<T>())
195            .and_then(|resource| resource.downcast_ref())
196            .expect("resource lease contains its selected capability")
197    }
198
199    /// Mutably borrows the capability selected for this lease.
200    pub fn resource_mut(&mut self) -> &mut T {
201        self.group
202            .capabilities
203            .get_mut(&TypeId::of::<T>())
204            .and_then(|resource| resource.downcast_mut())
205            .expect("resource lease contains its selected capability")
206    }
207}
208
209mod private {
210    pub trait GroupCapabilitiesSealed {}
211    pub trait ResourceGroupsSealed {}
212    pub trait ResourceSetSealed {}
213}
214
215/// A tuple of distinct capabilities belonging to one physical
216/// resource group.
217pub trait ResourceGroupCapabilities: private::GroupCapabilitiesSealed {
218    #[doc(hidden)]
219    fn into_capabilities(self) -> Result<CapabilityMap, RegistryError>;
220}
221
222/// A tuple of physical resource groups registered together.
223pub trait ResourceGroups: private::ResourceGroupsSealed {
224    #[doc(hidden)]
225    fn into_groups(self) -> Result<Vec<CapabilityMap>, RegistryError>;
226}
227
228/// A tuple of distinct resource types allocated atomically.
229///
230/// Each requested type is assigned to a different physical resource group.
231pub trait ResourceSet: private::ResourceSetSealed {
232    /// Tuple of leases returned when the complete set is acquired.
233    type Leases;
234
235    #[doc(hidden)]
236    fn is_available(registry: &Registry) -> bool;
237
238    #[doc(hidden)]
239    fn take(registry: &mut Registry) -> Option<Self::Leases>;
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)]
243/// Error returned when a resource group cannot be registered.
244pub enum RegistryError {
245    /// A group contains the same concrete capability type more than once.
246    DuplicateCapability,
247    /// A module-local resource ID is already registered for this slot.
248    DuplicateResourceId,
249}
250
251/// Collection of available and currently leased physical resource groups.
252///
253/// Registry queries only count currently available groups. A group remains in
254/// the registry while leased, but none of its capabilities can be acquired
255/// until its [`ResourceLease`] is returned.
256pub struct Registry {
257    id: u32,
258    groups: Vec<ResourceGroupSlot>,
259    next_registry_allocated_id: u32,
260}
261
262impl Default for Registry {
263    fn default() -> Self {
264        Self::new()
265    }
266}
267
268impl Registry {
269    /// Creates an empty registry with a unique identity.
270    ///
271    /// The identity prevents a lease from accidentally being returned to a
272    /// different registry.
273    pub fn new() -> Self {
274        Self {
275            id: NEXT_REGISTRY_ID
276                .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
277                .expect("registry id counter overflowed"),
278            groups: Vec::new(),
279            next_registry_allocated_id: 0,
280        }
281    }
282
283    /// Registers a module resource as a single-capability physical group.
284    pub fn register<T: 'static + Send>(
285        &mut self,
286        slot: ModuleSlot,
287        module_id: ModuleID,
288        resource: T,
289    ) {
290        let allocated_id = self.next_registry_allocated_id();
291        self.insert_group(
292            ResourceMetadata::ModuleAllocated {
293                allocated_id,
294                slot,
295                module_id,
296            },
297            single_capability(resource),
298        )
299        .expect("registry-allocated resource IDs are unique");
300    }
301
302    /// Registers several logical capabilities backed by one physical module
303    /// resource.
304    ///
305    /// # Errors
306    ///
307    /// Returns [`RegistryError::DuplicateCapability`] if the tuple contains a
308    /// concrete capability type more than once.
309    pub fn register_group<C: ResourceGroupCapabilities>(
310        &mut self,
311        slot: ModuleSlot,
312        module_id: ModuleID,
313        capabilities: C,
314    ) -> Result<(), RegistryError> {
315        let capabilities = capabilities.into_capabilities()?;
316        let allocated_id = self.next_registry_allocated_id();
317        self.insert_group(
318            ResourceMetadata::ModuleAllocated {
319                allocated_id,
320                slot,
321                module_id,
322            },
323            capabilities,
324        )
325    }
326
327    /// Registers a physical module group with a stable driver-defined local ID.
328    ///
329    /// Local IDs only need to be unique within a module slot. They are useful
330    /// when an app needs to correlate capabilities across boots.
331    ///
332    /// # Errors
333    ///
334    /// Returns [`RegistryError::DuplicateCapability`] for duplicate capability
335    /// types or [`RegistryError::DuplicateResourceId`] when `local_id` is
336    /// already registered for `slot`.
337    pub fn register_local_group<C: ResourceGroupCapabilities>(
338        &mut self,
339        slot: ModuleSlot,
340        module_id: ModuleID,
341        local_id: u16,
342        capabilities: C,
343    ) -> Result<(), RegistryError> {
344        self.insert_group(
345            ResourceMetadata::ModuleLocal {
346                local_id,
347                slot,
348                module_id,
349            },
350            capabilities.into_capabilities()?,
351        )
352    }
353
354    /// Atomically registers several physical groups from one module.
355    ///
356    /// # Errors
357    ///
358    /// Returns [`RegistryError::DuplicateCapability`] if any group repeats a
359    /// concrete capability type.
360    pub fn register_groups<G: ResourceGroups>(
361        &mut self,
362        slot: ModuleSlot,
363        module_id: ModuleID,
364        groups: G,
365    ) -> Result<(), RegistryError> {
366        let groups = groups.into_groups()?;
367        for capabilities in groups {
368            let allocated_id = self.next_registry_allocated_id();
369            self.insert_group(
370                ResourceMetadata::ModuleAllocated {
371                    allocated_id,
372                    slot,
373                    module_id,
374                },
375                capabilities,
376            )
377            .expect("registry-allocated resource IDs are unique");
378        }
379        Ok(())
380    }
381
382    /// Registers a platform resource as a single-capability physical group.
383    pub fn register_platform<T: 'static + Send>(&mut self, resource: T) {
384        let allocated_id = self.next_registry_allocated_id();
385        self.insert_group(
386            ResourceMetadata::PlatformAllocated { allocated_id },
387            single_capability(resource),
388        )
389        .expect("registry-allocated resource IDs are unique");
390    }
391
392    /// Registers several logical capabilities backed by one physical platform
393    /// resource.
394    ///
395    /// # Errors
396    ///
397    /// Returns [`RegistryError::DuplicateCapability`] if the tuple contains a
398    /// concrete capability type more than once.
399    pub fn register_platform_group<C: ResourceGroupCapabilities>(
400        &mut self,
401        capabilities: C,
402    ) -> Result<(), RegistryError> {
403        let capabilities = capabilities.into_capabilities()?;
404        let allocated_id = self.next_registry_allocated_id();
405        self.insert_group(
406            ResourceMetadata::PlatformAllocated { allocated_id },
407            capabilities,
408        )
409    }
410
411    /// Returns the number of currently available groups providing `T`.
412    pub fn resource_count<T: 'static + Send>(&self) -> usize {
413        let resource_type = TypeId::of::<T>();
414        self.groups
415            .iter()
416            .filter_map(|slot| slot.available.as_ref())
417            .filter(|group| group.capabilities.contains_key(&resource_type))
418            .count()
419    }
420
421    /// Returns whether at least one currently available group provides `T`.
422    pub fn has<T: 'static + Send>(&self) -> bool {
423        self.has_at_least::<T>(1)
424    }
425
426    /// Returns whether at least `count` currently available groups provide `T`.
427    pub fn has_at_least<T: 'static + Send>(&self, count: usize) -> bool {
428        self.resource_count::<T>() >= count
429    }
430
431    /// Returns whether all resource types in `S` can be leased from different
432    /// available physical groups.
433    pub fn has_resource_set<S: ResourceSet>(&self) -> bool {
434        S::is_available(self)
435    }
436
437    /// Leases one available physical group through capability `T`.
438    pub fn take_resource<T: 'static + Send>(&mut self) -> Option<ResourceLease<T>> {
439        let resource_type = TypeId::of::<T>();
440        let index = self.groups.iter().rposition(|slot| {
441            slot.available
442                .as_ref()
443                .is_some_and(|group| group.capabilities.contains_key(&resource_type))
444        })?;
445        Some(self.take_group_capability(index))
446    }
447
448    /// Atomically leases `count` different groups through capability `T`.
449    ///
450    /// A `count` of zero succeeds with an empty vector.
451    pub fn take_resources<T: 'static + Send>(
452        &mut self,
453        count: usize,
454    ) -> Option<Vec<ResourceLease<T>>> {
455        if count == 0 {
456            return Some(Vec::new());
457        }
458
459        let mut ids = self.resource_ids::<T>();
460        if ids.len() < count {
461            return None;
462        }
463        ids.truncate(count);
464
465        Some(
466            ids.into_iter()
467                .map(|id| self.take_resource_with_id(id))
468                .collect(),
469        )
470    }
471
472    /// Atomically leases one resource of every type in `S` from different
473    /// physical groups.
474    pub fn take_resource_set<S: ResourceSet>(&mut self) -> Option<S::Leases> {
475        S::take(self)
476    }
477
478    /// Returns a lease and makes its complete physical group available again.
479    ///
480    /// # Panics
481    ///
482    /// Panics if the lease came from a different registry or if internal lease
483    /// invariants have been violated.
484    pub fn return_resource<T: 'static + Send>(&mut self, lease: ResourceLease<T>) {
485        let ResourceLease {
486            registry_id,
487            group,
488            resource_type: _,
489        } = lease;
490        assert_eq!(
491            registry_id, self.id,
492            "resource lease returned to a different registry"
493        );
494        let id = group.metadata.id();
495        let slot = self
496            .groups
497            .iter_mut()
498            .find(|slot| slot.id == id)
499            .expect("leased resource group remains registered");
500        assert!(
501            slot.available.is_none(),
502            "resource group cannot be returned while already available"
503        );
504        slot.available = Some(group);
505    }
506
507    fn insert_group(
508        &mut self,
509        metadata: ResourceMetadata,
510        capabilities: CapabilityMap,
511    ) -> Result<(), RegistryError> {
512        let id = metadata.id();
513        if self.groups.iter().any(|group| group.id == id) {
514            return Err(RegistryError::DuplicateResourceId);
515        }
516        self.groups.push(ResourceGroupSlot {
517            id,
518            available: Some(ResourceGroup {
519                metadata,
520                capabilities,
521            }),
522        });
523        Ok(())
524    }
525
526    fn next_registry_allocated_id(&mut self) -> u32 {
527        let id = self.next_registry_allocated_id;
528        self.next_registry_allocated_id = self
529            .next_registry_allocated_id
530            .checked_add(1)
531            .expect("registry-allocated resource id counter overflowed");
532        id
533    }
534
535    /// Returns the identifiers of every available group providing `T`.
536    fn resource_ids<T: 'static + Send>(&self) -> Vec<ResourceId> {
537        let resource_type = TypeId::of::<T>();
538        self.groups
539            .iter()
540            .rev()
541            .filter_map(|slot| slot.available.as_ref())
542            .filter(|group| group.capabilities.contains_key(&resource_type))
543            .map(|group| group.metadata.id())
544            .collect()
545    }
546
547    /// Leases the group identified by `id` as capability `T`.
548    fn take_resource_with_id<T: 'static + Send>(&mut self, id: ResourceId) -> ResourceLease<T> {
549        let resource_type = TypeId::of::<T>();
550        let index = self
551            .groups
552            .iter()
553            .position(|slot| {
554                slot.id == id
555                    && slot
556                        .available
557                        .as_ref()
558                        .is_some_and(|group| group.capabilities.contains_key(&resource_type))
559            })
560            .expect("resource assignment references an available capability");
561        self.take_group_capability(index)
562    }
563
564    /// Removes the group at `index` from the available pool and wraps it in a lease.
565    fn take_group_capability<T: 'static + Send>(&mut self, index: usize) -> ResourceLease<T> {
566        let group = self.groups[index]
567            .available
568            .take()
569            .expect("selected resource group is available");
570        assert!(
571            group
572                .capabilities
573                .get(&TypeId::of::<T>())
574                .is_some_and(|resource| resource.is::<T>()),
575            "selected resource group contains the requested capability"
576        );
577
578        ResourceLease {
579            registry_id: self.id,
580            group,
581            resource_type: PhantomData,
582        }
583    }
584}
585
586/// Inserts a single capability into the registry.
587fn single_capability<T: 'static + Send>(resource: T) -> CapabilityMap {
588    let mut capabilities = BTreeMap::new();
589    capabilities.insert(TypeId::of::<T>(), Box::new(resource) as Box<dyn Any + Send>);
590    capabilities
591}
592
593/// Finds an assignment of distinct resource IDs to requested types, or `None`
594/// if no valid matching exists.
595fn resource_assignment(candidates: &[Vec<ResourceId>]) -> Option<Vec<ResourceId>> {
596    fn assign(
597        candidates: &[Vec<ResourceId>],
598        candidate_index: usize,
599        used: &mut Vec<ResourceId>,
600    ) -> bool {
601        if candidate_index == candidates.len() {
602            return true;
603        }
604
605        for &id in &candidates[candidate_index] {
606            if used.contains(&id) {
607                continue;
608            }
609
610            used.push(id);
611            if assign(candidates, candidate_index + 1, used) {
612                return true;
613            }
614            used.pop();
615        }
616
617        false
618    }
619
620    let mut assignment = Vec::new();
621    assign(candidates, 0, &mut assignment).then_some(assignment)
622}
623
624/// Returns true when no `TypeId` appears more than once in `types`.
625fn types_are_distinct(types: &[TypeId]) -> bool {
626    types
627        .iter()
628        .enumerate()
629        .all(|(index, resource_type)| !types[..index].contains(resource_type))
630}
631
632macro_rules! impl_group_capabilities {
633    ($(($resource:ident, $value:ident)),+) => {
634        impl<$($resource: 'static + Send),+> private::GroupCapabilitiesSealed
635            for ($($resource,)+)
636        {
637        }
638
639        impl<$($resource: 'static + Send),+> ResourceGroupCapabilities for ($($resource,)+) {
640            fn into_capabilities(self) -> Result<CapabilityMap, RegistryError> {
641                if !types_are_distinct(&[$(TypeId::of::<$resource>()),+]) {
642                    return Err(RegistryError::DuplicateCapability);
643                }
644
645                let ($($value,)+) = self;
646                let mut capabilities = BTreeMap::new();
647                $(
648                    capabilities.insert(
649                        TypeId::of::<$resource>(),
650                        Box::new($value) as Box<dyn Any + Send>,
651                    );
652                )+
653                Ok(capabilities)
654            }
655        }
656    };
657}
658
659impl_group_capabilities!((T1, t1), (T2, t2));
660impl_group_capabilities!((T1, t1), (T2, t2), (T3, t3));
661impl_group_capabilities!((T1, t1), (T2, t2), (T3, t3), (T4, t4));
662impl_group_capabilities!((T1, t1), (T2, t2), (T3, t3), (T4, t4), (T5, t5));
663impl_group_capabilities!((T1, t1), (T2, t2), (T3, t3), (T4, t4), (T5, t5), (T6, t6));
664impl_group_capabilities!(
665    (T1, t1),
666    (T2, t2),
667    (T3, t3),
668    (T4, t4),
669    (T5, t5),
670    (T6, t6),
671    (T7, t7)
672);
673impl_group_capabilities!(
674    (T1, t1),
675    (T2, t2),
676    (T3, t3),
677    (T4, t4),
678    (T5, t5),
679    (T6, t6),
680    (T7, t7),
681    (T8, t8)
682);
683
684macro_rules! impl_resource_groups {
685    ($(($group:ident, $value:ident)),+) => {
686        impl<$($group: ResourceGroupCapabilities),+> private::ResourceGroupsSealed
687            for ($($group,)+)
688        {
689        }
690
691        impl<$($group: ResourceGroupCapabilities),+> ResourceGroups for ($($group,)+) {
692            fn into_groups(self) -> Result<Vec<CapabilityMap>, RegistryError> {
693                let ($($value,)+) = self;
694                Ok(vec![$(($value.into_capabilities()?),)+])
695            }
696        }
697    };
698}
699
700impl_resource_groups!((G1, g1), (G2, g2));
701impl_resource_groups!((G1, g1), (G2, g2), (G3, g3));
702impl_resource_groups!((G1, g1), (G2, g2), (G3, g3), (G4, g4));
703impl_resource_groups!((G1, g1), (G2, g2), (G3, g3), (G4, g4), (G5, g5));
704impl_resource_groups!((G1, g1), (G2, g2), (G3, g3), (G4, g4), (G5, g5), (G6, g6));
705impl_resource_groups!(
706    (G1, g1),
707    (G2, g2),
708    (G3, g3),
709    (G4, g4),
710    (G5, g5),
711    (G6, g6),
712    (G7, g7)
713);
714impl_resource_groups!(
715    (G1, g1),
716    (G2, g2),
717    (G3, g3),
718    (G4, g4),
719    (G5, g5),
720    (G6, g6),
721    (G7, g7),
722    (G8, g8)
723);
724
725macro_rules! impl_resource_set {
726    ($($resource:ident),+) => {
727        impl<$($resource: 'static + Send),+> private::ResourceSetSealed for ($($resource,)+) {}
728
729        impl<$($resource: 'static + Send),+> ResourceSet for ($($resource,)+) {
730            type Leases = ($(ResourceLease<$resource>,)+);
731
732            fn is_available(registry: &Registry) -> bool {
733                if !types_are_distinct(&[$(TypeId::of::<$resource>()),+]) {
734                    return false;
735                }
736
737                let candidates = [$(registry.resource_ids::<$resource>()),+];
738                resource_assignment(&candidates).is_some()
739            }
740
741            fn take(registry: &mut Registry) -> Option<Self::Leases> {
742                if !types_are_distinct(&[$(TypeId::of::<$resource>()),+]) {
743                    return None;
744                }
745
746                let candidates = [$(registry.resource_ids::<$resource>()),+];
747                let assignment = resource_assignment(&candidates)?;
748                let mut ids = assignment.into_iter();
749                Some(($(
750                    registry.take_resource_with_id::<$resource>(
751                        ids.next().expect("resource assignment contains every requested type"),
752                    ),
753                )+))
754            }
755        }
756    };
757}
758
759impl_resource_set!(T1, T2);
760impl_resource_set!(T1, T2, T3);
761impl_resource_set!(T1, T2, T3, T4);
762impl_resource_set!(T1, T2, T3, T4, T5);
763impl_resource_set!(T1, T2, T3, T4, T5, T6);
764impl_resource_set!(T1, T2, T3, T4, T5, T6, T7);
765impl_resource_set!(T1, T2, T3, T4, T5, T6, T7, T8);
766impl_resource_set!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
767impl_resource_set!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);