Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ tasks:
- task: build-parser
- task: build-editor

package-extension:
desc: Package prebuilt extension commands into a .sdmmext archive
dir: tools/sdmmpack
requires:
vars: [EXTENSION_DIR, EXTENSION_OUTPUT]
cmds:
- go run . -source "{{.EXTENSION_DIR}}" -manifest "{{.EXTENSION_DIR}}/extension.json" -output "{{.EXTENSION_OUTPUT}}"

build-extension:
desc: Build every command declared by an extension manifest and package it
dir: tools/sdmmpack
requires:
vars: [EXTENSION_DIR, EXTENSION_OUTPUT]
cmds:
- go run . -build -source "{{.EXTENSION_DIR}}" -manifest "{{.EXTENSION_DIR}}/extension.json" -output "{{.EXTENSION_OUTPUT}}"

run:
deps:
- build-parser
Expand Down
148 changes: 148 additions & 0 deletions extensionapi/extensionapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Package extensionapi defines the RPC contract between StrongDMM and Go extensions.
package extensionapi

import (
"net/rpc"

"github.com/hashicorp/go-plugin"
)

const (
ProtocolVersion = 1
PluginName = "strongdmm.extension"
StageMap = "core.map"
StageSelection = "core.selection"
StageOverlays = "core.overlays"

CapabilityRender = "render"
CapabilityContextMenu = "context-menu"
)

type Color struct{ R, G, B float32 }
type AtomDefinition struct {
ID uint32
Path string
Vars map[string]string
}
type Tile struct {
X, Y, Z int
Atoms []uint32
}
type Map struct {
Width, Height, Levels int
Definitions []AtomDefinition
Tiles []Tile
}
type Vertex struct{ X, Y, R, G, B, A float32 }
type BilinearQuad struct {
X1, Y1, X2, Y2 float32
SouthWest, SouthEast, NorthWest, NorthEast Color
}
type Mesh struct {
Vertices []Vertex
Indices []uint32
Quads []BilinearQuad
}
type RenderCommand struct {
ID string
Kind string
Mesh Mesh
}
type RenderPass struct {
ID string
After, Before []string
Blend string
Commands []RenderCommand
}
type RenderPatch struct {
PassID string
Upsert []RenderCommand
Remove []string
}
type RenderUpdate struct {
Replace []RenderPass
Patches []RenderPatch
RemovePasses []string
}
type MapUpdate struct {
Width, Height, Levels int
Definitions []AtomDefinition
Tiles []Tile
}
type ContextTurf struct {
X, Y, Z int
Atoms []AtomDefinition
}
type ContextMenuRequest struct{ Turf ContextTurf }
type ContextMenuAction struct {
ID, Label string
Enabled bool
}
type ContextMenuResponse struct{ Actions []ContextMenuAction }
type ActionRequest struct {
ID string
Turf ContextTurf
}

// Request is a versioned extension operation. Extensions ignore operations
// outside the capabilities they declare in their package manifest.
type Request struct {
Version int
Type string
MapID string
Revision uint64
Settings map[string]bool
Map *Map
Update *MapUpdate
Render *RenderUpdate
Context *ContextMenuRequest
Action *ActionRequest
ContextMenu *ContextMenuResponse
Reason string
}

// Response carries optional output for a request. Render and context-menu
// operations use their corresponding fields; other operations may return ack.
type Response = Request

// Extension handles versioned requests for its declared capabilities.
type Extension interface {
Handle(Request) (Response, error)
}

// HandshakeConfig prevents StrongDMM from connecting to an unrelated process.
var HandshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: ProtocolVersion,
MagicCookieKey: "STRONGDMM_EXTENSION",
MagicCookieValue: "strongdmm-extension-v1",
}

// Plugin adapts an Extension to HashiCorp go-plugin's net/rpc transport.
type Plugin struct {
plugin.NetRPCUnsupportedPlugin
Impl Extension
}

func (p *Plugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &rpcServer{Impl: p.Impl}, nil
}

func (p *Plugin) Client(_ *plugin.MuxBroker, client *rpc.Client) (interface{}, error) {
return &rpcClient{client: client}, nil
}

type rpcClient struct{ client *rpc.Client }

func (c *rpcClient) Handle(request Request) (Response, error) {
var response Response
err := c.client.Call("Plugin.Handle", request, &response)
return response, err
}

type rpcServer struct{ Impl Extension }

func (s *rpcServer) Handle(request Request, response *Response) error {
result, err := s.Impl.Handle(request)
*response = result
return err
}
22 changes: 22 additions & 0 deletions extensionapi/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/SpaiR/StrongDMM-extension-api

go 1.24.0

require github.com/hashicorp/go-plugin v1.6.3

require (
github.com/fatih/color v1.7.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/go-hclog v0.14.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/oklog/run v1.0.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
54 changes: 54 additions & 0 deletions extensionapi/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
47 changes: 47 additions & 0 deletions extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# StrongDMM extensions

Extensions are Go executables via go-plugin to extend the editor in different ways.

```json
{
"id": "example.extension",
"name": "Example Extension",
"apiVersion": 1,
"capabilities": ["render", "context-menu"],
"commands": {
"windows-amd64": "bin/windows-amd64/example.exe"
}
}
```

Implement `extensionapi.Extension` and call `plugin.Serve` with
`extensionapi.HandshakeConfig` and an `extensionapi.Plugin`. The API exposes
versioned `Request` values and request-shaped responses. Rendering receives map
open/update/close operations, revisions, settings, and render-pass output. The
declared capabilities determine which operations an extension receives:
`render` receives map lifecycle operations and `context-menu` receives requests
for dynamic actions at a turf. Standard output is reserved for go-plugin's
handshake and transport; write extension logs to standard error.

Context-menu requests provide the clicked turf and its atom definitions. Return
actions with stable extension-local IDs; StrongDMM returns the selected ID in a
context action request. Extensions can ignore request types outside their
declared capabilities.

`RenderPass` IDs are globally unique. Passes can be placed relative to
`core.map`, `core.selection`, and `core.overlays`; invalid dependencies and
cycles are logged. `mesh` commands support RGBA vertex colors; `bilinear`
commands are intentionally opaque and support normal, multiply, or additive
blending.

Build and package an extension with StrongDMM's generic Task:

```text
task --taskfile path/to/StrongDMM/Taskfile.yml build-extension EXTENSION_DIR=path/to/extension EXTENSION_OUTPUT=path/to/extension/dist/extension.sdmmext
```

The task cross-builds every command declared in `extension.json` and packages
the resulting `.sdmmext` archive.

Extension API versions must match the host. Build and package extensions only
from trusted source.
14 changes: 14 additions & 0 deletions extensions/example/extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "example.extension",
"name": "Example Extension",
"apiVersion": 1,
"capabilities": ["render"],
"commands": {
"windows-amd64": "bin/windows-amd64/example-extension.exe",
"linux-amd64": "bin/linux-amd64/example-extension",
"darwin-arm64": "bin/darwin-arm64/example-extension"
},
"actions": [
{"id": "enabled", "menu": "Extensions", "label": "Enable Example Extension", "kind": "toggle-enabled"}
]
}
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ module sdmm
go 1.24.0

require (
github.com/SpaiR/StrongDMM-extension-api v0.0.0
github.com/SpaiR/imgui-go v1.12.1-0.20220214190844-a0bad21e1c5d
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
github.com/go-gl/mathgl v1.2.0
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-plugin v1.6.3
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f
github.com/mazznoer/csscolorparser v0.1.6
github.com/minio/selfupdate v0.6.0
Expand All @@ -17,17 +20,28 @@ require (
golang.design/x/clipboard v0.7.1
)

replace github.com/SpaiR/StrongDMM-extension-api => ./extensionapi

require (
aead.dev/minisign v0.2.0 // indirect
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
golang.org/x/image v0.28.0 // indirect
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading