155 lines
3.5 KiB
Go
155 lines
3.5 KiB
Go
package sdk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"runtime/debug"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
pluginMu sync.RWMutex
|
|
registeredPlugin Plugin
|
|
)
|
|
|
|
// Register registers a plugin with the server.
|
|
func Register(plugin Plugin) {
|
|
if plugin == nil {
|
|
panic("sdk: cannot register a nil plugin")
|
|
}
|
|
|
|
pluginMu.Lock()
|
|
defer pluginMu.Unlock()
|
|
if registeredPlugin != nil {
|
|
panic("sdk: a plugin is already registered")
|
|
}
|
|
registeredPlugin = plugin
|
|
enableOutputCapture()
|
|
}
|
|
|
|
// Dispatch dispatches a plugin operation to the registered plugin.
|
|
func Dispatch(operation int, input []byte) (output []byte) {
|
|
context := &Context{}
|
|
result := response{Status: "ok"}
|
|
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
result.Status = "panic"
|
|
result.Error = fmt.Sprint(recovered)
|
|
result.Stack = string(debug.Stack())
|
|
}
|
|
|
|
result.Logs = append(result.Logs, context.logs...)
|
|
result.Logs = append(result.Logs, drainCapturedLogs()...)
|
|
result.Actions = context.actions
|
|
result.SystemCalls = context.systemCalls
|
|
result.Snapshot = context.snapshot
|
|
|
|
encoded, err := json.Marshal(result)
|
|
if err != nil {
|
|
output = []byte(`{"status":"panic","error":"cannot encode plugin response"}`)
|
|
return
|
|
}
|
|
output = encoded
|
|
}()
|
|
|
|
plugin := currentPlugin()
|
|
var err error
|
|
|
|
switch operation {
|
|
case OperationMetadata:
|
|
metadata := plugin.Metadata()
|
|
if metadata.APIVersion == 0 {
|
|
metadata.APIVersion = ABIVersion
|
|
}
|
|
if metadata.Environment == "" {
|
|
metadata.Environment = PluginEnvironmentServer
|
|
}
|
|
result.Data = metadata
|
|
case OperationInit:
|
|
var event InitEvent
|
|
err = decode(input, &event)
|
|
if err == nil {
|
|
if handler, ok := plugin.(Initializer); ok {
|
|
err = handler.Init(context, event)
|
|
}
|
|
}
|
|
case OperationTick:
|
|
var snapshot ServerSnapshot
|
|
snapshot, err = decodeTickSnapshot(input)
|
|
if err == nil {
|
|
if handler, ok := plugin.(TickHandler); ok {
|
|
err = handler.Tick(context, snapshot)
|
|
}
|
|
}
|
|
case OperationChat:
|
|
var event ChatEvent
|
|
err = decode(input, &event)
|
|
if err == nil {
|
|
if handler, ok := plugin.(ChatHandler); ok {
|
|
err = handler.Chat(context, event)
|
|
}
|
|
}
|
|
case OperationDeath:
|
|
var event DeathEvent
|
|
err = decode(input, &event)
|
|
if err == nil {
|
|
if handler, ok := plugin.(DeathHandler); ok {
|
|
err = handler.Death(context, event)
|
|
}
|
|
}
|
|
case OperationSystemCallResult:
|
|
var event SystemCallResult
|
|
err = decode(input, &event)
|
|
if err == nil {
|
|
if handler, ok := plugin.(SystemCallResultHandler); ok {
|
|
err = handler.SystemCallResult(context, event)
|
|
}
|
|
}
|
|
case OperationDeinit:
|
|
var event DeinitEvent
|
|
err = decode(input, &event)
|
|
if err == nil {
|
|
if handler, ok := plugin.(Deinitializer); ok {
|
|
err = handler.Deinit(context, event)
|
|
}
|
|
}
|
|
case OperationClientTick:
|
|
var event ClientTickEvent
|
|
err = decode(input, &event)
|
|
if err == nil {
|
|
if handler, ok := plugin.(ClientTickHandler); ok {
|
|
err = handler.ClientTick(context, event)
|
|
}
|
|
}
|
|
default:
|
|
err = fmt.Errorf("sdk: unknown operation %d", operation)
|
|
}
|
|
|
|
if err != nil {
|
|
result.Status = "error"
|
|
result.Error = err.Error()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func currentPlugin() Plugin {
|
|
pluginMu.RLock()
|
|
defer pluginMu.RUnlock()
|
|
if registeredPlugin == nil {
|
|
panic("sdk: plugin was not registered; call sdk.Register from init")
|
|
}
|
|
return registeredPlugin
|
|
}
|
|
|
|
func decode(input []byte, target any) error {
|
|
if len(input) == 0 {
|
|
return errors.New("sdk: operation requires an input payload")
|
|
}
|
|
if err := json.Unmarshal(input, target); err != nil {
|
|
return fmt.Errorf("sdk: decode input: %w", err)
|
|
}
|
|
return nil
|
|
}
|