21 Commits
Author SHA1 Message Date
sanspie 208875264f Stage 9.5c3: docs runtime polish
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m43s
ci / checks (push) Successful in 16m57s
2026-10-20 02:33:48 +03:00
sanspie 8c21400bb1 Stage 9.5c2: docs session, Try it, sandbox (D293–D294) 2026-10-20 01:31:40 +03:00
sanspie e83521a34a Stage 9.5c1: web export (D295) 2026-10-20 00:59:39 +03:00
sanspie 3f5584d062 Stage 9.5b3b: component, settings and CLI reference (D292) 2026-10-20 00:11:43 +03:00
sanspie 6ce72aa1ec Stage 9.5b3a: docs IA and site shell (D292) 2026-10-19 23:27:11 +03:00
sanspie 9ee2b0b05c Stage 9.5b2: playground with Game View (D291) 2026-10-19 23:10:55 +03:00
sanspie 99dacd7a75 Stage 9.5b1: web platform and WebGL2 backend (D289–D290) 2026-10-19 22:47:08 +03:00
sanspie 5447383f2b gitignore: aego-runtime binary and .syso resources 2026-10-19 20:39:03 +03:00
sanspie 6745d027fb Stage 9.5a: export v1 (D286–D288) 2026-10-19 20:27:58 +03:00
sanspie e7dd01371e script/abi: Parse accepts the untyped Handle<> the encoder writes 2026-10-19 20:27:40 +03:00
sanspie 68802195b6 Stage 9.0: editor workflow 2026-10-19 20:27:40 +03:00
sanspie 7cba364aed Stage 9.4d4: aego test --determinism 2026-10-19 02:18:58 +03:00
sanspie 35e9849cae Stage 9.4d3: keyframes 2026-10-19 02:15:10 +03:00
sanspie f21d455d68 Stage 9.4d3a: CloneInto, spawn pool 2026-10-19 01:51:39 +03:00
sanspie d1a16d8208 Stage 9.4d2: rewind 2026-10-19 01:47:39 +03:00
sanspie 2a1826f876 Stage 9.4d1: save games 2026-10-19 00:37:50 +03:00
sanspie 3af2d5d977 Stage 9.4c: data assets, validators 2026-10-18 22:58:02 +03:00
sanspie b61a1656ad Stage 9.4b: signals, tags, spawn 2026-10-18 22:58:02 +03:00
sanspie 0fbf22a090 Stage 9.4a: curves, tween, timers
core/curve with presets, keys, wraps; KindCurve in schema, scenes, prefabs and the inspector curve editor.
modules/tween: tweens and timers in per-world pools, game and unscaled domains, script field targets via contracts/scriptfields.
Host handle types (abi.Handle, bind.Handle, manifest v5), yield on awaitable handles via contracts/scriptwait.
Script modules aego/curve, aego/tween, aego/timer; platformer enemy uses a cooldown and a fade-out tween.
Docs: tween.md, curves.md, language reference; golden for the curve editor.
2026-10-18 21:32:39 +03:00
sanspie 5e763aa73c Stage 9.2–9.3: input and settings 2026-10-18 21:32:39 +03:00
sanspie 6093ca9d8b Stage 9.1: time and debug 2026-10-17 23:14:21 +03:00
49 changed files with 734 additions and 3873 deletions
+1 -7
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -98,7 +98,6 @@ vet: bootstrap
lint: bootstrap lint: bootstrap
go run honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION) $(LINT_PKGS) go run honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION) $(LINT_PKGS)
go run honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION) -tags aego_editor ./editor/... ./app/... ./tests/...
# All graph variants, the semantic pass and the negative rule tests. # All graph variants, the semantic pass and the negative rule tests.
depcheck: depcheck:
+6 -2
View File
@@ -116,8 +116,12 @@ func TestPlayLeavesEditSceneByteIdentical(t *testing.T) {
t.Fatal("Spin is not registered") t.Fatal("Spin is not registered")
} }
speed, _ := td.Field("speed") speed, _ := td.Field("speed")
add := commands.NewMutation("Add "+td.FullName, commands.EnvFor(es), add := &commands.AddComponent{
commands.AddComp(es, spinner, td, []commands.FieldValue{{Field: speed.ID, Value: schema.Float32(3)}})) Node: spinner,
Type: td.ID,
Name: td.FullName,
Values: []commands.FieldValue{{Field: speed.ID, Value: schema.Float32(3)}},
}
if err := d.History.Exec(add); err != nil { if err := d.History.Exec(add); err != nil {
t.Fatal(err) t.Fatal(err)
} }
+1 -1
View File
@@ -42,7 +42,7 @@ func TestColliderFitsSprite(t *testing.T) {
} }
for _, name := range []string{"Crate", "Bare"} { for _, name := range []string{"Crate", "Bare"} {
id := es.ID(es.Find(es.Root(), name)) id := es.ID(es.Find(es.Root(), name))
if err := d.History.Exec(commands.NewMutation("Add "+td.FullName, commands.WithOnAdd(commands.EnvFor(es), ctx.OnAdd), commands.AddComp(es, id, td, nil))); err != nil { if err := d.History.Exec(&commands.AddComponent{Node: id, Type: td.ID, Name: td.FullName, OnAdd: ctx.OnAdd(td)}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
+6 -2
View File
@@ -51,8 +51,12 @@ func openSpinner(t *testing.T) *spinFixture {
} }
fd, _ := td.Field("speed") fd, _ := td.Field("speed")
f.typ, f.speed = td.ID, fd.ID f.typ, f.speed = td.ID, fd.ID
add := commands.NewMutation("Add "+td.FullName, commands.EnvFor(es), add := &commands.AddComponent{
commands.AddComp(es, f.spinner, td, []commands.FieldValue{{Field: fd.ID, Value: schema.Float32(3)}})) Node: f.spinner,
Type: td.ID,
Name: td.FullName,
Values: []commands.FieldValue{{Field: fd.ID, Value: schema.Float32(3)}},
}
if err := d.History.Exec(add); err != nil { if err := d.History.Exec(add); err != nil {
t.Fatal(err) t.Fatal(err)
} }
+18 -22
View File
@@ -10,7 +10,6 @@ import (
"aego/editor/core/history/commands" "aego/editor/core/history/commands"
"aego/format/aetext" "aego/format/aetext"
"aego/scene" "aego/scene"
"aego/scene/mutate"
"aego/scene/sceneio" "aego/scene/sceneio"
) )
@@ -238,24 +237,12 @@ func Paste(ctx *context.Editor) error {
if err != nil { if err != nil {
return err return err
} }
frags, err := commands.Fragments(data)
if err != nil {
return err
}
parent, index := pasteTarget(ctx) parent, index := pasteTarget(ctx)
ops := make([]mutate.Op, len(frags)) cmd := &commands.Create{Parent: parent, Index: index, Fragment: data, Name: "Paste"}
for i, f := range frags {
at := -1
if index >= 0 {
at = index + i
}
ops[i] = commands.Insert(parent, at, f)
}
cmd := commands.NewMutation("Create Paste", commands.EnvFor(d.Scene()), ops...)
if err := d.History.Exec(cmd); err != nil { if err := d.History.Exec(cmd); err != nil {
return err return err
} }
ctx.Select(cmd.Created()...) ctx.Select(append([]ids.NodeID(nil), cmd.Roots()...)...)
return nil return nil
} }
@@ -280,13 +267,17 @@ func Duplicate(ctx *context.Editor) error {
tx.Abort() tx.Abort()
return err return err
} }
c := commands.NewMutation("Create "+s.Name(h), commands.EnvFor(s), c := &commands.Create{
commands.Insert(s.ID(s.Parent(h)), commands.ChildIndex(s, h)+1, string(data))) Parent: s.ID(s.Parent(h)),
Index: commands.ChildIndex(s, h) + 1,
Fragment: data,
Name: s.Name(h),
}
if err := tx.Exec(c); err != nil { if err := tx.Exec(c); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
created = append(created, c.Created()...) created = append(created, c.Roots()...)
} }
tx.Commit() tx.Commit()
ctx.Select(created...) ctx.Select(created...)
@@ -303,13 +294,18 @@ func Delete(ctx *context.Editor) error {
if len(tops) == 0 { if len(tops) == 0 {
return nil return nil
} }
ops := make([]mutate.Op, len(tops)) targets := make([]ids.NodeID, len(tops))
for i, h := range tops { for i, h := range tops {
ops[i] = mutate.RemoveNode(commands.Ref(s.ID(h))) targets[i] = s.ID(h)
} }
if err := d.History.Exec(commands.NewMutation("Delete", commands.EnvFor(s), ops...)); err != nil { tx := d.History.Begin("Delete")
return err for _, id := range targets {
if err := tx.Exec(&commands.Delete{Node: id}); err != nil {
tx.Abort()
return err
}
} }
tx.Commit()
ctx.Select() ctx.Select()
return nil return nil
} }
+17 -5
View File
@@ -113,7 +113,8 @@ func (e Env) encodeComponent(td *schema.TypeDescriptor, values []commands.FieldV
sc := e.scratch() sc := e.scratch()
h := sc.Create(sc.Root(), "component") h := sc.Create(sc.Root(), "component")
sc.Flush() sc.Flush()
if err := putComponent(sc, h, td, values); err != nil { add := &commands.AddComponent{Node: sc.ID(h), Type: td.ID, Name: td.FullName, Values: values}
if err := add.Do(target{sc}); err != nil {
return nil, err return nil, err
} }
return sceneio.SaveSubtree(sc, h, true), nil return sceneio.SaveSubtree(sc, h, true), nil
@@ -121,14 +122,18 @@ func (e Env) encodeComponent(td *schema.TypeDescriptor, values []commands.FieldV
func (e Env) decodeComponent(data []byte) (*schema.TypeDescriptor, []commands.FieldValue, error) { func (e Env) decodeComponent(data []byte) (*schema.TypeDescriptor, []commands.FieldValue, error) {
sc := e.scratch() sc := e.scratch()
root, err := loadScratch(sc, data) c := &commands.Create{Index: -1, Fragment: data}
if err != nil { if err := c.Do(target{sc}); err != nil {
return nil, nil, err return nil, nil, err
} }
roots := c.Roots()
if len(roots) == 0 {
return nil, nil, errs.New(errs.EditorCommand, "clipboard component is empty")
}
reg := sc.Registry() reg := sc.Registry()
unknown, hasUnknown := reg.ByName("engine.Unknown") unknown, hasUnknown := reg.ByName("engine.Unknown")
var td *schema.TypeDescriptor var td *schema.TypeDescriptor
sc.ComponentsOf(sc.Handle(root), func(t schema.TypeID) { sc.ComponentsOf(sc.Handle(roots[0]), func(t schema.TypeID) {
if td != nil || (hasUnknown && t == unknown.ID) { if td != nil || (hasUnknown && t == unknown.ID) {
return return
} }
@@ -137,7 +142,7 @@ func (e Env) decodeComponent(data []byte) (*schema.TypeDescriptor, []commands.Fi
if td == nil { if td == nil {
return nil, nil, errs.New(errs.EditorCommand, "clipboard component type is not loaded in this editor") return nil, nil, errs.New(errs.EditorCommand, "clipboard component type is not loaded in this editor")
} }
values, err := componentValues(sc, root, td) values, err := componentValues(sc, roots[0], td)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -184,6 +189,13 @@ func (e Env) ReadComponent() (*schema.TypeDescriptor, []commands.FieldValue, boo
return td, values, true return td, values, true
} }
func (e Env) onAdd(td *schema.TypeDescriptor) func(*scene.Scene, scene.NodeHandle) {
if e.OnAdd == nil {
return nil
}
return e.OnAdd(td)
}
func (e Env) PasteComponent(d *document.SceneDocument, nodes []ids.NodeID) error { func (e Env) PasteComponent(d *document.SceneDocument, nodes []ids.NodeID) error {
td, values, ok := e.ReadComponent() td, values, ok := e.ReadComponent()
if !ok { if !ok {
-28
View File
@@ -1,28 +0,0 @@
package clipboard
import (
"aego/core/errs"
"aego/core/ids"
"aego/editor/core/history/commands"
"aego/scene"
"aego/scene/sceneio"
"aego/schema"
)
func loadScratch(sc *scene.Scene, data []byte) (ids.NodeID, error) {
root := sc.Root()
created, _, err := sceneio.LoadInto(sc, root, data, "fragment", sceneio.LoadOptions{KeepIDs: true})
if err != nil {
return ids.NodeID{}, err
}
for _, h := range created {
if sc.Parent(h) == root {
return sc.ID(h), nil
}
}
return ids.NodeID{}, errs.New(errs.EditorCommand, "the fragment holds no node")
}
func putComponent(sc *scene.Scene, h scene.NodeHandle, td *schema.TypeDescriptor, list []commands.FieldValue) error {
return commands.NewMutation("", commands.EnvFor(sc), commands.AddRaw(sc, sc.ID(h), td, list)).Do(target{sc})
}
+12 -8
View File
@@ -22,7 +22,6 @@ type compData struct {
func (e Env) pasteInto(tx *history.Transaction, d *document.SceneDocument, nodes []ids.NodeID, td *schema.TypeDescriptor, values []commands.FieldValue) error { func (e Env) pasteInto(tx *history.Transaction, d *document.SceneDocument, nodes []ids.NodeID, td *schema.TypeDescriptor, values []commands.FieldValue) error {
s := d.Scene() s := d.Scene()
env := commands.WithOnAdd(commands.EnvFor(s), e.OnAdd)
for _, n := range nodes { for _, n := range nodes {
h := s.Handle(n) h := s.Handle(n)
if h.IsZero() || !s.IsAlive(h) { if h.IsZero() || !s.IsAlive(h) {
@@ -32,7 +31,7 @@ func (e Env) pasteInto(tx *history.Transaction, d *document.SceneDocument, nodes
if !s.Accepts(h, td.ID) { if !s.Accepts(h, td.ID) {
continue continue
} }
if err := tx.Exec(commands.NewMutation("Add "+td.FullName, env, commands.AddComp(s, n, td, values))); err != nil { if err := tx.Exec(&commands.AddComponent{Node: n, Type: td.ID, Name: td.FullName, Values: values, OnAdd: e.onAdd(td)}); err != nil {
return err return err
} }
continue continue
@@ -95,7 +94,8 @@ func (e Env) CopyComponents(s *scene.Scene, node ids.NodeID, types []schema.Type
if err != nil { if err != nil {
return err return err
} }
if err := putComponent(sc, h, td, values); err != nil { add := &commands.AddComponent{Node: sc.ID(h), Type: td.ID, Name: td.FullName, Values: values}
if err := add.Do(target{sc}); err != nil {
return err return err
} }
} }
@@ -118,10 +118,11 @@ func (e Env) readComponents() ([]compData, bool) {
data = []byte(strings.TrimPrefix(t, componentsText)) data = []byte(strings.TrimPrefix(t, componentsText))
} }
sc := e.scratch() sc := e.scratch()
root, err := loadScratch(sc, data) cr := &commands.Create{Index: -1, Fragment: data}
if err != nil { if err := cr.Do(target{sc}); err != nil || len(cr.Roots()) == 0 {
return nil, false return nil, false
} }
root := cr.Roots()[0]
reg := sc.Registry() reg := sc.Registry()
unknown, hasUnknown := reg.ByName("engine.Unknown") unknown, hasUnknown := reg.ByName("engine.Unknown")
var out []compData var out []compData
@@ -185,11 +186,14 @@ func (e Env) EncodeScript(s *scene.Scene, node ids.NodeID, name string) ([]byte,
func (e Env) decodeScript(data []byte) (string, schema.PropertyBag, error) { func (e Env) decodeScript(data []byte) (string, schema.PropertyBag, error) {
sc := e.scratch() sc := e.scratch()
root, err := loadScratch(sc, data) cr := &commands.Create{Index: -1, Fragment: data}
if err != nil { if err := cr.Do(target{sc}); err != nil {
return "", schema.PropertyBag{}, err return "", schema.PropertyBag{}, err
} }
scs, ok := scriptcomp.ScriptsOf(sc, sc.Handle(root)) if len(cr.Roots()) == 0 {
return "", schema.PropertyBag{}, errs.New(errs.EditorCommand, "the preset is empty")
}
scs, ok := scriptcomp.ScriptsOf(sc, sc.Handle(cr.Roots()[0]))
if !ok || len(scs.Attachments) == 0 { if !ok || len(scs.Attachments) == 0 {
return "", schema.PropertyBag{}, errs.New(errs.EditorCommand, "the preset holds no script") return "", schema.PropertyBag{}, errs.New(errs.EditorCommand, "the preset holds no script")
} }
+11 -22
View File
@@ -4,11 +4,9 @@ import (
"testing" "testing"
"aego/core/errs" "aego/core/errs"
"aego/core/ids"
"aego/core/mathx" "aego/core/mathx"
"aego/editor/core/history" "aego/editor/core/history"
"aego/scene" "aego/scene"
"aego/scene/mutate"
"aego/scene/sceneio" "aego/scene/sceneio"
"aego/schema" "aego/schema"
) )
@@ -47,14 +45,6 @@ func newScene(t *testing.T) *scene.Scene {
return scene.New(types, nil, scene.Options{}) return scene.New(types, nil, scene.Options{})
} }
func del(s *scene.Scene, id ids.NodeID) *Mutation {
return NewMutation("Delete", EnvFor(s), mutate.RemoveNode(Ref(id)))
}
func reparent(s *scene.Scene, n, p scene.NodeHandle) *Mutation {
return NewMutation("Reparent", EnvFor(s), Move(s.ID(n), s.ID(p), -1, true))
}
func TestDeleteUndoRestoresIDsAndRefs(t *testing.T) { func TestDeleteUndoRestoresIDsAndRefs(t *testing.T) {
s := newScene(t) s := newScene(t)
first := s.Create(s.Root(), "First") first := s.Create(s.Root(), "First")
@@ -67,7 +57,7 @@ func TestDeleteUndoRestoresIDsAndRefs(t *testing.T) {
aID, childID := s.ID(a), s.ID(child) aID, childID := s.ID(a), s.ID(child)
h := history.New(target{s}) h := history.New(target{s})
if err := h.Exec(del(s, aID)); err != nil { if err := h.Exec(&Delete{Node: aID}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !s.Handle(aID).IsZero() || !s.Handle(childID).IsZero() { if !s.Handle(aID).IsZero() || !s.Handle(childID).IsZero() {
@@ -110,7 +100,7 @@ func TestReparentPreservesWorld(t *testing.T) {
before := s.World(n) before := s.World(n)
h := history.New(target{s}) h := history.New(target{s})
if err := h.Exec(reparent(s, n, p)); err != nil { if err := h.Exec(&Reparent{Node: s.ID(n), NewParent: s.ID(p), NewIndex: -1, KeepWorld: true}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if s.Parent(n) != p { if s.Parent(n) != p {
@@ -136,7 +126,7 @@ func TestReparentUnderZeroScaleFails(t *testing.T) {
s.SetLocal(p, scene.Transform2D{Scale: mathx.Vec2{X: 0, Y: 1e-9}}) s.SetLocal(p, scene.Transform2D{Scale: mathx.Vec2{X: 0, Y: 1e-9}})
s.Flush() s.Flush()
h := history.New(target{s}) h := history.New(target{s})
err := h.Exec(reparent(s, n, p)) err := h.Exec(&Reparent{Node: s.ID(n), NewParent: s.ID(p), NewIndex: -1, KeepWorld: true})
if !errs.Has(err, errs.SceneBadReparent) { if !errs.Has(err, errs.SceneBadReparent) {
t.Fatalf("err = %v", err) t.Fatalf("err = %v", err)
} }
@@ -180,17 +170,16 @@ func TestCreateAndComponentRoundTrip(t *testing.T) {
s := newScene(t) s := newScene(t)
h := history.New(target{s}) h := history.New(target{s})
c := NewMutation("Create Enemy", EnvFor(s), Insert(ids.NodeID{}, -1, string(fragment))) c := &Create{Index: -1, Fragment: fragment, Name: "Enemy"}
if err := h.Exec(c); err != nil { if err := h.Exec(c); err != nil {
t.Fatal(err) t.Fatal(err)
} }
roots := c.Created() if len(c.Roots()) != 1 {
if len(roots) != 1 { t.Fatalf("roots = %d", len(c.Roots()))
t.Fatalf("roots = %d", len(roots))
} }
n := s.Handle(roots[0]) n := s.Handle(c.Roots()[0])
td, _ := s.Registry().ByID(scene.Store[Health](s).Type()) typeID := scene.Store[Health](s).Type()
if err := h.Exec(NewMutation("Remove Health", EnvFor(s), RemoveComp(s.ID(n), td))); err != nil { if err := h.Exec(&RemoveComponent{Node: s.ID(n), Type: typeID, Name: "Health"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if scene.Has[Health](s, n) { if scene.Has[Health](s, n) {
@@ -201,11 +190,11 @@ func TestCreateAndComponentRoundTrip(t *testing.T) {
t.Fatal("component data not restored") t.Fatal("component data not restored")
} }
h.Undo() h.Undo()
if !s.Handle(roots[0]).IsZero() { if !s.Handle(c.Roots()[0]).IsZero() {
t.Fatal("create not undone") t.Fatal("create not undone")
} }
h.Redo() h.Redo()
if s.Handle(roots[0]).IsZero() { if s.Handle(c.Roots()[0]).IsZero() {
t.Fatal("redo must recreate the same node id") t.Fatal("redo must recreate the same node id")
} }
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+51
View File
@@ -3,10 +3,61 @@ package commands
import ( import (
"aego/core/errs" "aego/core/errs"
"aego/core/ids" "aego/core/ids"
"aego/core/log"
"aego/editor/core/history"
"aego/scene" "aego/scene"
"aego/scene/sceneio" "aego/scene/sceneio"
) )
type Replace struct {
Node ids.NodeID
Before []byte
After []byte
Name string
Post func(s *scene.Scene)
parent ids.NodeID
index int
ready bool
}
func (c *Replace) Label() string {
if c.Name != "" {
return c.Name
}
return "Replace"
}
func (c *Replace) Do(t history.Target) error {
s := t.Scene()
if err := c.swap(s, c.After); err != nil {
return err
}
if c.Post != nil {
c.Post(s)
}
return nil
}
func (c *Replace) Undo(t history.Target) error {
return c.swap(t.Scene(), c.Before)
}
func (c *Replace) Merge(history.Command) bool {
return false
}
func (c *Replace) swap(s *scene.Scene, data []byte) error {
if !c.ready {
h := s.Handle(c.Node)
if h.IsZero() || !s.IsAlive(h) {
return errs.New(errs.SceneDeadNode, "node is not in the scene", log.F("node", c.Node.String()))
}
c.parent, c.index, c.ready = s.ID(s.Parent(h)), ChildIndex(s, h), true
}
_, err := ReplaceNode(s, c.Node, c.parent, c.index, data, nil)
return err
}
func ReplaceNode(s *scene.Scene, node, parent ids.NodeID, index int, data []byte, lib *sceneio.Library) (scene.NodeHandle, error) { func ReplaceNode(s *scene.Scene, node, parent ids.NodeID, index int, data []byte, lib *sceneio.Library) (scene.NodeHandle, error) {
if h := s.Handle(node); !h.IsZero() && s.IsAlive(h) { if h := s.Handle(node); !h.IsZero() && s.IsAlive(h) {
s.Destroy(h) s.Destroy(h)
+4 -4
View File
@@ -18,7 +18,7 @@ func TestReparent3DPreservesWorld(t *testing.T) {
s.Flush() s.Flush()
before := s.World4(n) before := s.World4(n)
h := history.New(target{s}) h := history.New(target{s})
if err := h.Exec(reparent(s, n, p)); err != nil { if err := h.Exec(&Reparent{Node: s.ID(n), NewParent: s.ID(p), NewIndex: -1, KeepWorld: true}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if s.Parent(n) != p { if s.Parent(n) != p {
@@ -39,7 +39,7 @@ func TestReparent3DUnderTwoDFails(t *testing.T) {
n := s.Create3D(s.Root(), "N") n := s.Create3D(s.Root(), "N")
s.Flush() s.Flush()
h := history.New(target{s}) h := history.New(target{s})
err := h.Exec(reparent(s, n, flat)) err := h.Exec(&Reparent{Node: s.ID(n), NewParent: s.ID(flat), NewIndex: -1, KeepWorld: true})
if !errs.Has(err, errs.SceneSpaceMismatch) { if !errs.Has(err, errs.SceneSpaceMismatch) {
t.Fatalf("err = %v", err) t.Fatalf("err = %v", err)
} }
@@ -57,7 +57,7 @@ func TestReparent2DIntoThreeDKeepsWorld(t *testing.T) {
s.Flush() s.Flush()
before := s.World4(n) before := s.World4(n)
h := history.New(target{s}) h := history.New(target{s})
if err := h.Exec(reparent(s, n, p)); err != nil { if err := h.Exec(&Reparent{Node: s.ID(n), NewParent: s.ID(p), NewIndex: -1, KeepWorld: true}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !s.Under3D(n) || !s.World4(n).NearlyEqual(before, 1e-4) { if !s.Under3D(n) || !s.World4(n).NearlyEqual(before, 1e-4) {
@@ -97,7 +97,7 @@ func TestDeleteUndoKeepsSpace(t *testing.T) {
s.Flush() s.Flush()
id := s.ID(n) id := s.ID(n)
h := history.New(target{s}) h := history.New(target{s})
if err := h.Exec(del(s, id)); err != nil { if err := h.Exec(&Delete{Node: id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
h.Undo() h.Undo()
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -480,7 +480,6 @@ func (c *Controller) CopyToEdit(nodes []ids.NodeID, t schema.TypeID) error {
if !ok { if !ok {
return errs.New(errs.SchemaUnknownType, "component type is not registered") return errs.New(errs.SchemaUnknownType, "component type is not registered")
} }
env := commands.EnvFor(es)
tx := real.History.Begin("Copy " + td.FullName + " from Play") tx := real.History.Begin("Copy " + td.FullName + " from Play")
for _, n := range nodes { for _, n := range nodes {
if _, has := es.GetByType(es.Handle(n), t); !has { if _, has := es.GetByType(es.Handle(n), t); !has {
@@ -493,7 +492,7 @@ func (c *Controller) CopyToEdit(nodes []ids.NodeID, t schema.TypeID) error {
} }
values = append(values, commands.FieldValue{Field: td.Fields[i].ID, Value: v}) values = append(values, commands.FieldValue{Field: td.Fields[i].ID, Value: v})
} }
if err := tx.Exec(commands.NewMutation("Add "+td.FullName, env, commands.AddComp(es, n, td, values))); err != nil { if err := tx.Exec(&commands.AddComponent{Node: n, Type: t, Name: td.FullName, Values: values}); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
-12
View File
@@ -3,7 +3,6 @@ package playdiff
import ( import (
"reflect" "reflect"
"strconv" "strconv"
"strings"
"aego/core/curve" "aego/core/curve"
"aego/core/ids" "aego/core/ids"
@@ -13,7 +12,6 @@ import (
"aego/editor/core/history/commands" "aego/editor/core/history/commands"
"aego/editor/core/props" "aego/editor/core/props"
"aego/scene" "aego/scene"
"aego/scene/mutate"
"aego/scene/scriptcomp" "aego/scene/scriptcomp"
"aego/schema" "aego/schema"
) )
@@ -98,16 +96,6 @@ func Touched(h *history.History) map[Key]bool {
switch c := c.(type) { switch c := c.(type) {
case *commands.SetProperty: case *commands.SetProperty:
out[Key{Node: c.Node, Type: c.Type, Field: c.Field}] = true out[Key{Node: c.Node, Type: c.Type, Field: c.Field}] = true
case *commands.Mutation:
for _, ch := range c.Changes() {
switch {
case ch.Op != mutate.KindSet && ch.Op != mutate.KindReset:
case ch.Type != 0:
out[Key{Node: ch.NodeID, Type: ch.Type, Field: ch.Field}] = true
case strings.HasPrefix(ch.Path, "transform."):
out[Key{Node: ch.NodeID}] = true
}
}
case *commands.SetTransform: case *commands.SetTransform:
out[Key{Node: c.Node}] = true out[Key{Node: c.Node}] = true
case *commands.SetTransform3D: case *commands.SetTransform3D:
+1 -12
View File
@@ -45,7 +45,7 @@ func pair(t *testing.T) (*document.SceneDocument, *document.SceneDocument, ids.N
} }
s.Flush() s.Flush()
d := m.New(rel, s) d := m.New(rel, s)
if err := d.History.Exec(commands.NewMutation("Add "+td.FullName, commands.EnvFor(s), commands.AddComp(s, id, td, nil))); err != nil { if err := d.History.Exec(&commands.AddComponent{Node: id, Type: td.ID, Name: td.FullName}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
docs[i] = d docs[i] = d
@@ -108,14 +108,3 @@ func TestApplyIsOneStep(t *testing.T) {
t.Fatal("undo must restore the edit scene") t.Fatal("undo must restore the edit scene")
} }
} }
func TestTouchedSeesInspectorEdits(t *testing.T) {
_, play, id, td := pair(t)
fd, _ := td.Field("tint")
if err := play.Props.Set(nil, []ids.NodeID{id}, td.ID, fd.ID, schema.Color(mathx.Color{G: 1, A: 1})); err != nil {
t.Fatal(err)
}
if !playdiff.Touched(play.History)[playdiff.Key{Node: id, Type: td.ID, Field: fd.ID}] {
t.Fatal("a field set through the Inspector is not touched")
}
}
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -15,7 +15,6 @@ import (
"aego/editor/core/document" "aego/editor/core/document"
"aego/editor/core/history/commands" "aego/editor/core/history/commands"
"aego/scene" "aego/scene"
"aego/scene/mutate"
"aego/scene/prefab" "aego/scene/prefab"
"aego/scene/sceneio" "aego/scene/sceneio"
) )
@@ -218,7 +217,7 @@ func (s *Service) CreateFrom(d *document.SceneDocument, nodes []ids.NodeID, dir
tx.Abort() tx.Abort()
return err return err
} }
if err := tx.Exec(commands.NewMutation("Create Prefab", s.env(sc), mutate.Replace(commands.Ref(sc.ID(h)), string(after)))); err != nil { if err := tx.Exec(&commands.Replace{Node: sc.ID(h), Before: before, After: after, Name: "Create Prefab"}); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
+6 -9
View File
@@ -6,7 +6,6 @@ import (
"aego/editor/core/history" "aego/editor/core/history"
"aego/editor/core/history/commands" "aego/editor/core/history/commands"
"aego/scene" "aego/scene"
"aego/scene/mutate"
) )
func (a *Access) Rename(tx *history.Transaction, node ids.NodeID, name string) error { func (a *Access) Rename(tx *history.Transaction, node ids.NodeID, name string) error {
@@ -14,29 +13,27 @@ func (a *Access) Rename(tx *history.Transaction, node ids.NodeID, name string) e
if err != nil { if err != nil {
return err return err
} }
if a.s.Name(h) == name || name == "" { before := a.s.Name(h)
if before == name || name == "" {
return nil return nil
} }
return a.within(tx, "Rename", func(tx *history.Transaction) error { return a.within(tx, "Rename", func(tx *history.Transaction) error {
return tx.Exec(commands.NewMutation("Rename", commands.EnvFor(a.s), mutate.Rename(commands.Ref(node), name))) return tx.Exec(&commands.Rename{Node: node, Before: before, After: name})
}) })
} }
func (a *Access) SetEnabled(tx *history.Transaction, nodes []ids.NodeID, v bool) error { func (a *Access) SetEnabled(tx *history.Transaction, nodes []ids.NodeID, v bool) error {
text := "false"
if v {
text = "true"
}
return a.within(tx, "Toggle Enabled", func(tx *history.Transaction) error { return a.within(tx, "Toggle Enabled", func(tx *history.Transaction) error {
for _, n := range nodes { for _, n := range nodes {
h, err := a.handle(n) h, err := a.handle(n)
if err != nil { if err != nil {
return err return err
} }
if a.s.Enabled(h) == v { before := a.s.Enabled(h)
if before == v {
continue continue
} }
if err := tx.Exec(commands.NewMutation("Toggle Enabled", commands.EnvFor(a.s), mutate.Set(commands.Ref(n), "enabled", text))); err != nil { if err := tx.Exec(&commands.SetEnabled{Node: n, Before: before, After: v}); err != nil {
return err return err
} }
} }
+2 -2
View File
@@ -113,7 +113,7 @@ func (a *Access) within(tx *history.Transaction, label string, fn func(*history.
} }
func (a *Access) Set(tx *history.Transaction, nodes []ids.NodeID, t schema.TypeID, f schema.FieldID, v schema.Value) error { func (a *Access) Set(tx *history.Transaction, nodes []ids.NodeID, t schema.TypeID, f schema.FieldID, v schema.Value) error {
td, fd, err := a.Descriptor(t, f) _, fd, err := a.Descriptor(t, f)
if err != nil { if err != nil {
return err return err
} }
@@ -131,7 +131,7 @@ func (a *Access) Set(tx *history.Transaction, nodes []ids.NodeID, t schema.TypeI
if Equal(before, nv) { if Equal(before, nv) {
continue continue
} }
if err := tx.Exec(commands.SetField(a.s, n, td, fd, nv, name)); err != nil { if err := tx.Exec(&commands.SetProperty{Node: n, Type: t, Field: f, Before: before, After: nv, Name: name}); err != nil {
return err return err
} }
} }
+7 -3
View File
@@ -372,12 +372,16 @@ func InstantiatePlace(ctx *context.Editor, t Template, parent ids.NodeID, index
if err := place(s, scratch, h, parent, pl); err != nil { if err := place(s, scratch, h, parent, pl); err != nil {
return nil, err return nil, err
} }
cmd := commands.NewMutation("Create "+t.Name, commands.EnvFor(s), cmd := &commands.Create{
commands.Insert(parent, index, string(sceneio.SaveSubtree(scratch, h, true)))) Parent: parent,
Index: index,
Fragment: sceneio.SaveSubtree(scratch, h, true),
Name: t.Name,
}
if err := d.History.Exec(cmd); err != nil { if err := d.History.Exec(cmd); err != nil {
return nil, err return nil, err
} }
created := cmd.Created() created := append([]ids.NodeID(nil), cmd.Roots()...)
ctx.Select(created...) ctx.Select(created...)
return created, nil return created, nil
} }
+13 -9
View File
@@ -681,29 +681,33 @@ func (p *Panel) move(d *document.SceneDocument, nodes []ids.NodeID, target ids.N
if n == th { if n == th {
continue continue
} }
parent, index := target, -1 cmd := &commands.Reparent{Node: s.ID(n), KeepWorld: true}
switch zone { switch zone {
case zoneInto:
cmd.NewParent = target
cmd.NewIndex = -1
case zoneBefore: case zoneBefore:
ph := s.Parent(th) parent := s.Parent(th)
idx := commands.ChildIndex(s, th) idx := commands.ChildIndex(s, th)
if s.Parent(n) == ph && commands.ChildIndex(s, n) < idx { if s.Parent(n) == parent && commands.ChildIndex(s, n) < idx {
idx-- idx--
} }
parent, index = s.ID(ph), idx cmd.NewParent = s.ID(parent)
cmd.NewIndex = idx
case zoneAfter: case zoneAfter:
ph := s.Parent(anchor) parent := s.Parent(anchor)
idx := commands.ChildIndex(s, anchor) + 1 idx := commands.ChildIndex(s, anchor) + 1
if s.Parent(n) == ph && commands.ChildIndex(s, n) < idx { if s.Parent(n) == parent && commands.ChildIndex(s, n) < idx {
idx-- idx--
} }
parent, index = s.ID(ph), idx cmd.NewParent = s.ID(parent)
cmd.NewIndex = idx
} }
if p.d.Prefabs != nil && parent != s.ID(s.Parent(n)) && p.d.Prefabs.Locked(d, s.ID(n)) { if p.d.Prefabs != nil && cmd.NewParent != s.ID(s.Parent(n)) && p.d.Prefabs.Locked(d, s.ID(n)) {
tx.Abort() tx.Abort()
p.d.Notices.Notify(context.LevelWarn, s.Name(n)+" comes from a prefab and keeps its parent, unpack the instance to move it") p.d.Notices.Notify(context.LevelWarn, s.Name(n)+" comes from a prefab and keeps its parent, unpack the instance to move it")
return return
} }
cmd := commands.NewMutation("Reparent", commands.EnvFor(s), commands.Move(s.ID(n), parent, index, true))
if err := tx.Exec(cmd); err != nil { if err := tx.Exec(cmd); err != nil {
tx.Abort() tx.Abort()
p.d.Notices.Notify(context.LevelError, err.Error()) p.d.Notices.Notify(context.LevelError, err.Error())
+4 -10
View File
@@ -647,10 +647,9 @@ func (p *Panel) component(d *document.SceneDocument, t schema.TypeID) {
func (p *Panel) remove(d *document.SceneDocument, td *schema.TypeDescriptor) { func (p *Panel) remove(d *document.SceneDocument, td *schema.TypeDescriptor) {
p.confirm(d, "Remove "+p.reg.Meta(td).DisplayName, func() error { p.confirm(d, "Remove "+p.reg.Meta(td).DisplayName, func() error {
env := commands.EnvFor(d.Scene())
tx := d.History.Begin("Remove " + td.FullName) tx := d.History.Begin("Remove " + td.FullName)
for _, id := range p.nodes { for _, id := range p.nodes {
if err := tx.Exec(commands.NewMutation("Remove "+td.FullName, env, commands.RemoveComp(id, td))); err != nil { if err := tx.Exec(&commands.RemoveComponent{Node: id, Type: td.ID, Name: td.FullName}); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
@@ -662,16 +661,13 @@ func (p *Panel) remove(d *document.SceneDocument, td *schema.TypeDescriptor) {
func (p *Panel) reset(d *document.SceneDocument, td *schema.TypeDescriptor) { func (p *Panel) reset(d *document.SceneDocument, td *schema.TypeDescriptor) {
p.confirm(d, "Reset "+p.reg.Meta(td).DisplayName, func() error { p.confirm(d, "Reset "+p.reg.Meta(td).DisplayName, func() error {
s := d.Scene()
env := commands.EnvFor(s)
defaults := p.reg.Defaults(td.ID)
tx := d.History.Begin("Reset " + td.FullName) tx := d.History.Begin("Reset " + td.FullName)
for _, id := range p.nodes { for _, id := range p.nodes {
if err := tx.Exec(commands.NewMutation("Remove "+td.FullName, env, commands.RemoveComp(id, td))); err != nil { if err := tx.Exec(&commands.RemoveComponent{Node: id, Type: td.ID, Name: td.FullName}); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
if err := tx.Exec(commands.NewMutation("Add "+td.FullName, env, commands.AddComp(s, id, td, defaults))); err != nil { if err := tx.Exec(&commands.AddComponent{Node: id, Type: td.ID, Name: td.FullName, Values: p.reg.Defaults(td.ID)}); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
@@ -800,14 +796,12 @@ func (p *Panel) addComponent(d *document.SceneDocument) {
func (p *Panel) add(d *document.SceneDocument, td *schema.TypeDescriptor) { func (p *Panel) add(d *document.SceneDocument, td *schema.TypeDescriptor) {
p.confirm(d, "Add "+p.reg.Meta(td).DisplayName, func() error { p.confirm(d, "Add "+p.reg.Meta(td).DisplayName, func() error {
s := d.Scene() s := d.Scene()
env := commands.WithOnAdd(commands.EnvFor(s), p.d.Clip.OnAdd)
defaults := p.reg.Defaults(td.ID)
tx := d.History.Begin("Add " + td.FullName) tx := d.History.Begin("Add " + td.FullName)
for _, n := range p.nodes { for _, n := range p.nodes {
if _, ok := s.GetByType(s.Handle(n), td.ID); ok { if _, ok := s.GetByType(s.Handle(n), td.ID); ok {
continue continue
} }
if err := tx.Exec(commands.NewMutation("Add "+td.FullName, env, commands.AddComp(s, n, td, defaults))); err != nil { if err := tx.Exec(&commands.AddComponent{Node: n, Type: td.ID, Name: td.FullName, Values: p.reg.Defaults(td.ID), OnAdd: p.d.Clip.OnAdd(td)}); err != nil {
tx.Abort() tx.Abort()
return err return err
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-87
View File
@@ -1,87 +0,0 @@
package mutate
import (
"bytes"
"aego/asset"
"aego/core/ids"
"aego/scene"
"aego/scene/sceneio"
"aego/schema"
)
const RootName = "Root"
type Env struct {
Types *scene.Types
Resolver asset.Resolver
Library *sceneio.Library
Assets func(ids.AssetID) bool
Lookup func(path string) (ids.AssetID, bool)
OnAdd func(td *schema.TypeDescriptor, s *scene.Scene, h scene.NodeHandle)
OnRemove func(td *schema.TypeDescriptor, s *scene.Scene, h scene.NodeHandle)
}
type Problem struct {
Error bool `json:"error"`
Node string `json:"node,omitempty"`
Message string `json:"message"`
}
type Document struct {
Name string
Prefab bool
Scene *scene.Scene
Problems []Problem
env Env
reg *schema.Registry
}
func isPrefab(src []byte) bool {
return bytes.HasPrefix(bytes.TrimSpace(src), []byte("prefab"))
}
func Load(src []byte, name string, env Env) (*Document, error) {
s, rep, err := sceneio.Load(src, name, env.Types, env.Resolver, sceneio.LoadOptions{KeepIDs: true, Prefabs: env.Library})
if err != nil {
return nil, err
}
d := &Document{Name: name, Prefab: isPrefab(src), Scene: s, env: env, reg: s.Registry()}
for _, w := range rep.Warnings {
d.Problems = append(d.Problems, Problem{Node: w.Path, Message: w.Msg})
}
for _, u := range rep.Unknown {
d.Problems = append(d.Problems, Problem{Message: "component " + u + " is not registered; it is kept unchanged"})
}
return d, nil
}
func Wrap(s *scene.Scene, name string, prefab bool, env Env) *Document {
return &Document{Name: name, Prefab: prefab, Scene: s, env: env, reg: s.Registry()}
}
func (d *Document) Bytes() []byte {
if d.Prefab {
return sceneio.SavePrefab(d.Scene)
}
return sceneio.Save(d.Scene)
}
func (d *Document) library() *sceneio.Library {
if d.env.Library == nil {
d.env.Library = sceneio.NewLibrary(d.reg, d.Scene.Resolver())
}
return d.env.Library
}
func (d *Document) assetID(text string) (ids.AssetID, error) {
if id, err := ids.ParseAssetID(text); err == nil {
return id, nil
}
if d.env.Lookup != nil {
if id, ok := d.env.Lookup(text); ok {
return id, nil
}
}
return ids.AssetID{}, errNoAsset(text)
}
-171
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-141
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-94
View File
@@ -1,94 +0,0 @@
package prefab
type Level uint8
const (
LevelProperty Level = iota
LevelComponent
LevelNode
LevelAll
LevelOrphan
)
const (
OwnerAdded = "#node"
OwnerRemove = "#remove"
OwnerOrder = "#order"
)
var levelNames = [...]string{"property", "component", "node", "all", "orphan"}
func (l Level) String() string {
if int(l) < len(levelNames) {
return levelNames[l]
}
return "all"
}
func ParseLevel(s string) (Level, bool) {
if s == "" {
return LevelAll, true
}
for i, n := range levelNames {
if n == s {
return Level(i), true
}
}
return LevelAll, false
}
type Pick struct {
Level Level
Key Key
Prop PropKey
}
func (p Pick) wants(orphan bool, k Key, owner, field string, rootKey Key) bool {
if p.Level == LevelOrphan {
if !orphan || k != p.Key {
return false
}
return p.Prop.Owner == "" || (owner == p.Prop.Owner && (p.Prop.Field == "" || field == p.Prop.Field))
}
if orphan || (k == rootKey && owner == OwnerTransform) {
return false
}
switch p.Level {
case LevelProperty:
return k == p.Key && owner == p.Prop.Owner && field == p.Prop.Field
case LevelComponent:
return k == p.Key && owner == p.Prop.Owner
case LevelNode:
return k == p.Key
}
return true
}
func (p Pick) Split(o Overrides, rootKey Key) (sel, rest Overrides) {
put := func(take bool, fn func(*Overrides)) {
if take {
fn(&sel)
return
}
fn(&rest)
}
for _, x := range o.Props {
put(p.wants(x.Orphan, x.Node, x.Prop.Owner, x.Prop.Field, rootKey), func(t *Overrides) { t.Props = append(t.Props, x) })
}
for _, x := range o.AddComps {
put(p.wants(x.Orphan, x.Node, x.Comp.Owner, "", rootKey), func(t *Overrides) { t.AddComps = append(t.AddComps, x) })
}
for _, x := range o.RemoveComps {
put(p.wants(false, x.Node, x.Owner, "", rootKey), func(t *Overrides) { t.RemoveComps = append(t.RemoveComps, x) })
}
for _, x := range o.AddNodes {
put(p.wants(x.Orphan, LocalKey(x.Node.Local), OwnerAdded, "", rootKey), func(t *Overrides) { t.AddNodes = append(t.AddNodes, x) })
}
for _, k := range o.Remove {
put(p.wants(false, k, OwnerRemove, "", rootKey), func(t *Overrides) { t.Remove = append(t.Remove, k) })
}
for _, x := range o.Order {
put(p.wants(false, x.Parent, OwnerOrder, "", rootKey), func(t *Overrides) { t.Order = append(t.Order, x) })
}
return sel, rest
}
+1 -1
View File
@@ -50,7 +50,7 @@ func TestEditorPlayDiff(t *testing.T) {
} }
s.Flush() s.Flush()
d := m.New(rel, s) d := m.New(rel, s)
if err := d.History.Exec(commands.NewMutation("Add "+td.FullName, commands.EnvFor(s), commands.AddComp(s, id, td, nil))); err != nil { if err := d.History.Exec(&commands.AddComponent{Node: id, Type: td.ID, Name: td.FullName}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
docs[i] = d docs[i] = d
+1 -1
View File
@@ -34,7 +34,7 @@ func spriteNodes(t *testing.T, n int) (*testEditor, []ids.NodeID, *schema.TypeDe
h := sc.Create(sc.Root(), "N") h := sc.Create(sc.Root(), "N")
sc.Flush() sc.Flush()
id := sc.ID(h) id := sc.ID(h)
if err := d.History.Exec(commands.NewMutation("Add "+td.FullName, commands.EnvFor(sc), commands.AddComp(sc, id, td, nil))); err != nil { if err := d.History.Exec(&commands.AddComponent{Node: id, Type: td.ID, Name: td.FullName}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
nodes = append(nodes, id) nodes = append(nodes, id)
-2
View File
@@ -33,7 +33,6 @@ func keyHold(s interface{ Input() *input.State }, k input.Scancode, down bool) {
func drive(e interface{ Input() *input.State }, pc *play.Controller, from, to int, at func(frame int)) { func drive(e interface{ Input() *input.State }, pc *play.Controller, from, to int, at func(frame int)) {
for i := from; i < to; i++ { for i := from; i < to; i++ {
e.Input().BeginFrame()
keyHold(e, input.KeyD, i%400 >= 20 && i%400 < 200) keyHold(e, input.KeyD, i%400 >= 20 && i%400 < 200)
keyHold(e, input.KeyA, i%400 >= 220 && i%400 < 380) keyHold(e, input.KeyA, i%400 >= 220 && i%400 < 380)
keyHold(e, input.KeySpace, i%90 >= 60 && i%90 < 64) keyHold(e, input.KeySpace, i%90 >= 60 && i%90 < 64)
@@ -195,7 +194,6 @@ func TestKeyframeReplayWritesNoSaves(t *testing.T) {
s, pc := rewindSession(t) s, pc := rewindSession(t)
e := s.Host.Engine e := s.Host.Engine
drive(e, pc, 0, 99, nil) drive(e, pc, 0, 99, nil)
e.Input().BeginFrame()
keyHold(e, input.KeyF5, true) keyHold(e, input.KeyF5, true)
pc.Tick(1.0 / 60) pc.Tick(1.0 / 60)
keyHold(e, input.KeyF5, false) keyHold(e, input.KeyF5, false)
-38
View File
@@ -141,41 +141,3 @@ func TestPrefabEditorFlow(t *testing.T) {
t.Fatal("instances without an override follow the prefab") t.Fatal("instances without an override follow the prefab")
} }
} }
func TestCreatePrefabUndo(t *testing.T) {
_, ctx := openEditor(t)
if err := ctx.NewScene("scenes/undo.aescene"); err != nil {
t.Fatal(err)
}
d := ctx.Doc()
sc := d.Scene()
crate := sc.Create(sc.Root(), "Crate")
sc.Create(crate, "Lid")
sc.Flush()
id := sc.ID(crate)
before := sceneio.SaveSubtree(sc, crate, true)
if err := prefabs.New(ctx).CreateFrom(d, []ids.NodeID{id}, ""); err != nil {
t.Fatal(err)
}
if pi, ok := sceneio.InstanceOf(sc, sc.Handle(id)); !ok || !pi.Owner {
t.Fatal("node must become a prefab instance")
}
if got := d.History.Steps(nil); len(got) == 0 || got[len(got)-1] != "Create Prefab" {
t.Fatalf("history %v", got)
}
if err := d.History.Undo(); err != nil {
t.Fatal(err)
}
if _, ok := sceneio.InstanceOf(sc, sc.Handle(id)); ok {
t.Fatal("undo must turn the instance back into plain nodes")
}
if got := sceneio.SaveSubtree(sc, sc.Handle(id), true); string(got) != string(before) {
t.Fatalf("undo changed the subtree:\n%s", got)
}
if err := d.History.Redo(); err != nil {
t.Fatal(err)
}
if pi, ok := sceneio.InstanceOf(sc, sc.Handle(id)); !ok || !pi.Owner {
t.Fatal("redo must link the instance again")
}
}
+2 -2
View File
@@ -164,7 +164,7 @@ func TestFakeComponentHooks(t *testing.T) {
ctx := f.s.App.Context() ctx := f.s.App.Context()
d := f.doc d := f.doc
crate := f.node("Crate") crate := f.node("Crate")
add := commands.NewMutation("Add "+f.body.FullName, commands.WithOnAdd(commands.EnvFor(d.Scene()), ctx.OnAdd), commands.AddComp(d.Scene(), crate, f.body, nil)) add := &commands.AddComponent{Node: crate, Type: f.body.ID, Name: f.body.FullName, OnAdd: ctx.OnAdd(f.body)}
if err := d.History.Exec(add); err != nil { if err := d.History.Exec(add); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -184,7 +184,7 @@ func TestFakeComponentHooks(t *testing.T) {
if f.m.Changed != 3 { if f.m.Changed != 3 {
t.Fatalf("OnChanged after set and undo ran %d times, want 3", f.m.Changed) t.Fatalf("OnChanged after set and undo ran %d times, want 3", f.m.Changed)
} }
if err := d.History.Exec(commands.NewMutation("Remove "+f.body.FullName, commands.EnvFor(d.Scene()), commands.RemoveComp(crate, f.body))); err != nil { if err := d.History.Exec(&commands.RemoveComponent{Node: crate, Type: f.body.ID, Name: f.body.FullName}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if f.m.Removed != 1 || f.m.LastRemoved.Size != 2 { if f.m.Removed != 1 || f.m.LastRemoved.Size != 2 {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-87
View File
@@ -1,87 +0,0 @@
//go:build aego_editor
package mutate_test
import (
"testing"
"unsafe"
"aego/editor/core/history/commands"
"aego/scene"
"aego/scene/scriptcomp"
"aego/schema"
)
func defaults(td *schema.TypeDescriptor) []commands.FieldValue {
var p unsafe.Pointer
switch {
case td.Default != nil:
p = td.Default()
case td.New != nil:
p = td.New()
default:
return nil
}
out := make([]commands.FieldValue, 0, len(td.Fields))
for i := range td.Fields {
var v schema.Value
td.Fields[i].Get(p, &v)
out = append(out, commands.FieldValue{Field: td.Fields[i].ID, Value: v})
}
return out
}
func freshNode(s *scene.Scene, td *schema.TypeDescriptor) scene.NodeHandle {
var h scene.NodeHandle
if td.Space.Allows(scene.Space2D.Kind()) {
h = s.Create2D(s.Root(), "N")
} else {
h = s.Create3D(s.Root(), "N")
}
s.Flush()
return h
}
func TestEveryComponentPassesTheEditorPaths(t *testing.T) {
e := env(t)
reg := e.Types.Schema()
types, fields := 0, 0
for _, td := range reg.Components() {
if td.Hidden || td.FullName == "engine.Unknown" || td.ID == scriptcomp.ScriptTypeID {
continue
}
types++
list := defaults(td)
s := scene.New(e.Types, nil, scene.Options{})
h := freshNode(s, td)
id := s.ID(h)
if err := commands.NewMutation("Add", commands.EnvFor(s), commands.AddComp(s, id, td, list)).Do(target{s}); err != nil {
t.Errorf("%s: add-comp: %v", td.FullName, err)
continue
}
scratch := scene.New(e.Types, nil, scene.Options{})
sh := freshNode(scratch, td)
if err := commands.NewMutation("", commands.EnvFor(scratch), commands.AddRaw(scratch, scratch.ID(sh), td, list)).Do(target{scratch}); err != nil {
t.Errorf("%s: raw add-comp: %v", td.FullName, err)
}
for i := range td.Fields {
fd := &td.Fields[i]
if !fd.Flags.Has(schema.FlagSerialize) || !fd.Flags.Has(schema.FlagEditor) || fd.Flags.Has(schema.FlagReadOnly) {
continue
}
fields++
cur, err := s.GetProperty(h, td.ID, fd.ID)
if err != nil {
t.Errorf("%s.%s: %v", td.FullName, fd.Name, err)
continue
}
if err := commands.SetField(s, id, td, fd, cur, fd.Name).Do(target{s}); err != nil {
t.Errorf("%s.%s: set: %v", td.FullName, fd.Name, err)
}
}
}
if types < 10 || fields < 50 {
t.Fatalf("only %d components and %d fields were checked", types, fields)
}
t.Logf("%d components, %d fields", types, fields)
}
-2
View File
@@ -109,7 +109,6 @@ var rules = []rule{
{"app", []string{"..."}, nil}, {"app", []string{"..."}, nil},
{"app/docstool", []string{"..."}, nil}, {"app/docstool", []string{"..."}, nil},
{"app/docsview", []string{"..."}, nil}, {"app/docsview", []string{"..."}, nil},
{"scene/mutate", []string{"core/...", "schema", "asset", "format/...", "scene", "scene/sceneio", "scene/prefab", "scene/scriptcomp", "data"}, nil},
{"app/edit", []string{"..."}, nil}, {"app/edit", []string{"..."}, nil},
{"app/projects", []string{"..."}, nil}, {"app/projects", []string{"..."}, nil},
{"app/supervise", []string{"..."}, nil}, {"app/supervise", []string{"..."}, nil},
@@ -242,7 +241,6 @@ var denied = []struct {
{"engine/base", []string{"modules/render3d", "modules/render3d/...", "asset/pipeline", "asset/importers/...", "editor/..."}}, {"engine/base", []string{"modules/render3d", "modules/render3d/...", "asset/pipeline", "asset/importers/...", "editor/..."}},
{"app/playground", []string{"platform/sdl", "gpu/gl33", "gpu/gl33/...", "asset/pipeline", "asset/importers/...", "modules/render3d", "modules/render3d/...", "editor/..."}}, {"app/playground", []string{"platform/sdl", "gpu/gl33", "gpu/gl33/...", "asset/pipeline", "asset/importers/...", "modules/render3d", "modules/render3d/...", "editor/..."}},
{"app/docsview", []string{"platform/sdl", "gpu/gl33", "gpu/gl33/...", "editor/shell", "editor/shell/...", "asset/importers/...", "script/compiler", "script/workspace"}}, {"app/docsview", []string{"platform/sdl", "gpu/gl33", "gpu/gl33/...", "editor/shell", "editor/shell/...", "asset/importers/...", "script/compiler", "script/workspace"}},
{"scene/mutate", []string{"editor/...", "ui/...", "platform/...", "engine", "engine/...", "app/..."}},
{"runtime/...", []string{"editor/..."}}, {"runtime/...", []string{"editor/..."}},
{"runtime/...", []string{"asset/pipeline", "asset/importers/...", "project", "app/run", "app/edit", "app/export", "app/export/..."}}, {"runtime/...", []string{"asset/pipeline", "asset/importers/...", "project", "app/run", "app/edit", "app/export", "app/export/..."}},
{"sdk", []string{"editor/..."}}, {"sdk", []string{"editor/..."}},