Skip to main content

rusteal_core/
reify_registry.rs

1// Reify registry: manages Rust-side type info, function callbacks, and instance data
2// for runtime-created UE classes.
3//
4// Three registries:
5// 1. Type registry: maps type_id -> RustTypeInfo (constructor, destructor, name)
6// 2. Function registry: maps callback_id -> Rust function closure
7// 3. Instance data: maps UObject pointer -> allocated Rust data
8
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex, OnceLock, RwLock};
11
12use crate::{lock_or_recover, read_or_recover, write_or_recover};
13
14// ---------------------------------------------------------------------------
15// Inventory-based auto-registration
16// ---------------------------------------------------------------------------
17
18/// Submitted by `#[uclass]` — holds register + finalize fn pointers.
19pub struct ClassRegistration {
20    pub register: fn(),
21    pub finalize: fn(),
22}
23inventory::collect!(ClassRegistration);
24
25/// Submitted by `#[uclass_impl]` — holds register_functions fn pointer, and the
26/// `#[class_defaults]` method's caller when the block has one.
27pub struct ClassFunctionRegistration {
28    pub register_functions: fn(),
29    pub class_defaults: Option<fn()>,
30}
31inventory::collect!(ClassFunctionRegistration);
32
33/// What a `#[class_defaults]` method may return: nothing, or a result whose
34/// error is logged.
35pub trait ClassDefaultsOutcome {
36    fn report(self, class: &str);
37}
38
39impl ClassDefaultsOutcome for () {
40    fn report(self, _class: &str) {}
41}
42
43impl ClassDefaultsOutcome for crate::error::RustealResult<()> {
44    fn report(self, class: &str) {
45        if let Err(e) = self {
46            let msg = format!("[Rusteal] {class}: class defaults failed: {e}");
47            let bytes = msg.as_bytes();
48            unsafe {
49                crate::ffi_dispatch::logging_log(1, bytes.as_ptr(), bytes.len() as u32);
50            }
51        }
52    }
53}
54
55/// Four-phase iteration: register all → register all functions → finalize all
56/// → class defaults, which run on class default objects that now exist.
57pub fn register_all_from_inventory() {
58    let mut class_count = 0u32;
59    for reg in inventory::iter::<ClassRegistration> {
60        (reg.register)();
61        class_count += 1;
62    }
63    let mut func_reg_count = 0u32;
64    for freg in inventory::iter::<ClassFunctionRegistration> {
65        (freg.register_functions)();
66        func_reg_count += 1;
67    }
68    for reg in inventory::iter::<ClassRegistration> {
69        (reg.finalize)();
70    }
71    for freg in inventory::iter::<ClassFunctionRegistration> {
72        if let Some(class_defaults) = freg.class_defaults {
73            class_defaults();
74        }
75    }
76
77    // Log registration summary (helps diagnose hot-reload issues).
78    let total_funcs = read_or_recover(func_registry()).len();
79    let msg = format!(
80        "[Rusteal] register_all_from_inventory: {} classes, {} impl blocks, {} function callbacks",
81        class_count, func_reg_count, total_funcs,
82    );
83    let bytes = msg.as_bytes();
84    unsafe {
85        crate::ffi_dispatch::logging_log(0, bytes.as_ptr(), bytes.len() as u32);
86    }
87}
88
89use rusteal_ffi::UObjectHandle;
90
91/// Information about a Rust type registered for reification.
92pub struct RustTypeInfo {
93    /// Human-readable type name (for debugging).
94    pub name: &'static str,
95    /// Allocate and return a default-initialized instance. The returned pointer
96    /// must be freeable by `drop_fn`.
97    pub construct_fn: fn() -> *mut u8,
98    /// Drop and deallocate an instance previously created by `construct_fn`.
99    pub drop_fn: unsafe fn(*mut u8),
100}
101
102use crate::ffi_dispatch::NativePtr;
103
104// Type for reify function callbacks: (obj, rust_data, params)
105// Uses Arc so we can clone the reference out of the registry and release
106// the lock before invoking the callback (prevents deadlock if the callback
107// makes FFI calls that re-enter Rust).
108// `params` is an opaque FFI buffer pointer (`NativePtr` = `*mut u8`).
109type ReifyFunctionCallback = Arc<dyn Fn(UObjectHandle, *mut u8, NativePtr) + Send + Sync>;
110
111// ---------------------------------------------------------------------------
112// Statics
113// ---------------------------------------------------------------------------
114
115static TYPE_REGISTRY: OnceLock<Mutex<HashMap<u64, RustTypeInfo>>> = OnceLock::new();
116static FUNC_REGISTRY: OnceLock<RwLock<Vec<ReifyFunctionCallback>>> = OnceLock::new();
117static INSTANCE_DATA: OnceLock<RwLock<HashMap<u64, InstanceEntry>>> = OnceLock::new();
118
119struct InstanceEntry {
120    data: *mut u8,
121    type_id: u64,
122}
123
124// SAFETY: The raw pointer in InstanceEntry is only accessed on the game thread.
125unsafe impl Send for InstanceEntry {}
126unsafe impl Sync for InstanceEntry {}
127
128fn type_registry() -> &'static Mutex<HashMap<u64, RustTypeInfo>> {
129    TYPE_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
130}
131
132fn func_registry() -> &'static RwLock<Vec<ReifyFunctionCallback>> {
133    FUNC_REGISTRY.get_or_init(|| RwLock::new(Vec::new()))
134}
135
136fn instance_data() -> &'static RwLock<HashMap<u64, InstanceEntry>> {
137    INSTANCE_DATA.get_or_init(|| RwLock::new(HashMap::new()))
138}
139
140// ---------------------------------------------------------------------------
141// Type registry
142// ---------------------------------------------------------------------------
143
144/// Register a Rust type for reification. Must be called before `create_class`.
145pub fn register_type(type_id: u64, info: RustTypeInfo) {
146    lock_or_recover(type_registry())
147        .insert(type_id, info);
148}
149
150// ---------------------------------------------------------------------------
151// Function registry
152// ---------------------------------------------------------------------------
153
154/// Register a Rust function callback and return its unique callback ID.
155pub fn register_function<F>(f: F) -> u64
156where
157    F: Fn(UObjectHandle, *mut u8, NativePtr) + Send + Sync + 'static,
158{
159    let mut vec = write_or_recover(func_registry());
160    let id = vec.len() as u64;
161    vec.push(Arc::new(f));
162    id
163}
164
165// ---------------------------------------------------------------------------
166// Instance lifecycle
167// ---------------------------------------------------------------------------
168
169/// Construct a Rust instance for a newly created UObject.
170/// Called from the C++ class constructor via `construct_rust_instance` callback.
171pub fn construct_instance(obj: UObjectHandle, type_id: u64) {
172    let types = lock_or_recover(type_registry());
173    let Some(info) = types.get(&type_id) else {
174        // Log warning — type not registered (might be a CDO before registration completes)
175        if crate::api::is_api_initialized() {
176            let msg = format!("[Rusteal] construct_instance: unknown type_id {type_id}");
177            let bytes = msg.as_bytes();
178            unsafe {
179                crate::ffi_dispatch::logging_log(1, bytes.as_ptr(), bytes.len() as u32);
180            }
181        }
182        return;
183    };
184    let data = (info.construct_fn)();
185    drop(types); // Release lock before acquiring instance_data lock
186
187    let key = obj.to_addr();
188    write_or_recover(instance_data())
189        .insert(key, InstanceEntry { data, type_id });
190}
191
192/// Drop and remove the Rust instance for a destroyed UObject.
193/// Called from the C++ delete listener via `drop_rust_instance` callback.
194pub fn drop_instance(obj: UObjectHandle, _type_id: u64) {
195    let key = obj.to_addr();
196    let entry = write_or_recover(instance_data()).remove(&key);
197
198    if let Some(entry) = entry {
199        let types = lock_or_recover(type_registry());
200        if let Some(info) = types.get(&entry.type_id) {
201            unsafe {
202                (info.drop_fn)(entry.data);
203            }
204        }
205    }
206}
207
208/// Invoke a registered Rust function callback.
209/// Called from the C++ thunk via `invoke_rust_function` callback.
210pub fn invoke_function(callback_id: u64, obj: UObjectHandle, params: NativePtr) {
211    let key = obj.to_addr();
212
213    // Look up instance data for this object (read lock — non-exclusive).
214    let rust_data = read_or_recover(instance_data())
215        .get(&key)
216        .map(|e| e.data)
217        .unwrap_or(std::ptr::null_mut());
218
219    // Clone the callback Arc out of the registry and release the read lock
220    // BEFORE invoking the callback. This prevents deadlocks if the callback
221    // makes FFI calls that re-enter Rust.
222    let func = {
223        let vec = read_or_recover(func_registry());
224        vec.get(callback_id as usize).cloned()
225    };
226
227    if let Some(func) = func {
228        func(obj, rust_data, params);
229    } else if crate::api::is_api_initialized() {
230        let vec_len = read_or_recover(func_registry()).len();
231        let msg = format!(
232            "[Rusteal] invoke_function: callback_id {} not found (registry size = {})",
233            callback_id, vec_len,
234        );
235        let bytes = msg.as_bytes();
236        unsafe {
237            crate::ffi_dispatch::logging_log(1, bytes.as_ptr(), bytes.len() as u32);
238        }
239    }
240}
241
242/// Clear all registries and drop all instance data.
243/// Called during shutdown before DLL unload (enables hot reload).
244pub fn clear_all() {
245    // 1. Drop all instance data, using the type's drop_fn.
246    if let Some(instances) = INSTANCE_DATA.get() {
247        let mut map = write_or_recover(instances);
248        let types = lock_or_recover(type_registry());
249        for (_, entry) in map.drain() {
250            if let Some(info) = types.get(&entry.type_id) {
251                unsafe {
252                    (info.drop_fn)(entry.data);
253                }
254            }
255        }
256        drop(types);
257    }
258    // 2. Clear function registry.
259    if let Some(funcs) = FUNC_REGISTRY.get() {
260        write_or_recover(funcs).clear();
261    }
262    // 3. Clear type registry.
263    if let Some(types) = TYPE_REGISTRY.get() {
264        lock_or_recover(types).clear();
265    }
266}
267
268/// Get the Rust instance data pointer for a UObject.
269/// Returns null if no instance data is registered.
270pub fn get_instance_data(obj: UObjectHandle) -> *mut u8 {
271    let key = obj.to_addr();
272    read_or_recover(instance_data())
273        .get(&key)
274        .map(|e| e.data)
275        .unwrap_or(std::ptr::null_mut())
276}