Skip to main content

rusteal_runtime/
lib.rs

1// rusteal-runtime: what a game crate depends on. It carries the safe runtime,
2// the FFI contract, the engine's reflection flags and the proc macros, and
3// generates the library entry points through `rusteal_runtime::entry!()`.
4//
5// The Unreal types themselves are not here: they are generated per project by
6// `rusteal build --all` into the game's own `bindings` crate, whose features decide
7// which engine modules exist.
8
9// Re-exports for proc macro path resolution and user access.
10pub use rusteal_ffi as ffi;
11pub use rusteal_core as runtime;
12pub use rusteal_ue_flags as ue_flags;
13pub use rusteal_macros::{uclass, uclass_impl, ustruct};
14
15// For proc macro generated inventory::submit! invocations.
16#[doc(hidden)]
17pub extern crate inventory as __inventory;
18
19pub mod prelude;
20
21// Re-export glam for convenience.
22pub use glam;
23
24// ---------------------------------------------------------------------------
25// Callbacks (shared between init and entry! macro)
26// ---------------------------------------------------------------------------
27
28extern "C" fn real_drop_rust_instance(
29    handle: ffi::UObjectHandle,
30    type_id: u64,
31    _rust_data: *mut u8,
32) {
33    runtime::ffi_boundary((), || {
34        runtime::reify_registry::drop_instance(handle, type_id);
35    });
36}
37
38extern "C" fn real_invoke_rust_function(
39    callback_id: u64,
40    obj: ffi::UObjectHandle,
41    params: *mut u8,
42) {
43    runtime::ffi_boundary((), || {
44        runtime::reify_registry::invoke_function(callback_id, obj, params);
45    });
46}
47
48extern "C" fn real_invoke_delegate_callback(callback_id: u64, params: *mut u8) {
49    runtime::ffi_boundary((), || {
50        runtime::delegate_registry::invoke(callback_id, params);
51    });
52}
53
54extern "C" fn real_construct_rust_instance(
55    obj: ffi::UObjectHandle,
56    type_id: u64,
57    _is_cdo: bool,
58) {
59    runtime::ffi_boundary((), || {
60        runtime::reify_registry::construct_instance(obj, type_id);
61    });
62}
63
64extern "C" fn real_on_shutdown() {
65    runtime::ffi_boundary((), || {
66        // The spawned work first: no thread may run this library's code once
67        // it is gone.
68        runtime::task::shutdown();
69        runtime::reify_registry::clear_all();
70        runtime::delegate_registry::clear_all();
71        runtime::pinned::clear_all();
72    });
73}
74
75extern "C" fn real_notify_pinned_destroyed(handle: ffi::UObjectHandle) {
76    runtime::ffi_boundary((), || {
77        runtime::pinned::notify_pinned_destroyed(handle);
78    });
79}
80
81extern "C" fn real_on_tick(delta_seconds: f32) {
82    runtime::ffi_boundary((), || {
83        runtime::task::tick(delta_seconds);
84    });
85}
86
87#[doc(hidden)]
88pub static __CALLBACKS: ffi::RustealRustCallbacks = ffi::RustealRustCallbacks {
89    drop_rust_instance: real_drop_rust_instance,
90    invoke_rust_function: real_invoke_rust_function,
91    invoke_delegate_callback: real_invoke_delegate_callback,
92    on_shutdown: real_on_shutdown,
93    construct_rust_instance: real_construct_rust_instance,
94    notify_pinned_destroyed: real_notify_pinned_destroyed,
95    on_tick: real_on_tick,
96};
97
98// ---------------------------------------------------------------------------
99// Init / Shutdown (called from entry!() generated code)
100// ---------------------------------------------------------------------------
101
102/// Initialize the Rusteal runtime. Called by the `entry!()` generated `rusteal_init`.
103///
104/// Stores the API table, registers all reified classes, and returns the
105/// callback table pointer. Returns null on failure, including a table from a
106/// plugin of another Rusteal version.
107///
108/// # Safety
109///
110/// `api_table` is null or points to a table the plugin keeps alive for as long
111/// as the library is loaded; at least its `version` field must be readable.
112pub unsafe fn init(api_table: *const ffi::RustealApiTable) -> *const ffi::RustealRustCallbacks {
113    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
114        if api_table.is_null() {
115            return std::ptr::null();
116        }
117        // The plugin writes its own version in the first field, readable
118        // whatever the rest of the table looks like. Another version means
119        // another table: refuse it before touching anything else.
120        if unsafe { (*api_table).version } != ffi::RUSTEAL_VERSION {
121            return std::ptr::null();
122        }
123
124        // Delegate API table storage to rusteal-core.
125        runtime::init_api(api_table);
126        runtime::task::mark_game_thread();
127
128        log_greeting();
129        register_all_classes();
130        run_load_hooks();
131        &__CALLBACKS as *const ffi::RustealRustCallbacks
132    }))
133    .unwrap_or(std::ptr::null())
134}
135
136/// Log the Rusteal greeting message.
137fn log_greeting() {
138    let msg = "[Rusteal] Rust side initialized";
139    let bytes = msg.as_bytes();
140    unsafe {
141        runtime::ffi_dispatch::logging_log(0, bytes.as_ptr(), bytes.len() as u32);
142    }
143}
144
145/// Register all Rust-defined UE classes via inventory auto-registration.
146fn register_all_classes() {
147    runtime::reify_registry::register_all_from_inventory();
148}
149
150/// A function the library runs every time it loads, hot reloads included,
151/// once its classes are registered. Submitted by [`on_load!`].
152#[doc(hidden)]
153pub struct LoadHook(pub fn());
154inventory::collect!(LoadHook);
155
156fn run_load_hooks() {
157    for hook in inventory::iter::<LoadHook> {
158        (hook.0)();
159    }
160}
161
162/// Run a function every time the library loads, hot reloads included, once
163/// its classes are registered: the place to register console commands and
164/// variables, which an unload removes.
165///
166/// ```ignore
167/// fn startup() {
168///     let _ = rusteal_runtime::runtime::console::register_command("my.hello", "Say hello", |_, _| {});
169/// }
170/// rusteal_runtime::on_load!(startup);
171/// ```
172#[macro_export]
173macro_rules! on_load {
174    ($function:path) => {
175        $crate::__inventory::submit! { $crate::LoadHook($function) }
176    };
177}
178
179/// Shut down the Rusteal runtime. Called by the `entry!()` generated `rusteal_shutdown`.
180pub fn shutdown() {
181    let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
182        (__CALLBACKS.on_shutdown)();
183    }));
184}
185
186/// Generates DLL exports for the Rusteal runtime entry points.
187///
188/// Place this at the top of your cdylib crate's `lib.rs`:
189/// ```ignore
190/// rusteal_runtime::entry!();
191/// ```
192#[macro_export]
193macro_rules! entry {
194    () => {
195        mod __rusteal_native_entry {
196            #[unsafe(no_mangle)]
197            pub extern "C" fn rusteal_init(
198                api_table: *const $crate::ffi::RustealApiTable,
199            ) -> *const $crate::ffi::RustealRustCallbacks {
200                // SAFETY: called by the plugin with its own static table.
201                unsafe { $crate::init(api_table) }
202            }
203
204            #[unsafe(no_mangle)]
205            pub extern "C" fn rusteal_shutdown() {
206                $crate::shutdown()
207            }
208
209            /// The Rusteal version this library was built with, encoded; the
210            /// plugin compares it with its own before calling `rusteal_init`.
211            #[unsafe(no_mangle)]
212            pub extern "C" fn rusteal_version() -> u32 {
213                $crate::ffi::RUSTEAL_VERSION
214            }
215
216            /// The callback table, which the plugin takes before calling
217            /// `rusteal_init`: the class default objects the registration
218            /// creates get their Rust data through it.
219            #[unsafe(no_mangle)]
220            pub extern "C" fn rusteal_callbacks() -> *const $crate::ffi::RustealRustCallbacks {
221                &$crate::__CALLBACKS
222            }
223        }
224    };
225}