Files

271 lines
7.8 KiB
Go

package author
import (
"math/rand"
"slices"
"strconv"
"testing"
"aego/core/ids"
"aego/core/mathx"
"aego/scene/prefab"
"aego/schema"
)
type library map[prefab.PrefabID]*prefab.Node
func (l library) Prefab(id prefab.PrefabID) (*prefab.Node, bool) {
n, ok := l[id]
return n, ok
}
type gen struct {
r *rand.Rand
}
func (g *gen) id() ids.NodeID {
return ids.NodeID{Hi: g.r.Uint64() | 1, Lo: g.r.Uint64()}
}
func (g *gen) f32() schema.Value {
return schema.Float32(float32(g.r.Intn(2000)-1000) / 8)
}
func (g *gen) vec() schema.Value {
return schema.Vec2(mathx.Vec2{X: float32(g.r.Intn(64)) / 4, Y: float32(g.r.Intn(64)) / 4})
}
func (g *gen) plain(name string) *prefab.Node {
n := prefab.NewNode(g.id(), name, false)
n.Comp(prefab.OwnerTransform).Set(prefab.FieldPosition, g.vec())
if g.r.Intn(2) == 0 {
n.Comps = append(n.Comps, prefab.Comp{Owner: "test.A", Kind: prefab.CompComponent, Fields: []prefab.Field{
{Name: "x", Value: g.f32()},
{Name: "r", Value: schema.Node(ids.NodeID{})},
}})
}
if g.r.Intn(3) == 0 {
n.Comps = append(n.Comps, prefab.Comp{Owner: prefab.ScriptOwner("game.S"), Kind: prefab.CompScript, Fields: []prefab.Field{
{Name: prefab.FieldActive, Value: schema.Bool(true)},
{Name: "Speed", Value: g.f32()},
}})
}
return n
}
func (g *gen) prefab(lib library, avail []prefab.PrefabID) *prefab.Node {
root := g.plain("Root")
nodes := []*prefab.Node{root}
for i := 0; i < 1+g.r.Intn(5); i++ {
parent := nodes[g.r.Intn(len(nodes))]
if parent.Instance != nil {
continue
}
var c *prefab.Node
if len(avail) > 0 && g.r.Intn(3) == 0 {
q := avail[g.r.Intn(len(avail))]
c = &prefab.Node{Local: g.id(), Instance: &prefab.Instance{Prefab: q}}
c.Instance.Overrides.Props = []prefab.PropOverride{{
Node: prefab.LocalKey(lib[q].Local),
Prop: prefab.PropKey{Owner: prefab.OwnerTransform, Field: prefab.FieldPosition},
Value: g.vec(),
}}
} else {
c = g.plain("N" + strconv.Itoa(i))
}
parent.Children = append(parent.Children, c)
nodes = append(nodes, c)
}
var locals []ids.NodeID
root.Walk(func(n, _ *prefab.Node) { locals = append(locals, n.Local) })
root.Walk(func(n, _ *prefab.Node) {
if c := n.Comp("test.A"); c != nil {
c.Set("r", schema.Node(locals[g.r.Intn(len(locals))]))
}
})
return root
}
func (g *gen) library() (library, *prefab.Node) {
lib := library{}
var avail []prefab.PrefabID
var top *prefab.Node
for i := 0; i < 3; i++ {
p := g.prefab(lib, avail)
id := ids.AssetID{Hi: g.r.Uint64(), Lo: g.r.Uint64()}
lib[id] = p
avail = append(avail, id)
top = p
}
return lib, top
}
func (g *gen) like(v schema.Value, all []*prefab.Node) schema.Value {
switch v.Kind {
case schema.KindFloat32:
return g.f32()
case schema.KindVec2:
return g.vec()
case schema.KindString:
return schema.String("s" + strconv.Itoa(g.r.Intn(9)))
case schema.KindBool:
return schema.Bool(v.I == 0)
case schema.KindInt64:
return schema.Int64(int64(g.r.Intn(8)))
case schema.KindNodeRef:
return schema.Node(all[g.r.Intn(len(all))].Local)
}
return v
}
func (g *gen) mutate(t *prefab.Node) {
for i := 0; i < 1+g.r.Intn(6); i++ {
var all []*prefab.Node
parents := map[*prefab.Node]*prefab.Node{}
t.Walk(func(n, p *prefab.Node) {
all = append(all, n)
parents[n] = p
})
n := all[g.r.Intn(len(all))]
switch g.r.Intn(6) {
case 0:
c := &n.Comps[g.r.Intn(len(n.Comps))]
f := &c.Fields[g.r.Intn(len(c.Fields))]
f.Value = g.like(f.Value, all)
case 1:
if n.Comp("test.B") == nil {
n.Comps = append(n.Comps, prefab.Comp{Owner: "test.B", Kind: prefab.CompComponent, Fields: []prefab.Field{
{Name: "s", Value: schema.String("b")},
}})
}
case 2:
for j := range n.Comps {
if n.Comps[j].Kind.Removable() {
n.Comps = slices.Delete(n.Comps, j, j+1)
break
}
}
case 3:
c := g.plain("Added")
n.Children = slices.Insert(n.Children, g.r.Intn(len(n.Children)+1), c)
case 4:
if p := parents[n]; p != nil {
p.Children = slices.DeleteFunc(p.Children, func(c *prefab.Node) bool { return c == n })
}
case 5:
g.r.Shuffle(len(n.Children), func(a, b int) { n.Children[a], n.Children[b] = n.Children[b], n.Children[a] })
}
}
}
func TestModelProperties(t *testing.T) {
for seed := int64(1); seed <= 100; seed++ {
g := &gen{r: rand.New(rand.NewSource(seed))}
lib, p := g.library()
base, _, err := prefab.Expand(lib, p, nil)
if err != nil {
t.Fatalf("seed %d: %v", seed, err)
}
mut := base.Clone()
g.mutate(mut)
o := prefab.Diff(mut, base)
got, orph, err := prefab.Expand(lib, p, &o)
if err != nil || !orph.Empty() {
t.Fatalf("seed %d: expand %v, orphans:\n%s", seed, err, prefab.FormatOverrides(orph))
}
if a, b := prefab.Format(got), prefab.Format(mut); a != b {
t.Fatalf("seed %d: Expand(Diff) differs from the edited tree\n%s\n---\n%s\noverrides:\n%s", seed, a, b, prefab.FormatOverrides(o))
}
n, err := Normalize(lib, p, o)
if err != nil || !Same(n, o) {
t.Fatalf("seed %d: Normalize changed a diff\n%s\n---\n%s", seed, prefab.FormatOverrides(n), prefab.FormatOverrides(o))
}
back, _, err := prefab.Expand(lib, p, &n)
if err != nil {
t.Fatal(err)
}
if d := prefab.Diff(back, base); !Same(d, n) {
t.Fatalf("seed %d: prefab.Diff(Expand(Normalize)) != Normalize", seed)
}
r, conf, err := Rebase(lib, n, p, p)
if err != nil || !Same(r, n) || len(conf) != 0 {
t.Fatalf("seed %d: rebase onto the same prefab is not the identity", seed)
}
}
}
func childPrefab() (*prefab.Node, *prefab.Node) {
g := &gen{r: rand.New(rand.NewSource(7))}
root := prefab.NewNode(g.id(), "Root", false)
child := prefab.NewNode(g.id(), "Child", false)
child.Comps = append(child.Comps, prefab.Comp{Owner: "test.A", Kind: prefab.CompComponent, Fields: []prefab.Field{
{Name: "x", Value: schema.Float32(1)},
}})
root.Children = []*prefab.Node{child}
return root, child
}
func TestRemovedTargetBecomesOrphan(t *testing.T) {
root, child := childPrefab()
lib := library{}
o, err := Normalize(lib, root, prefab.Overrides{Props: []prefab.PropOverride{{
Node: prefab.LocalKey(child.Local), Prop: prefab.PropKey{Owner: "test.A", Field: "x"}, Value: schema.Float32(9),
}}})
if err != nil {
t.Fatal(err)
}
next := root.Clone()
next.Children = nil
got, _, err := Rebase(lib, o, root, next)
if err != nil {
t.Fatal(err)
}
orph := got.Orphans()
if len(orph.Props) != 1 || orph.Props[0].Value.F != 9 {
t.Fatalf("orphans:\n%s", prefab.FormatOverrides(got))
}
again, err := Normalize(lib, next, got)
if err != nil || !Same(again, got) {
t.Fatal("normalize must keep orphan overrides")
}
tree, _, err := prefab.Expand(lib, next, &got)
if err != nil || len(tree.Children) != 0 {
t.Fatal("orphans must not be applied")
}
got2, _, err := Rebase(lib, got, next, root)
if err != nil || len(got2.Orphans().Props) != 1 {
t.Fatal("an orphan stays an orphan until it is removed")
}
}
func TestRebaseReportsConflicts(t *testing.T) {
root, child := childPrefab()
lib := library{}
key := prefab.LocalKey(child.Local)
prop := prefab.PropKey{Owner: "test.A", Field: "x"}
o, err := Normalize(lib, root, prefab.Overrides{Props: []prefab.PropOverride{{Node: key, Prop: prop, Value: schema.Float32(9)}}})
if err != nil {
t.Fatal(err)
}
next := root.Clone()
next.Children[0].Comp("test.A").Set("x", schema.Float32(3))
got, conf, err := Rebase(lib, o, root, next)
if err != nil {
t.Fatal(err)
}
if len(conf) != 1 || conf[0].Mine.F != 9 || conf[0].After.F != 3 {
t.Fatalf("conflicts = %+v", conf)
}
tree, _, _ := prefab.Expand(lib, next, &got)
if v, _ := tree.Find(key).Get(prop); v.F != 9 {
t.Fatal("the instance value wins over the changed prefab")
}
next.Children[0].Comp("test.A").Set("x", schema.Float32(9))
got, _, _ = Rebase(lib, got, root, next)
for _, p := range got.Props {
if p.Prop == prop {
t.Fatal("an override equal to the new prefab value must go away")
}
}
}