Skip to content
Maik Klein edited this page Sep 4, 2022 · 1 revision

How does it work?

We need to set up a communication scheme between Rust and Unreal, and we are going to use c ffi.

We will compile our Rust code into a C compatible dll via crate-type = ["cdylib"], and then use dlopen inside Unreal to load our Rust code.

Let's set up some data structures:

  • UnrealBindings: This will contain function pointers to Unreal and we will store this on the Rust side. This allows us to call into Unreal from within Rust, like getting the position of an actor.
  • RustBindings: This will contain function pointers to Rust and we will store this in Unreal. Sometimes Unreal has to call into Rust, eg call the Tick function of Rust, or getting reflection information out of Rust.

First we will look at UnrealBindings.

pub type GetSpatialDataFn = extern "C" fn(
    actor: *const AActorOpaque,
    position: &mut Vector3,
    rotation: &mut Quaternion,
    scale: &mut Vector3,
);

extern "C" {
    pub fn GetSpatialData(
        actor: *const AActorOpaque,
        position: &mut Vector3,
        rotation: &mut Quaternion,
        scale: &mut Vector3,
    );
}

#[repr(C)]
pub struct ActorFns {
    pub get_spatial_data: GetSpatialDataFn,
    ...
}

#[repr(C)]
pub struct UnrealBindings {
    pub actor_fns: ActorFns,
    ...
}

We define an extern function GetSpatialData and the respective GetSpatialDataFn function pointer. With the help of cbindgen we can translate the code above into a c header Bindings.h.

Now we hop into Unreal and implement GetSpatialData

void GetSpatialData(const AActorOpaque* actor,
                    Vector3* position,
                    Quaternion* rotation,
                    Vector3* scale)
{
    const auto Transform = ToAActor(actor)->GetTransform();
    *position = ToVector3(Transform.GetTranslation());
    *rotation = ToQuaternion(Transform.GetRotation());
    *scale = ToVector3(Transform.GetScale3D());
}

Then we simply fill out the UnrealBindings struct with the function pointer to GetSpatialData.

ActorFns actor_fns = {};
actor_fns.get_spatial_data = &GetSpatialData;
...

UnrealBindings b = {};
b.actor_fns = actor_fns;
...

Now we have initialized UnrealBindings with some function pointers, but we need a way to get this struct into Rust. Going back to Rust we are going to define an entry point.

pub type EntryUnrealBindingsFn =
    unsafe extern "C" fn(bindings: UnrealBindings, rust_bindings: *mut RustBindings) -> u32;

#[no_mangle]
pub unsafe extern "C" fn register_unreal_bindings(
    bindings: UnrealBindings,
    rust_bindings: *mut RustBindings,
) -> u32 {
    ...
}

In Unreal we simply retrieve the register_unreal_bindings entry point

void* LocalBindings = FPlatformProcess::GetDllExport(LocalHandle, TEXT("register_unreal_bindings\0"));

And then we pass the UnrealBindings into register_unreal_bindings. The RustBindings will follow the exact same procedure.

pub type TickFn = unsafe extern "C" fn(dt: f32) -> ResultCode;

#[repr(C)]
pub struct RustBindings {
    pub tick: TickFn,
    ...
}

We create a RustBindings struct will all the function pointers we want, we implement the function in Rust and pass them into RustBindings. Now we simply give Unreal those bindings inside

Clone this wiki locally