SCUA

How-to

Native libraries and FFI

Use a native library to decode an image, process a buffer, or expose your engine's objects to scripts. SCUA supports compiled C bindings and dynamic FFI, where scripts describe and call exported C functions without a wrapper for each function.

The native integration APIs described here are experimental. The expanded dynamic FFI is unreleased development work; no downloaded release binary includes it. Use a source checkout containing the linked examples and an FFI-enabled build.

#Run your first native call

From the repository root on macOS, build SCUA and the supplied example library:

zig build -Dffi=true
zig build ffi-fixtures -Dffi=true
zig cc -std=c11 -Wall -Wextra -Werror -Iinclude examples/ffi_start.c zig-out/lib/libscua.a -lffi -o zig-out/bin/ffi-start
./zig-out/bin/ffi-start "$PWD/zig-out/lib/libscua-ffi-fixture.dylib" examples/ffi_start.scua

The example uses a small library included in the repository; you do not need raylib. The host program checks FFI availability, creates a partition, grants the library as demo, runs the script and frees the partition. Its command-line path is a choice made by this trusted example host, not a general SCUA CLI permission.

The native functions have these signatures:

int64_t ffi_add(int64_t a, int64_t b);
void ffi_scale(float *values, size_t count, float factor);

The complete script is:

import ffi

let library = ffi.open("demo")
let add = ffi.bind(library, "ffi_add", "i64", ["i64", "i64"])
assert(ffi.call(add, 20, 22) == 42)
print(ffi.call(add, 20, 22))

let Samples = {type = ffi.pointer("f32"), count_arg = 1}
let scale = ffi.bind(library, "ffi_scale", "void", [Samples, "size_t", "f32"])
let samples: { f32 } = [1.0, 2.0, 3.0]
ffi.call(scale, samples, 3, 2.0)
assert(samples[2] == 6.0)
print(samples[2])
ffi.close(library)

count_arg = 1 associates the pointer with the second C argument. SCUA checks that the requested element count fits the array before C runs. C must still respect that count and must not keep the array address after returning.

Output:

42
6.0

Use zig cc for this source-build example so the linker matches the Zig-produced static archive. The C host needs no FFI wrapper functions; it only supplies the library grant and runs the script.

#Choose a binding approach

Use a compiled binding when you want a small application-specific script API, need to adapt a difficult native contract, or want native objects to participate in explicit redacted checkpoints. Register host functions and return opaque userdata values; script authors call those functions without maintaining integer-handle tables.

Use dynamic FFI when the native C signature fits the supported declarations and the caller is trusted. A script can bind strings, buffers, records, output parameters and callbacks. You still need the library's documented signature and ownership rules. Loading a C++ library requires a C-compatible exported interface.

Your task Guide
Configure grants, bind functions, choose scalar types or variadic signatures Call native libraries
Pass text, image bytes, numeric arrays, records and output parameters Pass native data
Let C invoke a script function or accept an owned callback result Native callbacks
Close resources, opt into cleanup, reconcile native writes, or use unsafe addresses Resource lifetimes
Describe layouts, globals and unions; generate declarations from headers Binding declarations
Return host-owned objects and rebuild resources after a checkpoint Bind userdata
Embed the runtime or drive scripts from an engine Embedding and engine callbacks

#Platform support

Dynamic FFI is disabled by default. Build with -Dffi=true; there is no blanket --allow-ffi CLI switch, and --allow-all does not grant native libraries. The browser playground does not support native FFI.

Target Current status
macOS ARM64 Focused native execution and interpreter/JIT checks include the latest lifetime and handoff additions.
macOS x86-64 Focused execution under Rosetta includes the latest additions.
Linux ARM64 and x86-64 Existing native fixtures have container execution evidence. The newest lifetime, tracking and handoff additions still need a Linux refresh. Install the target libffi development package and pkg-config.
Windows x86-64 Preview build with -Dffi-windows-preview=true and -Dffi-prefix=/path/to/target/libffi. Cross-build and Wine smoke checks pass; real-Windows validation is outstanding.
Other targets Unsupported by this dynamic FFI provider.

On Linux the example library is libscua-ffi-fixture.so; host linking may also require -lm -ldl -lpthread. The commands above are the verified macOS path, not a Windows build recipe. A library, its dependencies and SCUA must agree on architecture and ABI. Cross-compiling successfully is not evidence that a target has been tested.

#Keep native authority separate from saved state

Native code runs with the host process's authority. Grants control which libraries scripts select; they do not sandbox the library or protect against an incorrect signature. Native calls can crash or block indefinitely, outside SCUA's execution and allocation limits. Use a separate process when you need enforceable isolation.

Dynamic FFI handles and type descriptors cannot be sent to actors or persisted, even after close and even in a redacted checkpoint. Remove them from the saved graph and explicitly grant, open and bind again after load. Keep durable asset IDs or connection settings separately from process-local resources.

Compiled userdata has a different contract: both its registered type and the checkpoint operation must opt into redaction. Reload produces a detached value, never a restored pointer. Your application explicitly recreates the resource from durable data.

#Current limits

Callbacks enter SCUA only on the owning thread during an active native call. They are not an asynchronous or audio-thread callback facility. Packed or over-aligned structures can have explicit storage layouts, but general by-value calls require ABI support that is not yet available. Header import supports a defined subset of C and requires manual lifetime annotations. Long double, native SIMD ABI types and flexible array members are not general supported declarations.

The raylib binding repository demonstrates compiled bindings. It is not a promise that dynamic FFI alone can bind every raylib function.