196 lines
5.3 KiB
Go
196 lines
5.3 KiB
Go
package runtime
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"aego/engine"
|
|
scriptstdlib "aego/modules/scripting/stdlib"
|
|
"aego/platform/input"
|
|
"aego/scene"
|
|
"aego/scene/frame"
|
|
"aego/scene/scriptcomp"
|
|
"aego/schema"
|
|
"aego/script/ast"
|
|
"aego/script/bind"
|
|
"aego/script/bytecode"
|
|
"aego/script/checker"
|
|
"aego/script/compiler"
|
|
"aego/script/diag"
|
|
"aego/script/parser"
|
|
"aego/script/source"
|
|
"aego/script/stdlib"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
const program = `package game
|
|
|
|
script Mover {
|
|
@export var Speed float = 2
|
|
var readies int
|
|
|
|
func Ready() {
|
|
readies++
|
|
}
|
|
|
|
func Update(dt float) {
|
|
p := node.Position()
|
|
p.X += Speed * dt
|
|
node.SetPosition(p)
|
|
if node.Has<Marker>() {
|
|
m := node.Get<Marker>()
|
|
m.FlipX = readies == 1
|
|
}
|
|
}
|
|
}
|
|
|
|
script Spinner {
|
|
func Update(dt float) {
|
|
for {
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
func setup(t *testing.T) (*scene.Types, *bind.Registry, *bytecode.Module) {
|
|
t.Helper()
|
|
types := scene.NewTypes(schema.NewRegistry())
|
|
if err := frame.RegisterTypes(types); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registerMarker(types); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := scriptcomp.Register(types); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := types.Schema().Freeze(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reg := bind.New()
|
|
scriptstdlib.Install(reg, stdlib.Options{Seed: 1})
|
|
BindComponents(reg, types.Schema())
|
|
if err := reg.Freeze(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sm := source.NewManager()
|
|
f, ds := parser.ParseSource(sm, "game/main.aes", []byte(program))
|
|
a := checker.Check(sm, map[string][]*ast.File{"game": {f}}, reg.Surface(), checker.Options{Module: "game"})
|
|
ds = append(ds, a.Diagnostics...)
|
|
mod, cds := compiler.Compile(a, "game", compiler.Options{Sources: sm})
|
|
ds = append(ds, cds...)
|
|
if mod == nil || diag.HasErrors(ds) {
|
|
for _, d := range ds {
|
|
t.Log(diag.Format(sm, d))
|
|
}
|
|
t.Fatal("program does not compile")
|
|
}
|
|
return types, reg, mod
|
|
}
|
|
|
|
func attach(s *scene.Scene, name, typ string, props schema.PropertyBag) scene.NodeHandle {
|
|
h := s.Create(s.Root(), name)
|
|
sc := scene.Add[scriptcomp.ScriptComponent](s, h)
|
|
sc.Attachments = append(sc.Attachments, scriptcomp.Attachment{TypeName: typ, Enabled: true, Props: props})
|
|
return h
|
|
}
|
|
|
|
func TestRuntimeEndToEnd(t *testing.T) {
|
|
types, reg, mod := setup(t)
|
|
rt := New(Options{Bindings: reg, Schema: types.Schema(), VM: vm.Options{Budget: 10_000}})
|
|
if err := rt.Link([]*bytecode.Module{mod}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := types.Schema().ByName("game.Mover"); !ok {
|
|
t.Fatal("Link did not publish script types")
|
|
}
|
|
s := scene.New(types, nil, scene.Options{})
|
|
var props schema.PropertyBag
|
|
props.Set("Speed", schema.Float32(4))
|
|
mover := attach(s, "Mover", "game.Mover", props)
|
|
*scene.Add[Marker](s, mover) = Marker{}
|
|
attach(s, "Spinner", "game.Spinner", schema.PropertyBag{})
|
|
s.Flush()
|
|
if err := rt.Instantiate(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
w := engine.NewWorld("test")
|
|
w.Input = &input.State{}
|
|
for frame := uint64(1); frame <= 2; frame++ {
|
|
rt.Update(&engine.Context{World: w, Dt: 0.5, Frame: frame})
|
|
}
|
|
|
|
if got := s.Local(mover).Position.X; got != 4 {
|
|
t.Fatalf("mover x = %v, want 4 after two 0.5s frames at speed 4", got)
|
|
}
|
|
if m, _ := scene.Get[Marker](s, mover); !m.FlipX {
|
|
t.Fatal("script did not write the Go component through node.Get<Marker>(), or Ready ran more than once")
|
|
}
|
|
if n := len(rt.Faults()); n != 1 || rt.Faults()[0].Fault.Code != vm.CodeBudget {
|
|
t.Fatalf("faults = %+v, want one budget fault from Spinner", rt.Faults())
|
|
}
|
|
}
|
|
|
|
func TestRuntimeDestroyRemovesInstance(t *testing.T) {
|
|
types, reg, mod := setup(t)
|
|
rt := New(Options{Bindings: reg, Schema: types.Schema(), VM: vm.Options{Budget: 10_000}})
|
|
if err := rt.Link([]*bytecode.Module{mod}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := scene.New(types, nil, scene.Options{})
|
|
h := attach(s, "Mover", "game.Mover", schema.PropertyBag{})
|
|
s.Flush()
|
|
if err := rt.Instantiate(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w := engine.NewWorld("test")
|
|
w.Input = &input.State{}
|
|
rt.Update(&engine.Context{World: w, Dt: 0.1, Frame: 1})
|
|
s.Destroy(h)
|
|
s.Flush()
|
|
rt.Update(&engine.Context{World: w, Dt: 0.1, Frame: 2})
|
|
rt.Update(&engine.Context{World: w, Dt: 0.1, Frame: 3})
|
|
if st := rt.Stats(); st.Instances != 0 || st.Update != 0 {
|
|
t.Fatalf("destroyed node still has %d instances and %d scheduled updates", st.Instances, st.Update)
|
|
}
|
|
}
|
|
|
|
func TestComponentRefOnDestroyedNode(t *testing.T) {
|
|
types, reg, mod := setup(t)
|
|
rt := New(Options{Bindings: reg, Schema: types.Schema()})
|
|
if err := rt.Link([]*bytecode.Module{mod}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := scene.New(types, nil, scene.Options{})
|
|
h := s.Create(s.Root(), "N")
|
|
*scene.Add[Marker](s, h) = Marker{}
|
|
s.Flush()
|
|
if err := rt.Instantiate(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ref := vm.Value{A: bind.NodeValue(h).A, B: 0}
|
|
var v vm.Value
|
|
if err := rt.GetField(ref, 0, &v); err != nil {
|
|
t.Fatalf("live component: %v", err)
|
|
}
|
|
s.Destroy(h)
|
|
err := rt.GetField(ref, 0, &v)
|
|
var fl *vm.Fault
|
|
if !errors.As(err, &fl) || fl.Code != vm.CodeNil {
|
|
t.Fatalf("destroyed node: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestComponentFieldLimit(t *testing.T) {
|
|
td := &schema.TypeDescriptor{FullName: "game.Big", Owner: "game"}
|
|
if err := fieldLimit(td, 255); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err := fieldLimit(td, 256)
|
|
if err == nil || !strings.Contains(err.Error(), "game.Big") || !strings.Contains(err.Error(), "module game") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|