201 lines
5.7 KiB
Markdown
201 lines
5.7 KiB
Markdown
# 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.
|
|
|
|
The current MVP targets:
|
|
|
|
- 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;
|
|
- native Go plugins built with `-buildmode=c-shared`;
|
|
- initialization, 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.
|
|
|
|
The backend interface is deliberately independent from Fabric and native FFI so
|
|
that a WASI/WASM backend can implement the same protocol later.
|
|
|
|
## Build and try the example
|
|
|
|
Build the Fabric mod:
|
|
|
|
```bash
|
|
./gradlew build
|
|
```
|
|
|
|
Build the example Go plugin:
|
|
|
|
```bash
|
|
./examples/hello-native/build.sh
|
|
```
|
|
|
|
Copy the resulting library to either location:
|
|
|
|
```text
|
|
mods/libhello_native.so
|
|
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.
|
|
|
|
Native access must be enabled for the unnamed Java module:
|
|
|
|
```text
|
|
--enable-native-access=ALL-UNNAMED
|
|
```
|
|
|
|
The development run configurations add this automatically. A production server
|
|
must add it to its JVM arguments.
|
|
|
|
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.
|
|
|
|
## Plugin programming model
|
|
|
|
A Go project only imports the SDK and registers one value. The native C exports,
|
|
panic boundary, output allocation, and `gmb_free` implementation live inside the
|
|
SDK and are linked into the final library automatically:
|
|
|
|
```go
|
|
package main
|
|
|
|
import "github.com/yawaflua/GoMinecraftBridge/sdk"
|
|
|
|
type myPlugin struct{}
|
|
|
|
func init() {
|
|
sdk.Register(myPlugin{})
|
|
}
|
|
|
|
func main() {}
|
|
```
|
|
|
|
Until the SDK is published as a tagged Go module, reference the local checkout:
|
|
|
|
```go
|
|
require github.com/yawaflua/GoMinecraftBridge/sdk v0.0.0
|
|
|
|
replace github.com/yawaflua/GoMinecraftBridge/sdk => /path/to/GoMinecraftBridge/sdk
|
|
```
|
|
|
|
Build it normally:
|
|
|
|
```bash
|
|
go build -buildmode=c-shared -o dist/my_plugin.so .
|
|
```
|
|
|
|
It implements only the callbacks it needs:
|
|
|
|
```go
|
|
func (myPlugin) Tick(ctx *sdk.Context, snapshot sdk.ServerSnapshot) error {
|
|
for _, entity := range snapshot.Entities {
|
|
// Read the immutable snapshot.
|
|
_ = entity
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (myPlugin) Chat(ctx *sdk.Context, event sdk.ChatEvent) error {
|
|
ctx.SendMessage(event.PlayerUUID, "Hello from Go")
|
|
return nil
|
|
}
|
|
```
|
|
|
|
See [`examples/hello-native/main.go`](examples/hello-native/main.go) for a
|
|
complete native entrypoint and every currently supported callback.
|
|
|
|
### Snapshots
|
|
|
|
Entity snapshots are enabled by default. Blocks are opt-in because walking all
|
|
loaded blocks every tick would be prohibitively expensive:
|
|
|
|
```go
|
|
ctx.SubscribeSnapshot(true,
|
|
sdk.BlockReference{
|
|
Dimension: "minecraft:overworld",
|
|
X: 0,
|
|
Y: 64,
|
|
Z: 0,
|
|
},
|
|
)
|
|
```
|
|
|
|
The subscription returned by a callback applies to subsequent ticks for that
|
|
plugin.
|
|
|
|
### Actions and system calls
|
|
|
|
Actions are fire-and-forget operations implemented by the bridge:
|
|
|
|
- `minecraft:chat.broadcast`;
|
|
- `minecraft:chat.player`.
|
|
|
|
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`.
|
|
|
|
Another Java mod can expose a custom call without changing the bridge:
|
|
|
|
```java
|
|
GoMinecraftBridgeApi.systemCalls().register("example:claim.owner", (context, payload) -> {
|
|
JsonObject result = new JsonObject();
|
|
result.addProperty("owner", "player uuid");
|
|
return result;
|
|
});
|
|
```
|
|
|
|
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.
|
|
|
|
## Failure model
|
|
|
|
The SDK converts callback errors into `status=error` and recovers ordinary Go
|
|
panics at the ABI boundary. A panic disables that plugin logically. Native Go
|
|
libraries remain loaded until JVM shutdown because unloading a live Go runtime is
|
|
unsafe.
|
|
|
|
Native memory corruption, a C/Go runtime fatal error, or a segmentation fault can
|
|
still terminate Minecraft. The planned WASM backend is the isolated option for
|
|
untrusted plugins.
|
|
|
|
## Verification
|
|
|
|
Run all Java and Go tests, including the real Java-to-Go FFI test:
|
|
|
|
```bash
|
|
./examples/hello-native/build.sh
|
|
GMB_TEST_LIBRARY="$PWD/examples/hello-native/dist/libhello_native.so" ./gradlew test
|
|
(cd sdk && go test ./...)
|
|
```
|
|
|
|
The wire-level contract is documented in [`docs/native-abi.md`](docs/native-abi.md).
|
|
|
|
## 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.
|
|
|
|
The planned split is:
|
|
|
|
- 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.
|
|
|
|
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.
|