Native Plugins¶
While Squirrel handles gameplay logic gracefully, certain operations - like heavy mathematical simulations, custom rendering pipelines, or integrating massive third-party C/C++ libraries - require raw execution speed.
Nutshell supports loading native shared libraries (.dll, .so, .dylib)
dynamically at runtime. This allows you to write performance-critical code in
systems languages like Go, C, C++, or Rust, and expose it seamlessly to your
Squirrel scripts.
The Compatibility Signature¶
To securely interface with the active Nutshell virtual machine, every native plugin must expose a specific C-ABI compatible entry point.
When your script calls load("myplugin"), Nutshell dynamically calculates the
required symbol name by taking the plugin's name, converting it to lowercase,
and prepending ns_open_.
Nutshell expects this exported function to accept a pointer to the virtual machine alongside Nutshell's major and minor semantic version numbers, and a bound native API pointer.
This versioning allows your plugin to validate compatibility before attempting to register functions, preventing issues if Nutshell's internal data structures have changed.
The native API integration enable direct interaction with Nutshell rendering capabilities.
Signature Definition¶
Your plugin must export a function matching this exact signature:
#include <stdint.h>
#if defined(_WIN32)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif
// Example for load("myplugin")
extern "C" EXPORT int ns_open_myplugin(uintptr_t vm, int engine_major, int engine_minor, uintptr_t native_api) {
// Validate engine version and store native_api pointer if needed
// Bind your native functions into the VM
return 0; // Return 0 on success, non-zero on failure
}
Creating a Plugin in Go¶
Because Nutshell itself is built with Go, you can easily author
high-performance native plugins using Go's c-shared build mode.
Below is a complete example of creating a simple Arithmetics plugin that
exposes native addition and multiplication functions to Nutshell scripts.
The plugin¶
Create a new directory named arithmetics and add the following main.go file.
This example uses the lenain.info/nutshell/squirrel package to interact
directly with the VM stack.
package main
import "C"
import (
"fmt"
. "lenain.info/nutshell/squirrel"
)
//export ns_open_arithmetics
func ns_open_arithmetics(vm uintptr, major int, minor int, nativeAPI uintptr) C.int {
// Always initialize the Squirrel bindings for the plugin context
if err := SQLoadSquirrel(); err != nil {
fmt.Printf("arithmetics: failed to load squirrel in plugin: %v\n", err)
return 1
}
// Optional: Log version matching
fmt.Printf("Arithmetics Plugin: Loaded by Nutshell v%d.%d\n", major, minor)
// Push the root table to the top of the stack
SQ_pushroottable(vm)
// Push the name of our new module/class
SQ_pushstring(vm, "Arithmetics", -1)
// Create a new table to hold our functions
SQ_newtable(vm)
// Helper function to bind native Go functions to Squirrel slots
register := func(name string, fn func(uintptr) int) {
SQ_pushstring(vm, name, -1)
id := SQRegisterCallback(fn)
SQ_pushuserpointer(vm, id)
SQ_newclosure(vm, SQClosureDispatcher(), 1)
SQ_newslot(vm, -3, SQFalse)
}
// Register our math functions
register("add", nativeAdd)
register("multiply", nativeMultiply)
// Bind the table to the root table under the name "Arithmetics"
SQ_newslot(vm, -3, SQFalse)
// Pop the root table off the stack
SQ_pop(vm, 1)
return 0 // Success
}
// nativeAdd extracts two integers from the VM stack and returns their sum.
func nativeAdd(vm uintptr) int {
// Parameter 1 is the hidden 'this' pointer.
// We expect 2 additional arguments: Parameter 2 and Parameter 3.
// Parameter 4 is the upvalue callback ID attached to the closure
if SQ_gettop(vm) != 4 {
return SQ_throwerror(vm, "Arithmetics.add: expected exactly 2 arguments")
}
a := SQFloat(vm, 2, 0)
b := SQFloat(vm, 3, 0)
// Push the result back onto the stack
SQ_pushfloat(vm, float32(a+b))
// Return 1 to indicate to Squirrel that we returned a value
return 1
}
// nativeMultiply extracts two floats from the VM stack and returns their product.
func nativeMultiply(vm uintptr) int {
if SQ_gettop(vm) != 4 {
return SQ_throwerror(vm, "Arithmetics.multiply: expected exactly 2 arguments")
}
a := SQFloat(vm, 2, 0.0)
b := SQFloat(vm, 3, 0.0)
SQ_pushfloat(vm, float32(a*b))
return 1
}
// Required for c-shared build mode
func main() {}
Building the shared library¶
To compile the Go code into a shared library that Nutshell can load, you must
enable CGO and set the build mode to c-shared.
You can use the following Makefile commands to compile for both Windows and Linux environments for either static or shared Nutshell version:
# Cross-compilation environments (CGO is strictly required for c-shared)
LINUX_ENV = CGO_ENABLED=1 GOOS=linux GOARCH=amd64
WIN_ENV = CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc
# Output directory
OUT_DIR = .
.PHONY: all static shared clean
all: static
# Build plugins intended for the static nutshell engine
static:
@echo ">> Building Go plugin for static engine..."
$(LINUX_ENV) go build -ldflags="-s -w" -tags "plugin" -buildmode=c-shared -o $(OUT_DIR)/arithmetics.so
$(WIN_ENV) go build -ldflags="-s -w" -tags "plugin" -buildmode=c-shared -o $(OUT_DIR)/arithmetics.dll
# Build plugins intended for the shared nutshell engine
shared:
@echo ">> Building Go plugin for shared engine..."
$(LINUX_ENV) go build -ldflags="-s -w" -tags "plugin squirrel_shared" -buildmode=c-shared -o $(OUT_DIR)/arithmetics.so
$(WIN_ENV) go build -ldflags="-s -w" -tags "plugin squirrel_shared" -buildmode=c-shared -o $(OUT_DIR)/arithmetics.dll
# Clean generated binaries and the auto-generated C header files
clean:
@echo ">> Cleaning Go plugin builds..."
rm -f *.so *.dll *.h
Using the plugin in your scripts¶
Once compiled, place arithmetics.so (or .dll) in your project's directory.
You can now load and execute your native functions directly from your script.
// Load the native plugin.
// Nutshell handles the platform-specific extensions (.so / .dll) automatically.
load("arithmetics")
function update(dt) {
// The plugin registered the 'Arithmetics' table globally
local sum = Arithmetics.add(10, 5)
local product = Arithmetics.multiply(3.14, 2.0)
print("Native Sum: " + sum + "\n") // 15
print("Native Product: " + product + "\n") // 6.28
}