Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

WASM Plugins

Requires feature: plugin_runtime

Nightshade includes a WASM-based plugin system that allows loading and executing WebAssembly modules at runtime. Plugins can spawn entities, manipulate transforms, load assets, and respond to input events.

Feature Flags

[dependencies]
nightshade = { version = "...", features = ["plugin_runtime"] }

The plugin_runtime feature enables the Wasmtime-based runtime and WASI support. The base plugins feature provides the shared command/event types for writing plugin guest code. Desktop only.

Plugin Runtime

Loading Plugins

#![allow(unused)]
fn main() {
let mut runtime = PluginRuntime::new(PluginRuntimeConfig {
    plugins_path: "plugins/".to_string(),
    ..Default::default()
});

runtime.load_plugin("plugins/my_plugin.wasm");
runtime.load_plugins_from_directory("plugins/");
}

Running Plugins Each Frame

#![allow(unused)]
fn main() {
fn run_systems(&mut self, world: &mut World) {
    runtime.run_plugins_frame(world);
    runtime.process_pending_commands(world);
}
}

run_plugins_frame() calls each plugin's on_frame() export. process_pending_commands() executes any engine commands the plugins have queued.

Plugin Lifecycle

Plugins are compiled WASM modules that export specific functions:

ExportRequiredDescription
on_init()NoCalled once when the plugin is loaded
on_frame()NoCalled every frame
plugin_alloc(size) -> *mut u8YesMemory allocation for receiving events
plugin_receive_event(ptr, len)YesReceives serialized events from the engine

Engine Commands

Plugins send commands to the engine through a host-provided API:

CommandDescription
SpawnPrimitiveCreate a cube, sphere, cylinder, plane, or cone
DespawnEntityRemove an entity
SetEntityPositionSet entity world position
SetEntityScaleSet entity scale
SetEntityRotationSet entity rotation
GetEntityPositionRequest entity position (async)
GetEntityScaleRequest entity scale (async)
GetEntityRotationRequest entity rotation (async)
SetEntityMaterialSet material properties
SetEntityColorSet entity color
LoadTextureLoad a texture by path
LoadPrefabLoad a glTF prefab
ReadFileRead a file from disk
LogPrint a message to the host console

Guest API

From within a plugin:

#![allow(unused)]
fn main() {
use nightshade::plugin::*;

fn on_init() {
    let entity_id = spawn_primitive(Primitive::Cube, 0.0, 1.0, 0.0);
    set_entity_position(entity_id, 5.0, 2.0, 0.0);
    log("Plugin initialized!");
}
}

Engine Events

Events sent from the engine to plugins:

EventDescription
FrameStartNew frame beginning
KeyPressed / KeyReleasedKeyboard input
MouseMovedMouse position change
MouseButtonPressed / MouseButtonReleasedMouse button input
EntitySpawnedEntity creation confirmed with host entity ID
FileLoaded / TextureLoaded / PrefabLoadedAsync asset load results
ErrorError notification

Entity ID Mapping

Plugins use their own entity ID space. The runtime maintains a bidirectional mapping between plugin entity IDs and host entity IDs. When a plugin spawns an entity, it receives a local ID immediately and gets the real host ID through an EntitySpawned event.

Custom Linker Functions

Extend the plugin API with custom host functions:

#![allow(unused)]
fn main() {
runtime.with_custom_linker(|linker| {
    linker.func_wrap("env", "my_custom_function", |param: i32| -> i32 {
        param * 2
    });
});
}

Platform Notes

  • Desktop only (uses Wasmtime, not available on WASM targets)
  • Full WASI P1 support for file I/O within plugins
  • Memory-safe communication via postcard serialization
  • Automatic cleanup of plugin resources on configurable intervals