Skip to main content

forge/
singleton.rs

1use alloc::{boxed::Box, ffi::CString, vec::Vec};
2
3use sys::singleton::*;
4
5use crate::mt::object::{MtObject, Object};
6
7/// Access point for the game's registered singletons.
8///
9/// Allows looking up singletons by class name or DTI `id`, and retrieving them as
10/// [`MtObject`] references or as concrete subclasses. Also supports fetching all
11/// registered singletons at once as a boxed slice of [`MtObject`] references.
12pub struct SingletonManager;
13
14impl SingletonManager {
15    /// Looks up a singleton by class name, returning it as an [`MtObject`].
16    ///
17    /// Returns `None` if no singleton with that name is registered. See
18    /// [`get_by_name_typed`](Self::get_by_name_typed) to receive a concrete
19    /// subclass instead.
20    pub fn get_by_name(name: &str) -> Option<&'static mut MtObject> {
21        Self::get_by_name_typed(name)
22    }
23
24    /// Looks up a singleton by its DTI `id`, returning it as an [`MtObject`].
25    ///
26    /// Returns `None` if no singleton with that id is registered. See
27    /// [`get_by_id_typed`](Self::get_by_id_typed) to receive a concrete
28    /// subclass instead.
29    pub fn get_by_id(id: u32) -> Option<&'static mut MtObject> {
30        Self::get_by_id_typed(id)
31    }
32
33    /// Looks up a singleton by class name and returns it as the subclass `T`.
34    ///
35    /// The downcast to `T` is unchecked; the caller must ensure the singleton
36    /// is actually a `T`. Returns `None` if `name` contains an interior NUL
37    /// byte or if no singleton with that name is registered.
38    pub fn get_by_name_typed<T: Object>(name: &str) -> Option<&'static mut T> {
39        let c_name = CString::new(name).ok()?;
40        let ptr = unsafe { forge_singleton_getInstanceByName(c_name.as_bytes().as_ptr()) };
41
42        if ptr.is_null() {
43            None
44        } else {
45            Some(unsafe { &mut *(ptr as *mut T) })
46        }
47    }
48
49    /// Looks up a singleton by its DTI `id` and returns it as the subclass `T`.
50    ///
51    /// The downcast to `T` is unchecked; the caller must ensure the singleton
52    /// is actually a `T`. Returns `None` if no singleton with that id is
53    /// registered.
54    pub fn get_by_id_typed<T: Object>(id: u32) -> Option<&'static mut T> {
55        let ptr = unsafe { forge_singleton_getInstanceById(id) };
56
57        if ptr.is_null() {
58            None
59        } else {
60            Some(unsafe { &mut *(ptr as *mut T) })
61        }
62    }
63
64    /// Returns every registered singleton as a boxed slice of [`MtObject`]
65    /// references.
66    ///
67    /// The count is queried first, then the instances are fetched into a buffer
68    /// of that size. If the game reports a different count on the second call
69    /// (e.g. singletons were registered or removed in between) the mismatch is
70    /// logged and the actual number returned is used.
71    pub fn get_all() -> Box<[&'static mut MtObject]> {
72        let count = unsafe { forge_singleton_getAllInstances(core::ptr::null_mut(), 0) };
73        let mut instances = Vec::with_capacity(count as usize);
74
75        let actual = unsafe {
76            let n = forge_singleton_getAllInstances(instances.as_mut_ptr(), count);
77            instances.set_len(n as usize);
78            n
79        };
80
81        if actual != count {
82            log::error!("Mismatching singleton count. Expected {count} got {actual}");
83        }
84
85        instances
86            .iter()
87            .map(|&inst| unsafe { &mut *(inst as *mut MtObject) })
88            .collect()
89    }
90}