Warning This project is still in its infancy. Interfaces are liable to change with each release until v1.0.
A Godot addon allowing for loading and interacting with WebAssembly (Wasm) modules from GDScript. Note that this project is still in development.
Godot Wasm can be used either as a GDNative addon or Godot module. It uses Wasmer as the WebAssembly runtime.
Godot Wasm supports installation via addon or module. Installing as an addon is far faster and simpler and requires merely including the asset in your Godot project while installing as a module requires recompilation of the Godot engine.
Installation in a Godot project involves simply downloading and installing a zip file from Godot's UI. Recompilation of the engine is not required.
- Download the Godot Wasm addon zip file from the releases page.
- In Godot's Asset Library tab, click Import and select the addon zip file. Follow prompts to complete installation of the addon.
Alternatively, you can use the Asset Library tab within the Godot editor, search for "Wasm", and follow the prompts to install. Yet another alternative is downloading directly from the asset page and following the installation instructions above. Note that the Asset Library has an approval process that can take several days and may therefore be a version or two behind.
Installation as a Godot module requires recompilation of the Godot engine.
- Clone or download the Godot engine following this guide.
- Download the Godot Wasm source via the releases page or Code → Download ZIP on GitHub.
- Include the entire Godot Wasm directory within the godot/modules directory.
- Rename the Godot Wasm directory to wasm. All project files e.g. SCsub should now be in godot/modules/wasm.
Recompile the Godot engine following this guide. More information on custom Godot modules can be found in this guide.
Compiling the web/HTML5 export template is not yet supported (see #18).
Once installed as an addon in a Godot project, the Godot Wasm addon class can be accessed via Wasm
.
- Create a Wasm module or use the example module.
- Add the Wasm module to your Godot project.
- In GDScript, instantiate the
Wasm
class viavar wasm = Wasm.new()
. - Load your Wasm module bytecode as follows replacing
YOUR_WASM_MODULE_PATH
with the path to your Wasm module e.g. example.wasm. TheWasm.load()
method accepts a PoolByteArray and a dictionary defining Wasm module imports. All imports should be satisfied and may differ with each Wasm module.var file = File.new() file.open("res://YOUR_WASM_MODULE_PATH", File.READ) var buffer = file.get_buffer(file.get_len()) var imports = { "functions": { "index.callback": [self, "callback"] } } # Set imports according to Wasm module wasm.load(buffer, imports) file.close()
- Access global constants and mutables exported by the Wasm module via
wasm.global("YOUR_GLOBAL_NAME")
replacingYOUR_GLOBAL_NAME
with the name of an exported Wasm module global. - Define an array containing the arguments to be supplied to your exported Wasm module function via
var args = [1, 2]
. Ensure the number of arguments and argument types match those expected by the exported Wasm module function. - Call a function exported by your Wasm module via
wasm.function("YOUR_FUNCTION_NAME", args)
replacingYOUR_FUNCTION_NAME
with the name of the exported Wasm module function.
Note Exporting to web/HTML5 is not supported. See #15 and #18.
Exporting to Windows, macOS, and Linux is officially supported. Exporting from Godot may require the following additional steps. See the export configuration of the example Godot project for a practical illustration.
- For macOS exports, disable library validation in Project → Export → Options.
- If your project contains Wasm files, they'll need to be marked for export. Add
*.wasm
in Project → Export → Resources.
Writing data to exported Wasm memory is supported via a familiar StreamPeer interface. This StreamPeer is available under the stream
property of the Wasm
object.
The internal StreamPeerWasm class mirrors the seek()
and get_position()
methods of StreamPeerBuffer with the addition of seek()
returning a reference to the StreamPeer allowing chaining i.e. wasm.stream.seek(0).get_u64()
.
Note that, as mentioned in Godot's StreamPeer documentation, writing strings via put_string()
and put_utf8_string()
will prepend four bytes containing the length of the string.
Examples are provided for both creating and consuming/using a Wasm module.
A simple example of loading a Wasm module, displaying the structure of its imports and exports, calling its exported functions, and providing GDScript callbacks via import functions. Some computationally-expensive benchmarks e.g. prime sieve in GDScript and Wasm can be compared. The Wasm module used is that generated by the Wasm Create example. All logic for this Godot project exists in Main.gd
.
An example AssemblyScript project which creates a simple Wasm module with the following exported entities.
Entity | Type | Description |
---|---|---|
global_const |
Global i64 |
Global constant |
global_var |
Global f64 |
Global mutable |
memory_value |
Global i64 |
Used to store first eight bytes of memory to demonstrate memory manipulation |
update_memory |
Function () → void |
Updates the value of memory_value |
add |
Function (i64, i64) → i64 |
Adds two integers |
fibonacci |
Function (i64) → i64 |
Return Fibonacci number at the position provided |
sieve |
Function (i64) → i32 |
Return largest prime number up to the limit provided |
From the example directory (examples/wasm-create), install or update Node modules npm i
. Run npm run build
to build the Wasm module. This will create and populate a build directory containing the Wasm module, example.wasm, amongst other build artifacts. If you have the Wasmer CLI installed, run wasmer inspect build/example.wasm
to view the Wasm module imports and exports.
Note that WebAssembly is a large topic and thoroughly documenting the creation of Wasm modules is beyond the scope of this project. AssemblyScript is just one of many ways to create a Wasm module.
An example of displaying graphics generated by Wasm modules. Graphics are read directly from the Wasm module memory using the StreamPeer interface.
Comparison of GDScript, GDNative, and Wasm (n=1000, p95). The following benchmarks were run on macOS 12.6.3, 16GB RAM, 2.8 GHz i7. The project was exported to avoid GDScript slowdown likely caused by performance monitoring. Speedup figures for both GDNative and Wasm are relative to GDScript. The benchmarks used are 1) a DP Nth Fibonacci number and 2) a Sieve of Atkin.
Benchmark | GDScript | GDNative | WRT GDScript | Wasm | WRT GDScript |
---|---|---|---|---|---|
Fibonacci (index=20) | 6.0 µs | 1.0 µs | 6.0x | 4.0 µs | 1.5x |
Fibonacci (index=50) | 13.0 µs | 1.0 µs | 13.0x | 4.0 µs | 3.3x |
Sieve (limit=1000) | 704.1 µs | 4.0 µs | 176.0x | 30.0 µs | 23.5x |
Sieve (limit=10000) | 6331.8 µs | 52.0 µs | 121.8x | 134.1 µs | 47.2x |
Sieve (limit=100000) | 62454.1 µs | 341.0 µs | 183.1x | 1053.1 µs | 59.3x |
This section is to aid in developing the Godot Wasm addon; not to use the addon in a Godot project.
These instructions are tailored to UNIX machines.
- Clone repo and submodules via
git clone --recurse-submodules https://github.com/ashtonmeuser/godot-wasm.git
. - Ensure the correct Godot submodule commit is checked out. Refer to relevant branch of the godot-cpp project e.g.
3.x
to verify submodule hash. At the time of this writing, the hash for the godot-cpp submodule was1049927a596402cd2e8215cd6624868929f5f18d
. - Download the Wasmer C library from Wasmer releases and add them to a wasmer directory at the root of this project. There should be include and lib subdirectories within the wasmer directory.
- Install SConstruct via
pip install SCons
. SConstruct is what Godot uses to build Godot and generate C++ bindings. For convenience, we'll use the same tool to build the Godot TF Inference addon. - Compile the Godot C++ bindings. From within the godot-cpp directory, run
scons target=release platform=PLATFORM generate_bindings=yes
replacingPLATFORM
with your relevant platform type e.g.osx
,linux
,windows
, etc. - Compile the Godot Wasm addon. From the repository root directory, run
scons platform=PLATFORM
once again replacingPLATFORM
with your platform. This will create the addons/godot-wasm/bin/PLATFORM directory wherePLATFORM
is your platform. You should see a dynamic library (.dylib, .so, .dll, etc.) created within this directory. - Copy the Wasmer dynamic libraries to the appropriate platform directory via
cp -RP wasmer/lib/. addons/godot-wasm/bin/PLATFORM/
replacingPLATFORM
with your platform. - Zip the addons directory via
zip -FSr addons.zip addons
. This allows the addon to be conveniently distributed and imported into Godot. This zip file can be imported directly into Godot (see Installation).
If frequently iterating on the addon using a Godot project, it may help to create a symlink from the Godot project to the compiled addon via ln -s RELATIVE_PATH_TO_ADDONS addons
from the root directory of your Godot project.
- No WASI bindings are provided to the Wasm module. This means that the guest Wasm module has no access to the host machines filesystem, etc. Pros for this are simplicity and increased security. Cons include not being able to generate truly random numbers (without a workaround) or run Wasm modules created in ways that require WASI bindings e.g. TinyGo (see relevant issue).
- Only
int
andfloat
return values are supported. While workarounds could be used, this limitation is because the only concrete types supported by Wasm are integers and floating point. - A default empty
args
parameter forfunction(name, args)
can not be supplied. DefaultArray
parameters in GDNative seem to retain values between calls. Calling methods of this addon without expected arguments produces undefined behaviour. This is reliant on godotengine/godot-cpp#209. - Web/HTML5 export is not supported (see #15 and #18).
There have been numerous discussions around modding/sandboxing support for Godot. Some of those are included below.
- Proposal: Implement a sandbox mode
- Issue Add support for WebAssembly plugins and scripts
- Proposal Add WASM (WASI) host support (including, but not limited to, the HTML5 target)
- Proposal: Add a method to disallow using all global classes in a particular GDScript
- Pull Request: Added globals disabled feature to GDScript class
Please feel free submit a PR or an issue.
- Load module
- Export functions
- Export global constants
- Export global mutables
- Export tables
- Export memories
- Import functions
- Import globals
- Map export names to indices (access function/global by name)
- Inspect module exports
- Automatically provide AssemblyScript special imports
- Automatically cast to 32-bit values
- Inspect function signatures
- Set export globals