Go¶
Pier's Go binding writes a mod as a Go package built into a DLL. You import
github.com/Maskviva/pier/bindings/go/levilamina and write levilamina.Subscribe(...); the
smallest working mod is examples/hello-pier-go.
The path names Pier, the ABI the package belongs to, and the package name names what you are
writing, a LeviLamina mod. The Rust binding does the same: its package is pier-rs and its
crate levilamina.
How it is put together¶
cgo reads abi.h itself. The binding does not mirror the function table by hand: cgo
includes the header, so the C compiler lays out PierApi exactly as the host does. The
package carries a copy of the header, because go get fetches nothing outside the module;
a check keeps the copy identical to the original.
Every slot is reached through a generated C function. Go cannot call a C function
pointer, so slots_gen.h has, for each slot, a piergo_call_<slot> that forwards the call
and a piergo_has_<slot> that applies both gates of the contract: the table is long enough
to hold the slot, and the slot is not NULL. tools/gen-go-slots.py writes it from abi.h,
and the go-binding check fails when it is stale.
The host calls exported Go functions. pier_main, the lifecycle callbacks, and the
callbacks of tasks, events and commands are //exported, and a small C file hands them to
the host as the function pointers it expects. A panic in any of them is recovered at the
boundary and logged; nothing unwinds into the host.
What you need¶
- Go 1.21 or later.
- A MinGW-w64
gcconPATH, which cgo builds the C side with. The DLL meets Pier through C functions only, which MinGW and MSVC call the same way on x64, so the MSVC requirement of a C++ mod does not apply. CGO_ENABLED=1, andgo build -buildmode=c-shared.
Build in a way that leaves the DLL depending on system DLLs only; a DLL that needs a MinGW runtime DLL the server does not have fails to load with error 0x7E.
The API¶
The API comes in three layers, all gated the same way:
levilamina.Raw: one typed method per slot without a callback, 173 of them, generated fromabi.hwith its documentation. A result means whatabi.hsays it means.- Generated facades: one method per property and verb of the constant tables, on
Player,Entity,BlockAtandItem, such asPlayer.Level,Player.SetLevel,Entity.IsOnFireorItem.Count, and a Go constant for each value, such asPPropLevel. - Hand-written functions over both, close to the Rust binding's:
| Area | Functions |
|---|---|
| Lifecycle and logging | Register, Mod, Loader, Unloader, Context.Logger() |
| Server and world | Status, CurrentTick, Tps, Mspt, Time, SetWeather, GameRule, ExecuteCommand, ListPlayers, ListActors, SpawnMob, Explode, FillRegion, SaveLevel |
| Tasks | Schedule, ScheduleAfter, Cancel, PendingTasks |
| Events | Subscribe, Event.Payload, Event.Dim, Event.CheckComplete, Event.Set, Event.Cancel |
| Commands and forms | RegisterCommand, NewCommand with typed overloads, RegisterCommandEnum, RegisterSoftEnum, SendForm |
| Players and entities | Player.SendMessage, Teleport, Inventory, CarriedItem, Entity.Snapshot, Owner, Target, and the generated properties |
| Blocks, items, containers | BlockAt.Info, State, SetState, Item.Payload, Container.Items, AddItem, Clear |
| Data | OpenKvDb, ParseSNBT, Nbt, SnbtToBinary |
| Economy | Money, AddMoney, TransferMoney, OnMoneyBefore, OnMoneyAfter |
| Cross-mod | BusPublish, BusPublishVetoable, BusSubscribe, RegisterService, CallService, ServiceCaller; see Talking to other mods |
| Network | RegisterPacketHook, RegisterPacketHookIDs, RegisterConnHook |
| Regions | ScanRegion, ScanRegionIndexed, SetBlocks |
| Dimensions and simulated players | AddDimension, AddGeneratedDimension, DimensionRule, SimSpawn, SimDo |
| Client | RegisterKey |
A call whose slot the host does not have returns a *NotProvidedError, which
IsNotProvided recognizes, and a read the host cannot answer is an error rather than a zero
or a false. Every slot of the table is reachable from Go except the two of lanes, which by design only
pair mods built by the same toolchain; a mod that publishes a lane also provides the service
a Go mod uses instead.
Rules worth knowing¶
- Threads. Most of the host is server-thread only. Lifecycle steps, event and command
callbacks already run there; a goroutine gets back onto it with
Schedule. - Tasks belong to the mod. A task that has not run when the mod unloads is dropped by the host, so nothing calls into a DLL that is gone.
- Payloads are copies.
Event.SNBTandInvocation.Argsare Go strings, valid after the callback returns. The fields of a payload are in the event payload reference. - One runtime per DLL. Every Go mod carries a whole Go runtime. Several Go mods on one server are several runtimes in one process, which Go does not test; check that it holds on your server before relying on it.
Your first Go mod walks through building and installing one, and Talking to other mods shows how a Go mod works with mods of other languages.