Appearance
Nim
std/dynlib and std/json cover everything; no packages are needed.
Install
bash
tar xzf guido-0.1.0-linux-x86_64.tar.gz
./guido-0.1.0-linux-x86_64/install.sh # installs ~/.local/lib/libguido.soExample
A button that responds to clicks (nim r -d:release main.nim):
nim
import std/[dynlib, json, os]
type
GuidoStart = proc (argc: cint, argv: cstringArray): cint {.cdecl.}
GuidoIteration = proc (): cint {.cdecl.}
GuidoSend = proc (line: cstring): cint {.cdecl.}
GuidoRecv = proc (): cstring {.cdecl.}
GuidoStop = proc () {.cdecl.}
let lib = loadLib(getEnv("HOME") / ".local/lib/libguido.so")
let guidoStart = cast[GuidoStart](lib.symAddr("guido_start"))
let guidoIteration = cast[GuidoIteration](lib.symAddr("guido_iteration"))
let guidoSend = cast[GuidoSend](lib.symAddr("guido_send"))
let guidoRecv = cast[GuidoRecv](lib.symAddr("guido_recv"))
let guidoStop = cast[GuidoStop](lib.symAddr("guido_stop"))
proc send(v: JsonNode) = discard guidoSend(cstring($v))
let argv = allocCStringArray(["--no-project"])
doAssert guidoStart(1, argv) == 0
deallocCStringArray(argv) # guido copies the args during start
send(%*{"jsonrpc": "2.0", "id": 1, "method": "handshake",
"params": {"clientInfo": {"name": "nim", "version": "0"}}})
send(%*{"jsonrpc": "2.0", "id": 2, "method": "instantiate",
"params": {"className": "Button"}})
var rootId, button: string
while guidoIteration() == 0: # ends when the window is closed
while true:
let p = guidoRecv()
if p.isNil: break
let msg = parseJson($p) # `$` copies; safe past the next recv
let id = if msg.hasKey("id"): msg["id"].getInt() else: 0
if id == 1:
rootId = msg["result"]["rootId"].getStr()
elif id == 2:
button = msg["result"]["oid"].getStr()
send(%*{"jsonrpc": "2.0", "method": "batch", "params": {"ops": [
{"method": "set", "params": {"oid": button, "property": "text", "value": "Click me"}},
{"method": "addChild", "params": {"parent": rootId, "child": button}}]}})
send(%*{"jsonrpc": "2.0", "id": 3, "method": "connect",
"params": {"oid": button, "signal": "pressed"}})
elif msg.hasKey("method") and msg["method"].getStr() == "signal":
echo "clicked"
guidoStop()A version with request routing is in the repo at examples/embedding/nim/.
Scenes from the Godot editor
The message shapes are documented on the C page and are identical here:
nim
send(%*{"jsonrpc": "2.0", "id": 10, "method": "loadPack",
"params": {"path": absolutePath("app.pck")}}) # absolute path
send(%*{"jsonrpc": "2.0", "id": 11, "method": "instantiateScene",
"params": {"path": "res://main.tscn"}})
send(%*{"jsonrpc": "2.0", "id": 12, "method": "call",
"params": {"oid": scene, "method": "get_node", "args": ["%SaveButton"]}})
# reply: {"result":{"result":{"$oid":"..."}}}; connect "pressed" on that oidStatic linking
nim
{.passL: "/path/to/libguido.a -lstdc++ -lm -lpthread -ldl".}
proc guido_start(argc: cint, argv: cstringArray): cint {.importc, cdecl.}
# the other four likewise (signatures in guido.h)The result is one self-contained ~22 MB binary. The archive must be built with lto=none; see Building from source.
Notes
$cstringcopies; do it before the nextguidoRecv.- One thread owns
start/iteration/stop;sendis thread-safe. For async programs, run the loop in a thread and forward lines over aChannel. - Batch fire-and-forget operations into one
batchmessage per frame; asyncrequest is the barrier before reads. - Route
signalnotifications bysubIdfrom theconnectreply. - Shutdown: run two more
guidoIterationcalls after the last sends, thenguidoStop(), then drain once for the finalquitnotification.