🦊 Vulfram — Functions, ABI and Usage Contract
This document describes the public C-ABI surface exposed by the Vulfram core, and the usage contract expected from hosts and language bindings.
It is intended for:
- Binding authors (N-API,
mlua,PyO3, etc.) - Advanced users who need to understand the low-level interaction.
1. Global Conventions
1.1 Return Type: `VulframResult (u32)`
All vulfram_* functions return a u32 status code:
0→ success (VULFRAM_SUCCESS)- non-zero → error (
VulframResultenum)
The binding must map this to language-level error handling. For example:
- N-API: throw JS Error on non-zero
- Python: raise Python exception
- Lua: return
nil, error throw error, depending on style
The exact enum values live in the Rust core, but the important contract is:
0= OK- Non-zero = failure (with possible subcodes for detailed errors)
1.2 Threading / Reentrancy
- All
vulfram_*functions are main-thread only. - The host must not call them concurrently from multiple threads.
- Any multithreading should be implemented on the host side, with a single thread delegating to Vulfram.
1.3 Serialization Format
All structured data that crosses the ABI uses MessagePack, via:
serdermp-serde
This includes:
- Command batches (
vulfram_send_queue) - Response batches (
vulfram_receive_queue) - Event batches (
vulfram_receive_events) - Profiling data (
vulfram_get_profiling)
Binding responsibilities:
- Serialize commands into MessagePack when calling
send_queue. - Deserialize MessagePack returned by
receive_queue,receive_events,get_profiling.
1.4 Output Buffers (`out_ptr`, `out_length`)
Several functions return data via pointer-out parameters:
u32 vulfram_xxx(uint8_t** out_ptr, size_t* out_length);
Contract:
Vulfram allocates a contiguous byte buffer and sets:
*out_ptrto the buffer address*out_lengthto the number of bytes
The caller on the C side (the binding) is responsible for:
- Copying the bytes into its own memory representation (JS Buffer, Python bytes, Lua string, etc.)
- Releasing the buffer using the deallocation mechanism defined in the dynamic library / binding.
The game code written in JS/Lua/Python never handles raw pointers. The binding hides this complexity.
2. Function List
All functions have the vulfram_ prefix and use C-ABI (extern "C").
2.1 Initialization and Shutdown
u32 vulfram_init(void);
u32 vulfram_dispose(void);
vulfram_init()- Initializes the core state, subsystems, and any global allocations.
- Must be called exactly once before any other function.
vulfram_dispose()- Shuts down the core, frees resources, and tears down subsystems.
- After calling this, no other
vulfram_*functions may be used unless a freshvulfram_init()is called (full restart).
2.2 Command Queue (Host → Core)
u32 vulfram_send_queue(const uint8_t* buffer, size_t length);
bufferPointer to a MessagePack-encoded batch of commands.lengthSize of the buffer in bytes.
Core behavior:
- Copies the
buffercontents into internal memory. - Decodes it into one or more internal command structures.
- Enqueues these commands to be processed on the next
vulfram_tick().
Typical commands include:
- Create/update/destroy resources (textures, geometries, materials…)
- Create/update/destroy components (cameras, models, etc.)
- Maintenance commands (e.g. discard unused uploads).
2.3 Response Queue (Core → Host)
u32 vulfram_receive_queue(uint8_t** out_ptr, size_t* out_length);
On success, returns a MessagePack buffer with a batch of responses:
- Acknowledgments, detailed error info, notifications, etc.
The buffer may be empty:
*out_length == 0indicates “no responses available”.
Binding responsibilities:
- Call
vulfram_receive_queue(&ptr, &len). - If
len > 0, copy[ptr .. ptr+len)to host memory. - Release the core-allocated buffer using the agreed mechanism.
- Deserialize MessagePack and route responses to the host/application.
Calling vulfram_receive_queue consumes and clears the response queue.
2.4 Event Queue (Input / Window)
u32 vulfram_receive_events(uint8_t** out_ptr, size_t* out_length);
- Returns a MessagePack buffer containing a batch of events:
- Keyboard, pointer, touch, gamepad (Gilrs on native, Gamepad API on web)
- Window system events (resize, close, focus, etc.) via the platform proxy
- System diagnostic events (
SystemEvent::Error) for command failures and routing/auto-graph diagnostics
Semantics are identical to vulfram_receive_queue, but the content is
strictly event data, not responses.
Typical flow:
- The core collects events during
vulfram_tick(). - The host calls
vulfram_receive_events(). - Binding copies and frees the buffer.
- Binding decodes events and forwards them into the host-side input system.
2.5 Upload of Raw Blobs
u32 vulfram_upload_buffer(uint64_t id,
uint32_t type,
const uint8_t* buffer,
size_t length);
2.5.1 `vulfram_upload_buffer`
Direction: Host → Core
Parameters:
idBufferIdchosen by the host. Used to reference this upload in later commands.typeUpload type (host-facing values are kebab-case strings):"raw""shader-source"(reserved)"geometry-data""vertex-data""index-data""image-data""binary-asset"
For the C ABI, pass the numeric mapping:
raw(0)shader-source(1)geometry-data(2)vertex-data(3)index-data(4)image-data(5)binary-asset(6)
buffer,lengthPointer and size of the raw data.
Behavior:
- The core copies the contents into its internal upload table.
- Later, commands (via
send_queue) such as geometry/texture creation look up these uploads byBufferIdandtype. - Uploads are treated as one-shot: once consumed by a
Create*command, they may be removed from the upload table.
2.6 Tick / Frame Advance
u32 vulfram_tick(uint64_t time, uint32_t delta_time);
Called once per frame by the host.
Parameters:
timeHost-provided time (forwarded to frame uniforms).delta_timeHost-provided delta time (forwarded to frame uniforms).
Core responsibilities in tick:
- Process pending commands enqueued via
send_queue. - Update internal component state (transforms, camera matrices, etc.).
- Collect input & window events from the active platform proxy.
- Execute the render pipeline on the GPU using WGPU.
- Populate internal queues for:
- responses (
receive_queue) - events (
receive_events) - profiling data (
get_profiling)
- responses (
2.7 Asynchronous Resource Linking (Fallback-Driven)
Resources can be created out of order:
- Models can reference geometry or material IDs that do not exist yet.
- Materials can reference texture IDs that do not exist yet.
When a referenced resource is missing, the core uses fallback resources so rendering continues. When the real resource appears later with the same ID, the core picks it up automatically on the next frame.
2.8 Resource Reuse Semantics
- A single geometry can be referenced by many models.
- A single material can be referenced by many models.
- A single texture can be referenced by many materials.
There is no ownership tracking. If a resource is disposed while still referenced, rendering falls back gracefully.
2.9 Profiling
u32 vulfram_get_profiling(uint8_t** out_ptr, size_t* out_length);
- Returns a MessagePack buffer containing profiling information:
- Timings in microseconds:
commandProcessingUs,gamepadProcessingUs,eventLoopPumpUsrequestRedrawUs,serializationUsrenderTotalUs,renderShadowUs,renderWindowsUsframeDeltaUs
- Derived:
fpsInstant
- Counters:
totalEventsDispatched,totalEventsCached
- Per-window:
windowFps[]withwindowId,fpsInstant,frameDeltaUs
- Timings in microseconds:
Usage patterns:
- Continuous debug mode: call each frame to build an in-app debug overlay.
- On-demand: call only when the user/dev requests profiling info.
Contract:
- Same as other
out_ptrfunctions:- binding copies the data, frees the buffer, then decodes MessagePack.
3. Recommended Frame Loop Contract
While the core is host-agnostic, we recommend the following call order per frame on the main thread:
Prepare game logic (host side)
- Run ECS / game systems.
- Decide which components/resources need to be created/updated.
- Optionally prepare uploads (meshes, textures, etc.).
Uploads (optional, zero or more calls)
- For each heavy blob:
vulfram_upload_buffer(buffer_id, type, ptr, length)
- For each heavy blob:
Send commands
- Build a MessagePack batch of commands (create/update components & resources, maintenance commands).
vulfram_send_queue(buffer, length)
Advance the core
vulfram_tick(time, delta_time)
Receive responses (consumes response queue)
vulfram_receive_queue(&ptr, &len)- If
len > 0:- copy & free buffer
- decode MessagePack and process responses
Receive events
vulfram_receive_events(&ptr, &len)- If
len > 0:- copy & free buffer
- decode MessagePack and feed the host’s input/window system
Profiling (optional)
vulfram_get_profiling(&ptr, &len)- If
len > 0:- copy & free buffer
- decode and feed debug UI / logs
4. Error Handling Guidelines for Bindings
On non-zero return from any
vulfram_*function:- The binding should surface an error in the host language.
For debuggability, the core may emit additional structured error info into
receive_queue, which the host can poll and log/visualize.
A typical pattern:
- Binding call fails (non-zero
VulframResult). - Binding throws/returns an error.
- Host can additionally call
vulfram_receive_queueto fetch detailed error responses, if desired.