The runtime
rusteal-core is the safe runtime: every unsafe call through the API table is confined to it, so the generated bindings and game code only see safe types. rusteal-runtime is the one crate a game depends on. This chapter is a map of both, and of the editor module that completes them on the C++ side.
rusteal-core, file by file
| File | Role |
|---|---|
lib.rs | Modules and re-exports; lock_or_recover and friends, which recover a poisoned lock instead of panicking. |
api.rs | The table pointer (static API: OnceLock): init_api (once), api() (panics when not initialized), is_api_initialized(). |
ffi_dispatch | Generated by build.rs, one function per table entry (see The Rust–C++ boundary). |
object_ref.rs | UObjectRef<T>, Checked<T>, casts, upcasts. |
weak_ptr.rs, soft_ref.rs, subclass_of.rs, pinned.rs | Weak and soft references, class references, GC roots (see Reification). |
traits.rs | UeClass, UeStruct, UeEnum, HasParent, Inherits, UeHandle, ValidHandle. |
reify_registry.rs | Registering the library’s types, instance data, function dispatch (see Reification). |
delegate_registry.rs | Closures bound to delegates and console commands, by id; DelegateBinding. |
dynamic_call.rs | Calling a function, or broadcasting a delegate, by name through reflection. |
containers.rs | UeArray, UeMap, UeSet views; OwnedStruct<T>. |
struct_ref.rs | UStructRef<T> and OutRef<T> into parameter buffers. |
fname.rs | FName. |
ue_math.rs | Rotator, Transform, LinearColor, Color, Plane, Ray, Sphere, boxes and bounds, on glam. |
world.rs | Spawning, finding, loading and creating objects, actors of a class, data table rows. |
console.rs, input.rs, widget.rs, logging.rs | Console commands and variables, Enhanced Input binding, widgets, ulog!. |
task.rs | Background work, the game-thread queue, tick hooks. |
error.rs, ffi_guard.rs | RustealError and panics (see Errors). |
Threads and ticks (task.rs)
The thread that runs rusteal_init is recorded as the game thread. Then:
run_on_game_thread(f)pushesfonto a queue; it runs on the next tick.spawn(work, done)runsworkon arusteal-taskthread and queuesdone(value)for the game thread. A panickingworkis logged on the game thread; a thread that cannot start is logged too. Running tasks are counted.on_tick(f)registers a hook called every tick with the delta time, until itsTickHandleis removed (the handle is#[must_use]: dropping it keeps the hook forever).tick(delta), theon_tickcallback: drains the queue, then calls the hooks, each undercatch_unwind, without holding the lock while user code runs, so a hook may add or remove hooks.shutdown(), fromon_shutdown: waits for running tasks (warning after a second), then clears everything, so no thread runs code that is unloaded.
On the C++ side, StartupModule adds TickLibraries to the core ticker (FTSTicker::GetCoreTicker()), which calls every library’s on_tick each frame.
Calling by name (dynamic_call.rs)
DynamicCall is what the bindings do not cover: functions defined in Blueprints, a BlueprintImplementableEvent’s implementation, delegates.
DynamicCall::new(obj, "Function")checks the object, finds the function by reflection (function_not_foundotherwise) and allocates its parameter buffer;for_delegate(obj, property)takes the delegate’s signature instead.set(name, value)finds the parameter and its offset and writes the value (anFNamethrough the property, to keep its display index);set_structcopies a struct.call()runsreflection_call_function;broadcast()a multicast delegate. The result’sget(name)reads an out parameter or the return value.- The buffer is freed when the call or its result is dropped.
The macros generate DynamicCalls for BlueprintImplementableEvent bodies and #[udelegate] broadcasts, logging a failure as a warning and returning a default.
Data
- Containers.
UeArray<T>,UeMap<K, V>andUeSet<T>areCopyviews, an owner and a property: nothing is cached, each call crosses the boundary (len,get,set,push…), andto_vec,bulk_iterandset_allcopy everything in one call, growing the buffer once when it is too small. Elements implementContainerElement: primitives (raw-copyable), handles and references,String(length then UTF-8),OwnedStruct<T>(bytes). - Structs.
OwnedStruct<T>owns a buffer sized, initialized and destroyed through reflection (new); copies made from bytes do not destroy.UStructRef<T>borrows memory Unreal owns;OutRef<T>writes an out parameter. - Math.
Rotatoris in degrees and converts to and fromDQuat(pitch clamped at ±90°);Transformis aDQuat, aDVec3translation and aDVec3scale;LinearColorconverts to and fromVec4. The engine’s large world coordinates are doubles, hence theDtypes.
Services
- Console (
console.rs):register_command(name, help, closure)stores the closure indelegate_registryand registers the command, which receives its arguments and aCommandContextwhoseworld::<W>()checks the world’s handle into a typed reference; a name already registered is refused.ConsoleVariable<T>(bool,i32,f32,String) registers a variable and reads and writes it as text. The plugin forgets a library’s commands when it unloads. - Input (
input.rs):bind_action_raw(actor, action, trigger, "Function")binds an Enhanced Input action to a#[ufunction], whose parameters must be a prefix of(FInputActionValue, ElapsedSeconds, TriggeredSeconds, UInputAction*); the actor needs anUEnhancedInputComponent.should_display_touch_interface()is the Slate check the templates use for touch controls. - Widgets (
widget.rs):create_widget_raw(owner, class)picks the owner asCreateWidgetdoes (player controller, world, game instance). - Logging: games log through the
logcrate.rusteal-runtime/src/ue_log.rsinstalls alog::Logwhen the library initializes (init), withInfoas the default maximum level, and writes each record to theLogRustealcategory:error!as an error,warn!as a warning, the rest at display level. Each library has its ownloginstance, so the game’s and every plugin’s loggers are independent. Underneath,ulog!(level, ...)(logging.rs,LOG_DISPLAY,LOG_WARNING,LOG_ERROR) is the raw call, kept for older projects; it needs the API initialized.
rusteal-runtime
What a game crate depends on, and nothing else:
- re-exports:
rusteal_coreasruntime,rusteal_ffiasffi,rusteal_ue_flagsasue_flags,glam, the macros, andinventory(hidden) for the macros’ registrations; __CALLBACKS: the seven callbacks, each inffi_boundary;init(table): the checks and the registration described in The Rust–C++ boundary;entry!(): the four exports (rusteal_init, which first records the crate’s source root for error locations,rusteal_shutdown,rusteal_version,rusteal_callbacks);on_load!(function): a hook run at the end of everyinit, hot reloads included;prelude: the references, containers, structs, errors, math types, handles,uclass/uclass_impl/ustructand theglamtypes a game uses most.uenum,ulog!,bail!,ensure!andtaskare reached through the crate (rusteal_runtime::runtime::ulog).
The engine’s types are not here: they come from the project’s own bindings crate.
The editor module (ue_plugin/Rusteal/Source/RustealEditor/)
Rusteal.uplugin declares RustealEditor as an Editor module: it depends on editor-only modules (UnrealEd, BlueprintGraph, DirectoryWatcher, MessageLog, EditorSubsystem), which a game target cannot link, so the runtime module stays free of them and exposes plain hooks the editor module fills in (RustealSetClassReinstancer, RustealSetSourceOpener). Its StartupModule:
- registers the Rusteal Message Log listing (filters, no pages, clearable);
- sets the source opener:
FSourceCodeNavigation::OpenSourceFile(file, line, column), or the default application; - sets the class reinstancer (see Hot reload);
- starts the auto reload.
It also declares URustealEditorSubsystem, the native parent of Rust editor subsystems, which the editor plugin template derives from. ShutdownModule undoes all four.
Testing the runtime
There is no test double of the API table: a test that reaches ffi_dispatch panics in api(). The unit tests (inline #[cfg(test)] modules) exercise the code paths that do not need the engine, and the runtime keeps those paths separate on purpose (is_api_initialized() guards):
| File | Tests |
|---|---|
error.rs | Codes, formatting, contexts, locations, std conversions, bail!/ensure!, source paths, size and Send/Sync. |
ffi_guard.rs | A panic returns the default. |
task.rs | spawn, on_tick, removal, shutdown, driven by calling tick() by hand. |
console.rs | Value parsing. |
ue_math.rs | Rotator and color conversions. |
subclass_of.rs | Hand-written UeClass probes. |
reify_registry.rs | Shape combination and the dependency order. |
What needs the engine is tested in a project (see Testing a change). CI runs cargo build --workspace, cargo test --workspace and cargo clippy --workspace --all-targets --no-deps -- -D warnings, all without Unreal.