Skip to main content

steel_registry/stat/
registry.rs

1use crate::stat::Stat;
2use crate::{RegistryEntry, RegistryExt};
3use rustc_hash::FxHashMap;
4use std::fmt::{Debug, Formatter};
5use std::marker::PhantomData;
6use std::sync::{LazyLock, OnceLock};
7use steel_utils::{Downcast, DowncastType, DowncastTypeKey, ErasedType, Identifier};
8
9/// Behavior required for a registry so that the values stored in that registry
10/// can be used for identifying a particular stat.
11#[expect(clippy::len_without_is_empty)]
12pub trait StatValueRegistry: ErasedType + Send + Sync + 'static {
13    fn len(&self) -> usize;
14    fn value_from_id(&self, id: usize) -> Option<&'static dyn StatValueRegistryEntry>;
15    fn value_from_key(&self, key: &Identifier) -> Option<&'static dyn StatValueRegistryEntry>;
16
17    fn key_from_id(&self, id: usize) -> Option<&'static Identifier> {
18        self.value_from_id(id)
19            .map(StatValueRegistryEntry::stat_value_key)
20    }
21    fn id_from_key(&self, key: &Identifier) -> Option<usize> {
22        self.value_from_key(key)
23            .map(StatValueRegistryEntry::stat_value_id)
24    }
25}
26
27impl<R> StatValueRegistry for R
28where
29    R: RegistryExt + ErasedType + Send + Sync + 'static,
30    R::Entry: StatValueRegistryEntry,
31{
32    fn len(&self) -> usize {
33        self.len()
34    }
35
36    fn value_from_id(&self, id: usize) -> Option<&'static dyn StatValueRegistryEntry> {
37        self.by_id(id)
38            .map(|value| value as &dyn StatValueRegistryEntry)
39    }
40
41    fn value_from_key(&self, key: &Identifier) -> Option<&'static dyn StatValueRegistryEntry> {
42        self.by_key(key)
43            .map(|value| value as &dyn StatValueRegistryEntry)
44    }
45}
46
47/// Behavior required for a registry entry so that it can be used for identifying a particular stat.
48pub trait StatValueRegistryEntry: Send + Sync + 'static {
49    // The functions here are prefixed so that it doesn't conflict
50    // with those of RegistryEntry.
51    fn stat_value_key(&self) -> &Identifier;
52    fn stat_value_id(&self) -> usize;
53}
54
55impl<E> StatValueRegistryEntry for E
56where
57    E: RegistryEntry + Send + Sync,
58{
59    fn stat_value_key(&self) -> &Identifier {
60        self.key()
61    }
62
63    fn stat_value_id(&self) -> usize {
64        self.id()
65    }
66}
67
68/// A structure that identifies a type of stat, using the
69/// registry type [`R`] for using items from it to identify a particular stat.
70pub struct StatType<R: RegistryExt> {
71    /// The identifier that identifies this stat type uniquely.
72    pub key: Identifier,
73
74    stat_type_entry_ref: OnceLock<StatTypeEntryRef>,
75    _phantom: PhantomData<R>,
76}
77
78impl<R: RegistryExt> StatType<R>
79where
80    R::Entry: StatValueRegistryEntry,
81{
82    /// Creates a new [`StatType`] from a key and its display name.
83    pub(crate) const fn new(key: Identifier) -> Self {
84        Self {
85            key,
86            stat_type_entry_ref: OnceLock::new(),
87            _phantom: PhantomData,
88        }
89    }
90
91    /// Returns the identifying key of this stat type.
92    #[must_use]
93    pub const fn key(&self) -> &Identifier {
94        &self.key
95    }
96
97    /// Gets the reference to the entry corresponding to their stat type.
98    ///
99    /// # Panics
100    ///
101    /// Panics if this stat type has not been registered with the [`StatTypeRegistry`].
102    pub fn stat_type_entry_ref(&self) -> StatTypeEntryRef {
103        self.stat_type_entry_ref
104            .get()
105            .expect("attempted to get the entry reference of an unregistered stat type")
106    }
107
108    /// Gets a [`Stat`] of this type with a given value.
109    ///
110    /// # Panics
111    ///
112    /// Panics if this stat type is unregistered with the [`StatTypeRegistry`].
113    pub fn get(&'static self, value: &'static R::Entry) -> Stat {
114        Stat::new(self, value)
115    }
116}
117
118pub type StatTypeRef<R> = &'static StatType<R>;
119
120/// A type-erased registry whose values can be used for identifying a particular stat.
121///
122/// Registries retain their concrete Rust type and can be recovered with [`Self::downcast_ref`].
123#[derive(Copy, Clone)]
124pub struct StatValueRegistryData {
125    value: &'static dyn StatValueRegistry,
126}
127
128impl StatValueRegistryData {
129    /// Erases the type of the provided registry.
130    #[must_use]
131    pub fn new(value: &'static dyn StatValueRegistry) -> Self {
132        Self { value }
133    }
134
135    /// Returns the concrete registry when it has type `R`.
136    #[must_use]
137    pub fn downcast_ref<R: StatValueRegistry + DowncastType>(&self) -> Option<&'static R> {
138        (*self.value).downcast_ref::<R>()
139    }
140
141    /// Returns the concrete type key of the registry involved in the data.
142    #[must_use]
143    pub fn type_key(&self) -> DowncastTypeKey {
144        self.value.downcast_type_key()
145    }
146}
147
148/// An entry stored in the stat type registry. It represents a stat type.
149///
150/// A stat type is always associated with a registry whose values will be used to identify
151/// stats from this stat type. Therefore, `StatTypeEntry` contains the registry responsible for the
152/// encoding and decoding of the values involved.
153///
154/// For example, `ITEM_DROPPED` uses the item registry to have each item in this registry become a stat
155/// under this stat type (like `diamond`) to track how many of those items have been dropped by the player.
156///
157/// Internally, the registry is stored in a [`LazyLock`], so that the reference
158/// to the registry is only loaded after it has initialized.
159pub struct StatTypeEntry {
160    /// The identifier of this stat type.
161    pub key: Identifier,
162
163    /// The registry that can encode and decode the stat identity involved in this stat type.
164    registry:
165        LazyLock<StatValueRegistryData, Box<dyn FnOnce() -> StatValueRegistryData + Send + Sync>>,
166}
167
168impl StatTypeEntry {
169    /// Gets the number of entries in this registry that this stat type is associated with.
170    pub fn registry_len(&self) -> usize {
171        self.registry.value.len()
172    }
173
174    /// Gets the key of an item in this registry by its registry ID.
175    pub fn key_from_id(&self, id: usize) -> Option<&Identifier> {
176        self.registry.value.key_from_id(id)
177    }
178
179    /// Gets the registry ID of an item in this registry by its key.
180    pub fn id_from_key(&self, key: &Identifier) -> Option<usize> {
181        self.registry.value.id_from_key(key)
182    }
183
184    /// Gets the erased value of an item in this registry by its registry ID.
185    pub fn value_from_id(&self, id: usize) -> Option<&'static dyn StatValueRegistryEntry> {
186        self.registry.value.value_from_id(id)
187    }
188
189    /// Gets the erased value of an item in this registry by its key.
190    pub fn value_from_key(&self, key: &Identifier) -> Option<&'static dyn StatValueRegistryEntry> {
191        self.registry.value.value_from_key(key)
192    }
193}
194
195impl Debug for StatTypeEntry {
196    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
197        f.debug_tuple("StatTypeEntry").field(&self.key).finish()
198    }
199}
200
201pub type StatTypeEntryRef = &'static StatTypeEntry;
202
203/// Registry of all stat types. A stat type is always associated with a registry whose values
204/// will be used to identify stats from this stat type.
205///
206/// For example, `ITEM_DROPPED` uses the item registry to have each item in this registry become a stat
207/// under this stat type (like `diamond`) to track how many of those items have been dropped by the player.
208pub struct StatTypeRegistry {
209    /// Stat types indexed by network ID.
210    stat_types_by_id: Vec<StatTypeEntryRef>,
211    /// Map which maps from the stat type identifier to its network ID.
212    stat_types_by_key: FxHashMap<Identifier, usize>,
213    /// Whether registration is still allowed.
214    allows_registering: bool,
215}
216
217impl StatTypeRegistry {
218    /// Creates a new registry for stat types.
219    #[must_use]
220    pub fn new() -> Self {
221        Self {
222            stat_types_by_id: Vec::new(),
223            stat_types_by_key: FxHashMap::default(),
224            allows_registering: true,
225        }
226    }
227
228    /// Registers a stat type in this registry.
229    ///
230    /// The registry supplied in this function must be in a supplier so that
231    /// it only runs once when the registries are initialized.
232    pub fn register<R, F>(&mut self, stat_type: StatTypeRef<R>, registry_supplier: F)
233    where
234        R: RegistryExt + StatValueRegistry,
235        F: (FnOnce() -> &'static R) + Send + Sync + 'static,
236    {
237        assert!(
238            self.allows_registering,
239            "Cannot register stat types after the registry has been frozen"
240        );
241
242        let key = &stat_type.key;
243        assert!(
244            !self.stat_types_by_key.contains_key(key),
245            "Cannot register duplicate stat type key {key}"
246        );
247
248        let entry = StatTypeEntry {
249            key: stat_type.key.clone(),
250            registry: LazyLock::new(Box::new(|| StatValueRegistryData::new(registry_supplier()))),
251        };
252
253        let entry_ref = Box::leak(Box::new(entry));
254        let id = self.stat_types_by_id.len();
255
256        let locked_ref = stat_type.stat_type_entry_ref.get_or_init(|| entry_ref);
257        assert_eq!(entry_ref, *locked_ref);
258
259        self.stat_types_by_id.push(entry_ref);
260        self.stat_types_by_key.insert(stat_type.key.clone(), id);
261    }
262
263    /// Iterates all stat type entries in this registry.
264    pub fn iter(&self) -> impl Iterator<Item = (usize, StatTypeEntryRef)> + '_ {
265        self.stat_types_by_id
266            .iter()
267            .enumerate()
268            .map(|(id, &entry)| (id, entry))
269    }
270}
271
272impl Default for StatTypeRegistry {
273    fn default() -> Self {
274        Self::new()
275    }
276}
277
278crate::impl_registry!(
279    StatTypeRegistry,
280    StatTypeEntry,
281    stat_types_by_id,
282    stat_types_by_key,
283    stat_types
284);