Adding client-side and server-side interactions. Also adds purpur/paper support
This commit is contained in:
@@ -1,31 +1,108 @@
|
||||
# Go Minecraft Bridge
|
||||
|
||||
Go Minecraft Bridge is a Fabric server-side host for plugins written in Go. The
|
||||
Minecraft-facing code stays in Java, while plugin logic receives immutable tick
|
||||
snapshots and returns actions or named system calls.
|
||||
Go Minecraft Bridge hosts plugins written in Go in both Minecraft server and
|
||||
client processes. The Minecraft-facing code stays in Java, while plugin logic
|
||||
receives immutable snapshots/events and returns actions or named system calls.
|
||||
|
||||
The current MVP targets:
|
||||
The shared code is built as two version-specific Fabric artifacts:
|
||||
|
||||
- Minecraft `26.1.2`, Fabric Loader `0.19.3`, Fabric API `0.149.1+26.1.2`;
|
||||
- Java 25 and Go 1.24 or newer;
|
||||
| Minecraft | Java | Loader | Fabric API | Cloth Config | Mod Menu |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| `1.21.1` | 21 | `0.16.14` | `0.116.14+1.21.1` | `15.0.140` | `11.0.3` |
|
||||
| `26.1.2` | 25 | `0.19.3` | `0.149.1+26.1.2` | `26.1.154` | `18.0.0` |
|
||||
|
||||
Architectury Loom drives the portable 1.21.1 target, with a small source
|
||||
overlay for Minecraft API differences. The unobfuscated 26.1.2 target uses
|
||||
Fabric Loom because Architectury Loom currently requires a mappings artifact
|
||||
that this Minecraft distribution does not publish. Architectury portability
|
||||
still produces one JAR per Minecraft ABI; it does not make one universal JAR.
|
||||
|
||||
The current MVP also targets:
|
||||
|
||||
- Go 1.24 or newer;
|
||||
- native Go plugins built with `-buildmode=c-shared`;
|
||||
- initialization, tick, chat, death, system-call-result, and deinitialization callbacks;
|
||||
- initialization, server/client tick, chat, death, system-call-result, and deinitialization callbacks;
|
||||
- entity snapshots and explicit subscriptions to block positions;
|
||||
- chat broadcast/direct-message actions;
|
||||
- extensible namespaced system calls;
|
||||
- capture of Go `stdout`, `stderr`, and the standard `log` package.
|
||||
- capture of Go `stdout`, `stderr`, and the standard `log` package;
|
||||
- FlatBuffers tick snapshots (ABI v2), while control-plane messages remain JSON;
|
||||
- a Cloth Config management screen exposed through Mod Menu.
|
||||
|
||||
The backend interface is deliberately independent from Fabric and native FFI so
|
||||
that a WASI/WASM backend can implement the same protocol later.
|
||||
|
||||
The server runtime is also packaged as one Paper plugin compatible with Paper
|
||||
and Purpur. Its Bukkit-facing code is compiled during every build against this
|
||||
API matrix:
|
||||
|
||||
| Server | Paper API used for verification | Java runtime |
|
||||
|---|---|---:|
|
||||
| Paper/Purpur `1.21.1` | `1.21.1-R0.1-SNAPSHOT` | 21+ |
|
||||
| Paper/Purpur `1.21.11` | `1.21.11-R0.1-SNAPSHOT` | 21+ |
|
||||
| Paper/Purpur `26.1` | `26.1.2.build.74-stable` | 25+ |
|
||||
|
||||
The Paper API preserves the Bukkit surface used by the bridge, so these targets
|
||||
share one Java 21 bytecode JAR. Purpur accepts it directly because Purpur is a
|
||||
Paper-compatible server implementation.
|
||||
|
||||
## Build and try the example
|
||||
|
||||
Build the Fabric mod:
|
||||
Build both Fabric targets:
|
||||
|
||||
```bash
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
The production JARs are written to:
|
||||
|
||||
```text
|
||||
versions/1.21.1/build/libs/go-minecraft-bridge-1.21.1-<version>.jar
|
||||
versions/26.1.2/build/libs/go-minecraft-bridge-26.1.2-<version>.jar
|
||||
platforms/paper/build/libs/go-minecraft-bridge-paper-<version>.jar
|
||||
```
|
||||
|
||||
Run a development client for one target with `./gradlew :mc1211:runClient` or
|
||||
`./gradlew :mc2612:runClient`.
|
||||
|
||||
## Paper and Purpur installation
|
||||
|
||||
Copy the shaded Paper JAR to the server's `plugins` directory, start the server
|
||||
once, and put native Go libraries in:
|
||||
|
||||
```text
|
||||
plugins/GoMinecraftBridge/go-plugins/libmy_plugin.so
|
||||
```
|
||||
|
||||
Use `.dll` on Windows and `.dylib` on macOS. Plugin data is stored separately in
|
||||
`plugins/GoMinecraftBridge/data/<plugin-id>`. Paper/Purpur invokes the same ABI
|
||||
operations as Fabric: metadata, init, server tick, chat, death, system-call
|
||||
result, and deinit. Snapshots, chat actions, and all built-in system calls use
|
||||
the public Bukkit/Paper API rather than Minecraft internals.
|
||||
|
||||
An operator or the server console can inspect and manage the runtime with:
|
||||
|
||||
```text
|
||||
/gmb status
|
||||
/gmb packages
|
||||
/gmb metadata <plugin-id>
|
||||
/gmb logs <plugin-id> [count]
|
||||
/gmb reload <plugin-id>
|
||||
/gmb rescan
|
||||
```
|
||||
|
||||
On `1.21.1` and `26.1.2`, an operator using the matching Fabric client mod can
|
||||
also open the existing Cloth Config screen. The Paper plugin exposes the same
|
||||
`go_minecraft_bridge:admin_request`/`admin_response` management channels, with
|
||||
package paths, metadata, logs, rescan, and reload withheld from non-OP players.
|
||||
Large responses are shortened below Paper's plugin-message limit; `/gmb` remains
|
||||
available for complete server-side output. A separate Fabric client target is
|
||||
still required before this UI can be used on Minecraft `1.21.11`.
|
||||
|
||||
Lifecycle reload does not unload the native library; replacing an already
|
||||
loaded binary still requires a full server restart. Start Paper/Purpur with
|
||||
`--enable-native-access=ALL-UNNAMED`, just like the Fabric server.
|
||||
|
||||
Build the example Go plugin:
|
||||
|
||||
```bash
|
||||
@@ -41,7 +118,23 @@ config/go-minecraft-bridge/plugins/libhello_native.so
|
||||
|
||||
Use `.dll` on Windows and `.dylib` on macOS. The plugin reports its own ID,
|
||||
name, version, authors, and config schema through the ABI; no sidecar manifest is
|
||||
required.
|
||||
required. Plugins may additionally declare an environment of `server`, `client`,
|
||||
or `both`; omitted environment metadata remains compatible and means `server`.
|
||||
Client and `both` plugins installed on a client belong in:
|
||||
|
||||
```text
|
||||
config/go-minecraft-bridge/client-plugins/libmy_plugin.so
|
||||
```
|
||||
|
||||
They run independently of the connected server. Client plugin data is kept in
|
||||
`config/go-minecraft-bridge/client-data/<plugin-id>`; server plugin data remains
|
||||
under `config/go-minecraft-bridge/data/<plugin-id>`.
|
||||
|
||||
The client runtime emits `Init`, `ClientTick`, and `Deinit`, captures plugin
|
||||
logs, supports local rescan/reload, and permits only the local
|
||||
`minecraft:client.chat.display` action. Server actions, snapshot subscriptions,
|
||||
and system calls are rejected in a client process, so a client plugin cannot use
|
||||
the bridge to bypass a remote server's permissions.
|
||||
|
||||
Native access must be enabled for the unnamed Java module:
|
||||
|
||||
@@ -56,6 +149,35 @@ After joining a development server, send `!go` in chat. The example responds
|
||||
directly to the player and requests `minecraft:server.info` through the system
|
||||
call registry.
|
||||
|
||||
## Cloth Config management screen
|
||||
|
||||
Install the Cloth Config and Mod Menu versions from the target table, then open
|
||||
**Mods → Go Minecraft Bridge → Configure**. The screen always shows local client
|
||||
packages and, when supported by the connected server, server packages. It provides:
|
||||
|
||||
- validation results for native packages found in `plugins` and `mods`;
|
||||
- plugin metadata, config schema, backend, origin, and lifecycle state;
|
||||
- the latest retained bridge/SDK/stdout/stderr logs;
|
||||
- a package rescan that can discover and initialize newly added libraries;
|
||||
- a logical plugin reload (`Deinit → Init`), including recovery from a disabled state.
|
||||
|
||||
Local client package inspection, logs, rescan, and lifecycle reload do not need
|
||||
server permission. Server information and controls still require a player from
|
||||
the server's vanilla OP list.
|
||||
|
||||
Management data and actions are returned only to players present in the
|
||||
server's vanilla OP list. A non-OP response contains no package paths, plugin
|
||||
metadata, or logs. Cloth Config and Mod Menu are client-only optional
|
||||
integrations; a dedicated server only needs Go Minecraft Bridge and Fabric API.
|
||||
The client mod can remain installed when joining an ordinary server without the
|
||||
bridge: it detects the missing management channel and disables only the remote
|
||||
package/plugin screen for that connection.
|
||||
|
||||
Native libraries cannot be safely unloaded from a running JVM. Rescan can load a
|
||||
new file, and lifecycle reload can restart an existing plugin, but replacing the
|
||||
bytes of an already loaded `.so`, `.dll`, or `.dylib` still requires a full JVM
|
||||
restart.
|
||||
|
||||
## Plugin programming model
|
||||
|
||||
A Go project only imports the SDK and registers one value. The native C exports,
|
||||
@@ -105,6 +227,13 @@ func (myPlugin) Chat(ctx *sdk.Context, event sdk.ChatEvent) error {
|
||||
ctx.SendMessage(event.PlayerUUID, "Hello from Go")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (myPlugin) ClientTick(ctx *sdk.Context, event sdk.ClientTickEvent) error {
|
||||
if event.Connected && event.Tick%200 == 0 {
|
||||
ctx.DisplayClientMessage("Client Go runtime is active")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
See [`examples/hello-native/main.go`](examples/hello-native/main.go) for a
|
||||
@@ -134,14 +263,39 @@ plugin.
|
||||
Actions are fire-and-forget operations implemented by the bridge:
|
||||
|
||||
- `minecraft:chat.broadcast`;
|
||||
- `minecraft:chat.player`.
|
||||
- `minecraft:chat.player`;
|
||||
- `minecraft:client.chat.display` via `ctx.DisplayClientMessage(...)` (client runtime only).
|
||||
|
||||
System calls return a result to the plugin's `SystemCallResult` callback. Built-in
|
||||
calls currently include:
|
||||
|
||||
- `minecraft:server.info`;
|
||||
- `minecraft:player.get`;
|
||||
- `minecraft:block.get`.
|
||||
- `sdk.SystemCallServerInfo` (`minecraft:server.info`);
|
||||
- `sdk.SystemCallPlayerGet` (`minecraft:player.get`);
|
||||
- `sdk.SystemCallBlockGet` (`minecraft:block.get`);
|
||||
- `sdk.SystemCallGetEntity` (`minecraft:get_entity`).
|
||||
|
||||
Built-in calls are requested through the typed `SystemCallType` API:
|
||||
|
||||
```go
|
||||
ctx.SystemCall(sdk.SystemCallServerInfo, map[string]any{})
|
||||
|
||||
runtimeID := snapshot.Entities[0].RuntimeID
|
||||
ctx.SystemCall(sdk.SystemCallGetEntity, sdk.GetEntityRequest{
|
||||
RuntimeID: &runtimeID,
|
||||
})
|
||||
```
|
||||
|
||||
`SystemCallGetEntity` accepts exactly one selector: `UUID` or `RuntimeID`. It
|
||||
returns a complete `sdk.EntitySnapshot`, including position, velocity, type,
|
||||
health, and dimension. The result data is JSON `null` when the selected entity
|
||||
is no longer loaded.
|
||||
|
||||
Calls registered by another mod remain available through the explicitly named
|
||||
custom API:
|
||||
|
||||
```go
|
||||
ctx.CustomSystemCall("example:claim.owner", map[string]any{})
|
||||
```
|
||||
|
||||
Another Java mod can expose a custom call without changing the bridge:
|
||||
|
||||
@@ -153,10 +307,9 @@ GoMinecraftBridgeApi.systemCalls().register("example:claim.owner", (context, pay
|
||||
});
|
||||
```
|
||||
|
||||
Discovered metadata, including each plugin's config schema, is available through
|
||||
`GoMinecraftBridgeApi.plugins()`. This is the integration point for a future
|
||||
Mod Menu/configuration screen; Go plugins are intentionally not injected into
|
||||
Fabric Loader's already-finalized mod list.
|
||||
Discovered metadata, including each plugin's config schema, is also available
|
||||
through `GoMinecraftBridgeApi.plugins()`. Go plugins are intentionally not
|
||||
injected into Fabric Loader's already-finalized mod list.
|
||||
|
||||
## Failure model
|
||||
|
||||
@@ -183,18 +336,23 @@ The wire-level contract is documented in [`docs/native-abi.md`](docs/native-abi.
|
||||
|
||||
## Codec and performance
|
||||
|
||||
Protocol v1 deliberately uses JSON because it makes the ABI inspectable while
|
||||
the schema is still changing. JSON is not intended to remain the hot-path codec:
|
||||
it formats every number as text and allocates while decoding large snapshots.
|
||||
ABI v2 uses FlatBuffers for the high-frequency Java → Go tick snapshot. Metadata,
|
||||
initialization, chat/death events, logs, actions, and arbitrary system calls stay
|
||||
JSON because they are smaller and occur less often. Plugin code still receives a
|
||||
normal `sdk.ServerSnapshot`; generated FlatBuffers types are internal to the SDK.
|
||||
|
||||
The planned split is:
|
||||
On the current development machine (Ryzen 5 1600), the synthetic 1000-entity Go
|
||||
benchmark measures roughly `0.65–0.69 ms` per FlatBuffers conversion versus
|
||||
`8.76–8.99 ms` for JSON decode (about 13× faster). Allocated memory drops from
|
||||
about `451 KB` to `260 KB`. This benchmark isolates Go decoding; actual server
|
||||
gain depends on entity count, snapshot subscriptions, Java world traversal, FFI
|
||||
copying, and plugin work.
|
||||
|
||||
- JSON for metadata, configuration, logs, and arbitrary custom system calls;
|
||||
- a generated binary schema for tick snapshots and action batches;
|
||||
- codec negotiation through the plugin metadata, without changing the three C
|
||||
ABI functions.
|
||||
Run the comparison locally with:
|
||||
|
||||
FlatBuffers is the current preferred snapshot codec because Go can read the
|
||||
snapshot directly from the transferred buffer. Protocol Buffers remains a good
|
||||
alternative if schema evolution and tooling prove more important than avoiding
|
||||
decode allocations.
|
||||
```bash
|
||||
(cd sdk && go test -bench 'Benchmark(JSON|FlatBuffers)Snapshot1000Entities' -benchmem -run '^$')
|
||||
```
|
||||
|
||||
The source schema is [`schema/tick_snapshot.fbs`](schema/tick_snapshot.fbs), and
|
||||
generated Java/Go readers are checked in so plugin consumers do not need `flatc`.
|
||||
|
||||
Reference in New Issue
Block a user