Files
aego/script/vm/scripts.go
T

187 lines
4.7 KiB
Go

package vm
import (
"fmt"
"strings"
"aego/script/bytecode"
)
type Scripts interface {
Find(node Value, script string) (Value, error)
Instance(ref Value, script string) ([]Value, InstanceID, bool)
}
type Signals interface {
Emit(src InstanceID, sig int, args []Value) error
Connect(src, dst Value, c *Connect) (Value, error)
Disconnect(c Value)
Connected(c Value) bool
}
type Connect struct {
Source string
Signal int
SignalName string
Target string
Handler *Func
}
type xfield struct {
script string
slot int
width int
}
type xmethod struct {
script string
fn *Func
}
type scriptLoc struct {
mod *Module
idx int
def *bytecode.ScriptDef
}
func shortName(full string) string {
return full[strings.LastIndexByte(full, '.')+1:]
}
func (l scriptLoc) method(name string) *Func {
want := shortName(l.def.FullName) + "." + name
for _, f := range l.mod.Funcs {
if f.Script == l.idx && f.Name == want {
return f
}
}
return nil
}
func (l scriptLoc) at(i int32) *Func {
if i < 0 || int(i) >= len(l.mod.Funcs) {
return nil
}
return l.mod.Funcs[i]
}
func (l scriptLoc) lifecycle(f *Func) bool {
for _, i := range [...]int32{l.def.Ready, l.def.FixedUpdate, l.def.Update, l.def.Reloaded} {
if l.at(i) == f {
return true
}
}
for _, h := range l.def.Events {
if l.at(h.Func) == f {
return true
}
}
return false
}
func (l scriptLoc) callable(f *Func) bool {
return f != nil && !f.Coroutine && !l.lifecycle(f)
}
func (mod *Module) ScriptMethod(script int, name string) *Func {
if script < 0 || script >= len(mod.src.Scripts) {
return nil
}
return scriptLoc{mod: mod, idx: script, def: &mod.src.Scripts[script]}.method(name)
}
func bindScripts(mods []*Module) error {
index := map[string]scriptLoc{}
for _, mod := range mods {
for i := range mod.src.Scripts {
d := &mod.src.Scripts[i]
index[d.FullName] = scriptLoc{mod: mod, idx: i, def: d}
}
}
for _, mod := range mods {
src := mod.src
target := func(name string) (scriptLoc, error) {
l, ok := index[name]
if !ok {
return l, fmt.Errorf("%s refers to script %s, which is not loaded", mod.Package, name)
}
return l, nil
}
mod.xfields = make([]xfield, len(src.XFields))
for i, x := range src.XFields {
l, err := target(src.ScriptRefs[x.Ref])
if err != nil {
return err
}
fd := l.def.Field(x.Name)
if fd == nil || fd.Type != x.Type || fd.Slots != x.Slots {
return fmt.Errorf("%s uses field %s.%s with a stale layout", mod.Package, l.def.FullName, x.Name)
}
mod.xfields[i] = xfield{script: l.def.FullName, slot: int(fd.Slot), width: int(fd.Slots)}
}
mod.xmethods = make([]xmethod, len(src.XMethods))
for i, x := range src.XMethods {
l, err := target(src.ScriptRefs[x.Ref])
if err != nil {
return err
}
f := l.method(x.Name)
if !l.callable(f) || f.Params != int(x.Params) || f.Results != int(x.Results) {
return fmt.Errorf("%s calls %s.%s through a ScriptRef, but it is missing, a lifecycle method, a coroutine or has another signature", mod.Package, l.def.FullName, x.Name)
}
mod.xmethods[i] = xmethod{script: l.def.FullName, fn: f}
}
mod.connects = make([]Connect, len(src.Connects))
for i, c := range src.Connects {
sl, err := target(c.Source)
if err != nil {
return err
}
tl, err := target(c.Target)
if err != nil {
return err
}
sig := sl.def.Signal(c.Signal)
if sig < 0 {
return fmt.Errorf("%s connects to signal %s.%s, which does not exist", mod.Package, c.Source, c.Signal)
}
h := tl.method(c.Handler)
if !tl.callable(h) || h.Results != 0 || h.Params != int(sl.def.Signals[sig].Slots) {
return fmt.Errorf("%s connects %s.%s to %s.%s, which cannot handle it", mod.Package, c.Source, c.Signal, c.Target, c.Handler)
}
mod.connects[i] = Connect{Source: c.Source, Signal: sig, SignalName: c.Signal, Target: c.Target, Handler: h}
}
}
return nil
}
func (m *Machine) Invoke(f *Func, self []Value, args []Value, owner InstanceID) *Fault {
if f == nil || f.Coroutine || len(args) != f.Params {
return &Fault{Code: CodeLink, Message: "handler is missing, is a coroutine or its argument count does not match"}
}
if len(m.frames) == 0 {
_, fl := m.CallAs(f, self, args, owner)
return fl
}
if len(m.frames) >= m.opt.MaxCallDepth {
return &Fault{Code: CodeCallDepth, Message: "call depth exceeds " + fmt.Sprint(m.opt.MaxCallDepth)}
}
last := &m.frames[len(m.frames)-1]
nb := last.base + last.f.Regs
if need := nb + f.Regs; need > len(m.stack) {
m.grow(need)
}
copy(m.stack[nb:], args)
clear(m.stack[nb+len(args) : nb+f.Regs])
floor := len(m.frames)
m.frames = append(m.frames, frame{f: f, base: nb, ret: nb, self: self, owner: owner})
m.stats.Calls++
m.exec(floor)
fl := m.fault
if fl != nil {
m.fault = nil
m.unwind(floor)
}
return fl
}