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