Skip to main content

turbo_tasks/
registry.rs

1use std::num::NonZeroU16;
2
3use anyhow::Error;
4use once_cell::sync::Lazy;
5use rustc_hash::FxHashMap;
6
7use crate::{
8    TraitType, ValueType,
9    id::{FunctionId, TraitTypeId, ValueTypeId},
10    macro_helpers::CollectableFunction,
11    native_function::NativeFunction,
12    value_type::{CollectableTrait, CollectableValueType},
13};
14
15/// A trait for types that can be registered in a registry.
16///
17/// This allows the generic registry to work with different types
18/// while maintaining their specific requirements.
19trait RegistryItem: 'static + Eq + std::hash::Hash {
20    /// The ID type used for this registry item
21    type Id: Copy + From<NonZeroU16> + std::ops::Deref<Target = u16> + std::fmt::Display;
22    const TYPE_NAME: &'static str;
23
24    /// Get the global name used for sorting and uniqueness validation
25    fn global_name(&self) -> &'static str;
26}
27
28impl RegistryItem for NativeFunction {
29    type Id = FunctionId;
30    const TYPE_NAME: &'static str = "Function";
31
32    fn global_name(&self) -> &'static str {
33        self.global_name
34    }
35}
36
37impl RegistryItem for ValueType {
38    type Id = ValueTypeId;
39    const TYPE_NAME: &'static str = "Value";
40
41    fn global_name(&self) -> &'static str {
42        self.global_name
43    }
44}
45
46impl RegistryItem for TraitType {
47    type Id = TraitTypeId;
48    const TYPE_NAME: &'static str = "Trait";
49    fn global_name(&self) -> &'static str {
50        self.global_name
51    }
52}
53
54/// A generic registry that maps between IDs and static references to items.
55///
56/// This eliminates the code duplication between Functions, Values, and Traits registries.
57struct Registry<T: RegistryItem> {
58    id_to_item: Box<[&'static T]>,
59    item_to_id: FxHashMap<&'static T, T::Id>,
60}
61
62impl<T: RegistryItem> Registry<T> {
63    /// Create a new registry from a collection of items.
64    ///
65    /// Items are sorted by global_name to ensure stable ID assignment.
66    fn new_from_items(mut items: Vec<&'static T>) -> Self {
67        // Sort by global name to get stable order
68        items.sort_unstable_by_key(|item| item.global_name());
69
70        let mut item_to_id = FxHashMap::with_capacity_and_hasher(items.len(), Default::default());
71
72        let mut id = NonZeroU16::MIN;
73        let mut prev_name: Option<&str> = None;
74        for &item in items.iter() {
75            let global_name = item.global_name();
76            if let Some(prev) = prev_name {
77                assert!(
78                    prev != global_name,
79                    "multiple {ty} items registered with name: {global_name}!",
80                    ty = T::TYPE_NAME
81                );
82            }
83            prev_name = Some(global_name);
84            item_to_id.insert(item, id.into());
85            id = id.checked_add(1).expect("overflowing item ids");
86        }
87
88        Self {
89            id_to_item: items.into_boxed_slice(),
90            item_to_id,
91        }
92    }
93
94    /// Get an item by its ID
95    fn get_item(&self, id: T::Id) -> &'static T {
96        self.id_to_item[*id as usize - 1]
97    }
98
99    /// Get the ID for an item
100    fn get_id(&self, item: &'static T) -> T::Id {
101        match self.item_to_id.get(&item) {
102            Some(id) => *id,
103            None => panic!(
104                "{ty} isn't registered: {item}",
105                ty = T::TYPE_NAME,
106                item = item.global_name()
107            ),
108        }
109    }
110
111    /// Validate that an ID is within the valid range
112    fn validate_id(&self, id: T::Id) -> Option<Error> {
113        let len = self.id_to_item.len();
114        if *id as usize <= len {
115            None
116        } else {
117            Some(anyhow::anyhow!(
118                "Invalid {ty} id, {id} expected a value <= {len}",
119                ty = T::TYPE_NAME
120            ))
121        }
122    }
123}
124
125static FUNCTIONS: Lazy<Registry<NativeFunction>> = Lazy::new(|| {
126    let functions = inventory::iter::<CollectableFunction>
127        .into_iter()
128        .map(|c| &**c.0)
129        .collect::<Vec<_>>();
130    Registry::new_from_items(functions)
131});
132
133pub fn get_native_function(id: FunctionId) -> &'static NativeFunction {
134    FUNCTIONS.get_item(id)
135}
136
137pub fn get_function_id(func: &'static NativeFunction) -> FunctionId {
138    FUNCTIONS.get_id(func)
139}
140
141pub fn validate_function_id(id: FunctionId) -> Option<Error> {
142    FUNCTIONS.validate_id(id)
143}
144
145static VALUES: Lazy<Registry<ValueType>> = Lazy::new(|| {
146    // Inventory does not guarantee an order. So we sort by the global name to get a stable order
147    // This ensures that assigned ids are also stable which is important since they are serialized.
148    let all_values = inventory::iter::<CollectableValueType>
149        .into_iter()
150        .map(|t| &**t.0)
151        .collect::<Vec<_>>();
152    Registry::new_from_items(all_values)
153});
154
155pub fn get_value_type_id(value: &'static ValueType) -> ValueTypeId {
156    VALUES.get_id(value)
157}
158
159pub fn get_value_type(id: ValueTypeId) -> &'static ValueType {
160    VALUES.get_item(id)
161}
162
163pub fn validate_value_type_id(id: ValueTypeId) -> Option<Error> {
164    VALUES.validate_id(id)
165}
166
167static TRAITS: Lazy<Registry<TraitType>> = Lazy::new(|| {
168    // Inventory does not guarantee an order. So we sort by the global name to get a stable order
169    // This ensures that assigned ids are also stable.
170    let all_traits = inventory::iter::<CollectableTrait>
171        .into_iter()
172        .map(|t| &**t.0)
173        .collect::<Vec<_>>();
174    Registry::new_from_items(all_traits)
175});
176
177pub fn get_trait_type_id(trait_type: &'static TraitType) -> TraitTypeId {
178    TRAITS.get_id(trait_type)
179}
180
181pub fn get_trait(id: TraitTypeId) -> &'static TraitType {
182    TRAITS.get_item(id)
183}
184
185pub fn validate_trait_type_id(id: TraitTypeId) -> Option<Error> {
186    TRAITS.validate_id(id)
187}