313 lines
6.9 KiB
Go
313 lines
6.9 KiB
Go
package ext
|
|
|
|
import (
|
|
"aego/editor/ext/inspect"
|
|
"fmt"
|
|
"runtime/debug"
|
|
|
|
"aego/asset"
|
|
"aego/core/errs"
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/editor/core/command"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/gizmo"
|
|
"aego/editor/core/shape"
|
|
"aego/editor/core/templates"
|
|
"aego/editor/core/workspace"
|
|
"aego/scene"
|
|
"aego/schema"
|
|
)
|
|
|
|
type Deps struct {
|
|
Editor *context.Editor
|
|
Workspace *workspace.Workspace
|
|
Inspectors *inspect.Registry
|
|
Gizmos *gizmo.Registry
|
|
Templates *templates.Registry
|
|
Shapes *shape.Registry
|
|
Commands *command.Registry
|
|
Schema *schema.Registry
|
|
}
|
|
|
|
type owned struct {
|
|
panels []string
|
|
commands []string
|
|
gizmos []schema.TypeID
|
|
inspectors []schema.TypeID
|
|
}
|
|
|
|
type pending struct {
|
|
id string
|
|
reason string
|
|
}
|
|
|
|
type Registry struct {
|
|
deps Deps
|
|
current string
|
|
owned map[string]*owned
|
|
disabled map[string]string
|
|
pending []pending
|
|
}
|
|
|
|
func New(d Deps) *Registry {
|
|
return &Registry{deps: d, owned: map[string]*owned{}, disabled: map[string]string{}}
|
|
}
|
|
|
|
func (r *Registry) Editor() *context.Editor {
|
|
return r.deps.Editor
|
|
}
|
|
|
|
func (r *Registry) Schema() *schema.Registry {
|
|
return r.deps.Schema
|
|
}
|
|
|
|
func (r *Registry) Inspectors() *inspect.Registry {
|
|
return r.deps.Inspectors
|
|
}
|
|
|
|
func safeCall(fn func() error) (err error) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
err = errs.New(errs.EditorPanel, "extension panicked", log.F("panic", fmt.Sprint(rec)))
|
|
}
|
|
}()
|
|
return fn()
|
|
}
|
|
|
|
func (r *Registry) Run(owner string, fn any) error {
|
|
h, ok := fn.(func(*Registry) error)
|
|
if !ok {
|
|
err := errs.New(errs.EditorPanel, "editor hook of "+owner+" has the unsupported type "+fmt.Sprintf("%T", fn), log.F("module", owner))
|
|
r.disableNow(owner, err.Error())
|
|
return err
|
|
}
|
|
r.current = owner
|
|
defer func() { r.current = "" }()
|
|
if err := safeCall(func() error { return h(r) }); err != nil {
|
|
r.disableNow(owner, err.Error())
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) ext() string {
|
|
if r.current == "" {
|
|
return "editor"
|
|
}
|
|
return r.current
|
|
}
|
|
|
|
func (r *Registry) own(id string) *owned {
|
|
o := r.owned[id]
|
|
if o == nil {
|
|
o = &owned{}
|
|
r.owned[id] = o
|
|
}
|
|
return o
|
|
}
|
|
|
|
func (r *Registry) Disable(id, reason string) {
|
|
if ed := r.deps.Editor; ed != nil && ed.Crash != nil {
|
|
ed.Crash("extension "+id, reason, debug.Stack())
|
|
}
|
|
r.pending = append(r.pending, pending{id: id, reason: reason})
|
|
}
|
|
|
|
func (r *Registry) Flush() {
|
|
list := r.pending
|
|
r.pending = nil
|
|
for _, p := range list {
|
|
r.disableNow(p.id, p.reason)
|
|
}
|
|
}
|
|
|
|
func (r *Registry) disableNow(id, reason string) {
|
|
if _, done := r.disabled[id]; done {
|
|
return
|
|
}
|
|
r.disabled[id] = reason
|
|
if o := r.owned[id]; o != nil {
|
|
for _, p := range o.panels {
|
|
r.deps.Workspace.Unregister(p)
|
|
}
|
|
for _, c := range o.commands {
|
|
r.deps.Commands.Unregister(c)
|
|
}
|
|
for _, g := range o.gizmos {
|
|
r.deps.Gizmos.Unregister(g)
|
|
}
|
|
for _, t := range o.inspectors {
|
|
r.deps.Inspectors.Disable(t, reason)
|
|
}
|
|
}
|
|
if r.deps.Workspace != nil {
|
|
r.deps.Workspace.RemovePresets(id)
|
|
}
|
|
if r.deps.Shapes != nil {
|
|
r.deps.Shapes.Remove(id)
|
|
}
|
|
if r.deps.Templates != nil {
|
|
r.deps.Templates.RemoveDrops(id)
|
|
}
|
|
if r.deps.Editor != nil {
|
|
r.deps.Editor.Notify(context.LevelError, "extension "+id+" was disabled: "+reason)
|
|
}
|
|
}
|
|
|
|
func (r *Registry) Disabled() map[string]string {
|
|
return r.disabled
|
|
}
|
|
|
|
func Safe(id string, r *Registry, fn func()) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.Disable(id, fmt.Sprint(rec))
|
|
}
|
|
}()
|
|
fn()
|
|
}
|
|
|
|
func (r *Registry) typeOf(fullName string) (*schema.TypeDescriptor, error) {
|
|
td, ok := r.deps.Schema.ByName(fullName)
|
|
if !ok || td.Kind != schema.TypeComponent {
|
|
return nil, errs.New(errs.SchemaUnknownType, "component is not registered", log.F("type", fullName), log.F("module", r.ext()))
|
|
}
|
|
return td, nil
|
|
}
|
|
|
|
const extOrder = 500
|
|
|
|
func (r *Registry) Panel(d workspace.Desc) error {
|
|
if d.Panel == nil {
|
|
return errs.New(errs.EditorPanel, "panel descriptor has no panel", log.F("module", r.ext()))
|
|
}
|
|
ext := r.ext()
|
|
id := d.Panel.ID()
|
|
d.Panel = &safePanel{p: d.Panel, ext: ext, reg: r}
|
|
if d.Order == 0 {
|
|
d.Order = extOrder
|
|
}
|
|
if err := r.deps.Workspace.Register(d); err != nil {
|
|
return err
|
|
}
|
|
o := r.own(ext)
|
|
o.panels = append(o.panels, id)
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) Inspector(fullName string, c inspect.Custom) error {
|
|
td, err := r.typeOf(fullName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ext := r.ext()
|
|
if err := r.deps.Inspectors.RegisterCustomID(td.ID, &safeCustom{inner: c, ext: ext, reg: r}); err != nil {
|
|
return err
|
|
}
|
|
o := r.own(ext)
|
|
o.inspectors = append(o.inspectors, td.ID)
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) Gizmo(fullName string, g gizmo.Gizmo) error {
|
|
td, err := r.typeOf(fullName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ext := r.ext()
|
|
if err := r.deps.Gizmos.Register(td.ID, &safeGizmo{inner: g, ext: ext, reg: r}); err != nil {
|
|
return err
|
|
}
|
|
o := r.own(ext)
|
|
o.gizmos = append(o.gizmos, td.ID)
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) Bounds(fullName string, fn shape.Func) error {
|
|
td, err := r.typeOf(fullName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ext := r.ext()
|
|
return r.deps.Shapes.Register(ext, td.ID, func(s *scene.Scene, res asset.Resolver, h scene.NodeHandle) (sh shape.Shape, ok bool) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.Disable(ext, fmt.Sprint(rec))
|
|
sh, ok = shape.Shape{}, false
|
|
}
|
|
}()
|
|
return fn(s, res, h)
|
|
})
|
|
}
|
|
|
|
func (r *Registry) Overlay(fn shape.Overlay) {
|
|
r.overlay(fn, false)
|
|
}
|
|
|
|
func (r *Registry) GameOverlay(fn shape.Overlay) {
|
|
r.overlay(fn, true)
|
|
}
|
|
|
|
func (r *Registry) overlay(fn shape.Overlay, game bool) {
|
|
ext := r.ext()
|
|
r.deps.Shapes.AddOverlayIn(ext, func(c *shape.OverlayCtx) {
|
|
Safe(ext, r, func() { fn(c) })
|
|
}, game)
|
|
}
|
|
|
|
func (r *Registry) AssetDrop(kind string, space scene.Space, setup func(*scene.Scene, scene.NodeHandle, ids.AssetID)) error {
|
|
ext := r.ext()
|
|
return r.deps.Templates.AddDrop(templates.Drop{
|
|
Owner: ext,
|
|
Kind: kind,
|
|
Space: space,
|
|
Setup: func(s *scene.Scene, h scene.NodeHandle, id ids.AssetID) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.Disable(ext, fmt.Sprint(rec))
|
|
panic(rec)
|
|
}
|
|
}()
|
|
setup(s, h, id)
|
|
},
|
|
})
|
|
}
|
|
|
|
func (r *Registry) Command(d command.Desc) error {
|
|
ext := r.ext()
|
|
run, enabled := d.Run, d.Enabled
|
|
if run == nil {
|
|
return errs.New(errs.EditorCommand, "command needs a run function", log.F("command", d.ID))
|
|
}
|
|
d.Run = func() (err error) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.Disable(ext, fmt.Sprint(rec))
|
|
err = errs.New(errs.EditorCommand, "command panicked", log.F("command", d.ID), log.F("panic", fmt.Sprint(rec)))
|
|
}
|
|
}()
|
|
return run()
|
|
}
|
|
if enabled != nil {
|
|
d.Enabled = func() (ok bool) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.Disable(ext, fmt.Sprint(rec))
|
|
ok = false
|
|
}
|
|
}()
|
|
return enabled()
|
|
}
|
|
}
|
|
if d.Category == "" {
|
|
d.Category = ext
|
|
}
|
|
if err := r.deps.Commands.Register(d); err != nil {
|
|
return err
|
|
}
|
|
o := r.own(ext)
|
|
o.commands = append(o.commands, d.ID)
|
|
return nil
|
|
}
|