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}