This project is under active development! We may introduce breaking changes at any time.
Cross-platform Native SDK for Colyseus. Aimed to be used for all native targets, such as Godot, Unreal Engine, Game Maker, and more.
| Release | Description | Platforms |
|---|---|---|
| Godot | GDExtension plugin for Godot 4.x | Windows, macOS, Linux, iOS, Android, Web |
| GameMaker | Native extension for GameMaker | Windows, macOS, Linux, iOS, Android, HTML5 (WASM) |
| Static Binaries | Pre-built static libraries (C API) | Windows, macOS, Linux, iOS, WebAssembly |
| Swift | Swift package (SwiftPM) | macOS, iOS, tvOS |
Requires Zig 0.15.x (0.16 is not supported yet).
git submodule update --init --recursive
zig build
# Run example
zig build run-exampleTests: zig build test, or zig build test -Dskip-integration=true without
the servers. See tests/README.md.
The whole public API is behind one header, and zig-out/include is the only
include path it needs:
#include <colyseus.h>A static build ships the libraries it links against as separate archives — mbedTLS, wslay and the Zig modules — because an archive does not absorb its dependencies. Pass the whole directory and let the linker sort it out:
cc app.c -I zig-out/include zig-out/lib/*.a -lpthread \
-framework CoreFoundation -framework Security # macOS/iOSOn Linux add -lm; on Windows link ws2_32, crypt32 and bcrypt. A shared
build (-Dshared=true) has already absorbed the closure, so -lcolyseus alone
is enough there.
From a Zig project, linking the artifact carries both its header tree and the
whole closure — no addIncludePath, nothing else to name:
const colyseus = b.dependency("colyseus", .{ .target = target, .optimize = optimize });
exe.linkLibrary(colyseus.artifact("colyseus"));Everything a room does with an incoming frame — schema decode, on_change /
on_add / listen callbacks, prediction bookkeeping — runs on the thread that
drives the socket. For a native app with a frame loop, the recommended setup is
polled mode: switch it on once at startup, before creating a client, then call
colyseus_poll() once per frame from the thread that reads room state.
colyseus_set_polled(true); // at startup, before connecting
// every frame, on the thread that reads room state:
colyseus_poll(); // socket IO, decode and every callback happen hereIn polled mode no SDK thread ever touches your state: matchmaking results, room
and state callbacks, auto-reconnection and latency probes are all delivered
inside colyseus_poll(), on the thread that calls it. A send from that thread
goes out immediately; a send from any other thread waits for the next poll.
Threaded delivery is still the default (a later release will flip it): each socket runs its own tick thread, matchmaking and reconnection report from worker threads, and reading room state from your main loop races the decoder.
The Godot and GameMaker bindings run polled for you. The full contract is in
include/colyseus/client.h and
include/colyseus/websocket_transport.h.
native-sdk/
├── build.zig # Build configuration
├── include/ # Public API headers
├── src/ # Implementation
├── examples/ # Example programs
├── docs/ # Documentation
└── third_party/ # Dependencies (cJSON, sds, uthash, wslay)
- cJSON - JSON parser (included)
- sds - String library (included)
- uthash - Hash table library (included)
- wslay - WebSocket library (included)
- mbedTLS - TLS library (system install required)
Work in progress. API is subject to change.
See LICENSE file for details.