204 lines
5.2 KiB
Go
204 lines
5.2 KiB
Go
package scripting_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"aego/contracts/scriptevents"
|
|
"aego/engine"
|
|
"aego/engine/base"
|
|
"aego/engine/module"
|
|
"aego/modules/scripting"
|
|
scriptsystems "aego/modules/scripting/systems"
|
|
"aego/scene"
|
|
"aego/scene/scriptcomp"
|
|
"aego/script/abi"
|
|
"aego/script/ast"
|
|
"aego/script/bind"
|
|
"aego/script/bytecode"
|
|
"aego/script/checker"
|
|
"aego/script/compiler"
|
|
"aego/script/diag"
|
|
"aego/script/parser"
|
|
"aego/script/runtime"
|
|
"aego/script/source"
|
|
"aego/script/stdlib"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
type evModule struct {
|
|
id string
|
|
name string
|
|
out *scriptevents.EventID
|
|
}
|
|
|
|
func (m evModule) Info() module.Info {
|
|
return module.Info{ID: m.id, Version: "1"}
|
|
}
|
|
|
|
func (m evModule) Register(r *module.Registry) error {
|
|
id := r.ScriptEvent(scriptevents.Event{Name: m.name, Params: []abi.TypeRef{abi.T(abi.Int)}, Names: []string{"n"}})
|
|
if m.out != nil {
|
|
*m.out = id
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type rig struct {
|
|
types *scene.Types
|
|
bind *bind.Registry
|
|
ev scriptevents.EventID
|
|
}
|
|
|
|
func newRig(t *testing.T) *rig {
|
|
types, err := base.NewTypes()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := &rig{types: types, bind: bind.New()}
|
|
if _, err := module.Load(types, r.bind, base.Core{}, scripting.New(stdlib.Options{Seed: 1}), evModule{id: "evtest", name: "OnHit", out: &r.ev}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := types.Schema().Freeze(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := scriptsystems.Freeze(r.bind, types.Schema()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *rig) compile(src string) (*bytecode.Module, []diag.Diagnostic) {
|
|
sm := source.NewManager()
|
|
id := sm.Add("scripts/evt/main.aes", []byte(src))
|
|
f, ds := parser.ParseFile(sm, id)
|
|
a := checker.Check(sm, map[string][]*ast.File{"evt": {f}}, r.bind.Surface(), checker.Options{Module: "evt"})
|
|
all := append(ds, a.Diagnostics...)
|
|
if diag.HasErrors(all) {
|
|
return nil, all
|
|
}
|
|
mod, cds := compiler.Compile(a, "evt", compiler.Options{Sources: sm})
|
|
return mod, append(all, cds...)
|
|
}
|
|
|
|
func hasCode(ds []diag.Diagnostic, code string) bool {
|
|
for _, d := range ds {
|
|
if d.Code == code {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
const targetSrc = `package evt
|
|
|
|
script Target {
|
|
var hits int
|
|
var order int
|
|
|
|
func OnHit(n int) {
|
|
hits++
|
|
order = order*10 + n
|
|
}
|
|
}
|
|
|
|
script Plain {
|
|
var x int
|
|
}
|
|
`
|
|
|
|
func TestEventHandlerIsLifecycle(t *testing.T) {
|
|
r := newRig(t)
|
|
mod, ds := r.compile(targetSrc)
|
|
if mod == nil {
|
|
t.Fatalf("compile: %v", ds)
|
|
}
|
|
if ev := mod.Scripts[0].Events; len(ev) != 1 || ev[0].Name != "OnHit" {
|
|
t.Fatalf("events %+v", ev)
|
|
}
|
|
cases := map[string]string{
|
|
diag.EventSignature: "package evt\nscript S {\n\tfunc OnHit(n float) {}\n}\n",
|
|
diag.EventCall: "package evt\nscript S {\n\tfunc OnHit(n int) {}\n\tfunc Update(dt float) {\n\t\tOnHit(1)\n\t}\n}\n",
|
|
diag.CoroutineLifecycle: "package evt\nscript S {\n\tfunc OnHit(n int) coroutine {\n\t\tyield\n\t}\n}\n",
|
|
}
|
|
for code, src := range cases {
|
|
if _, ds := r.compile(src); !hasCode(ds, code) {
|
|
t.Fatalf("%s not reported: %v", code, ds)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventRegistrationErrors(t *testing.T) {
|
|
for _, name := range []string{"Update", "onHit", ""} {
|
|
types, _ := base.NewTypes()
|
|
if _, err := module.Load(types, bind.New(), base.Core{}, scripting.New(stdlib.Options{}), evModule{id: "evtest", name: name}); err == nil {
|
|
t.Fatalf("event %q accepted", name)
|
|
}
|
|
}
|
|
types, _ := base.NewTypes()
|
|
if _, err := module.Load(types, bind.New(), base.Core{}, scripting.New(stdlib.Options{}), evModule{id: "a", name: "OnHit"}, evModule{id: "b", name: "OnHit"}); err == nil {
|
|
t.Fatal("duplicate event accepted")
|
|
}
|
|
}
|
|
|
|
func attach(s *scene.Scene, h scene.NodeHandle, typ string) {
|
|
sc := scene.Add[scriptcomp.ScriptComponent](s, h)
|
|
sc.Attachments = append(sc.Attachments, scriptcomp.Attachment{TypeName: typ, Enabled: true})
|
|
}
|
|
|
|
func TestEventDispatch(t *testing.T) {
|
|
r := newRig(t)
|
|
mod, ds := r.compile(targetSrc)
|
|
if mod == nil {
|
|
t.Fatalf("compile: %v", ds)
|
|
}
|
|
rt := runtime.New(runtime.Options{Bindings: r.bind, Schema: r.types.Schema()})
|
|
if err := rt.Link([]*bytecode.Module{mod}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := scene.New(r.types, nil, scene.Options{})
|
|
target := s.Create(s.Root(), "Target")
|
|
plain := s.Create(s.Root(), "Plain")
|
|
attach(s, target, "evt.Target")
|
|
attach(s, plain, "evt.Plain")
|
|
s.Flush()
|
|
if err := rt.Instantiate(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ec := &engine.Context{Frame: 1, Dt: 1.0 / 60}
|
|
rt.DispatchEvents(ec)
|
|
id, ok := rt.InstanceOf(target, "evt.Target")
|
|
if !ok {
|
|
t.Fatal("no Target instance")
|
|
}
|
|
field := func(name string) int64 {
|
|
v, _ := rt.Field(id, name)
|
|
return v.Int()
|
|
}
|
|
for _, n := range []int64{1, 2, 3} {
|
|
rt.Emit(target, r.ev, []vm.Value{vm.Int(n)})
|
|
}
|
|
if field("hits") != 0 {
|
|
t.Fatal("Emit called the handler before Flush")
|
|
}
|
|
rt.DispatchEvents(ec)
|
|
if field("hits") != 3 || field("order") != 123 {
|
|
t.Fatalf("hits %d order %d", field("hits"), field("order"))
|
|
}
|
|
calls := rt.EventCalls()
|
|
rt.Emit(plain, r.ev, []vm.Value{vm.Int(9)})
|
|
rt.Flush()
|
|
if rt.EventCalls() != calls {
|
|
t.Fatal("an instance without a handler was visited")
|
|
}
|
|
args := []vm.Value{vm.Int(1)}
|
|
allocs := testing.AllocsPerRun(10, func() {
|
|
for i := 0; i < 1000; i++ {
|
|
rt.Emit(target, r.ev, args)
|
|
}
|
|
rt.Flush()
|
|
})
|
|
if allocs != 0 {
|
|
t.Fatalf("1000 events allocate %v times", allocs)
|
|
}
|
|
}
|