259 lines
5.6 KiB
Go
259 lines
5.6 KiB
Go
package runtime
|
|
|
|
import (
|
|
"errors"
|
|
"slices"
|
|
"strings"
|
|
|
|
"aego/core/ids"
|
|
"aego/engine"
|
|
"aego/engine/recorder"
|
|
"aego/script/abi"
|
|
"aego/script/bind"
|
|
"aego/script/coroutine"
|
|
"aego/script/signal"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
var (
|
|
errNodeGone = errors.New("node reference is nil or destroyed")
|
|
errSourceGone = errors.New("signal source is nil or its instance is gone")
|
|
errTargetGone = errors.New("signal handler owner is nil or its instance is gone")
|
|
errHandler = errors.New("signal handler is not a method of the target script")
|
|
)
|
|
|
|
func shortName(full string) string {
|
|
return full[strings.LastIndexByte(full, '.')+1:]
|
|
}
|
|
|
|
func (t *scriptType) methodIndex(name string) int {
|
|
want := t.prefix + name
|
|
for i, f := range t.methods {
|
|
if f.Name == want {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (t *scriptType) lifecycle(f *vm.Func) bool {
|
|
if f == t.ready || f == t.fixed || f == t.update || f == t.reloaded {
|
|
return true
|
|
}
|
|
for _, e := range t.events {
|
|
if e != nil && e == f {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (r *Runtime) live(id vm.InstanceID) *instance {
|
|
if id == vm.NoInstance || int(id) > len(r.inst) {
|
|
return nil
|
|
}
|
|
in := &r.inst[id-1]
|
|
if in.state == dead || in.base < 0 {
|
|
return nil
|
|
}
|
|
return in
|
|
}
|
|
|
|
func (r *Runtime) instance(ref vm.Value, script string) (*instance, vm.InstanceID) {
|
|
id := vm.InstanceID(ref.B)
|
|
in := r.live(id)
|
|
if in == nil || in.typ.name != script || bind.NodeValue(in.node).A != ref.A {
|
|
return nil, vm.NoInstance
|
|
}
|
|
return in, id
|
|
}
|
|
|
|
func (r *Runtime) selfOf(id vm.InstanceID) ([]vm.Value, bool) {
|
|
in := r.live(id)
|
|
if in == nil || in.state != Running {
|
|
return nil, false
|
|
}
|
|
r.ctx.Node, r.ctx.Self = in.node, id
|
|
t := in.typ
|
|
return t.vals[in.base : in.base+t.slots], true
|
|
}
|
|
|
|
func (r *Runtime) deliver(c signal.Conn, args []vm.Value) (bool, error) {
|
|
if c.Go != nil {
|
|
return r.callGo(c, args), nil
|
|
}
|
|
in := r.live(c.Dst)
|
|
if in == nil {
|
|
return false, nil
|
|
}
|
|
if in.state != Running {
|
|
return true, nil
|
|
}
|
|
t := in.typ
|
|
if c.Method < 0 || c.Method >= len(t.methods) {
|
|
return false, nil
|
|
}
|
|
r.handlers++
|
|
node, self := r.ctx.Node, r.ctx.Self
|
|
r.ctx.Node, r.ctx.Self = in.node, c.Dst
|
|
fl := r.m.Invoke(t.methods[c.Method], t.vals[in.base:in.base+t.slots], args, c.Dst)
|
|
r.ctx.Node, r.ctx.Self = node, self
|
|
if fl == nil {
|
|
return true, nil
|
|
}
|
|
if fl.Code == vm.CodeCallDepth {
|
|
return true, fl
|
|
}
|
|
r.fault(c.Dst, fl)
|
|
return true, nil
|
|
}
|
|
|
|
func (r *Runtime) Coroutines(ec *engine.Context) {
|
|
if !r.begin(ec) {
|
|
return
|
|
}
|
|
r.ctx.Dt = float32(ec.Dt)
|
|
r.co.Frame(float32(ec.Dt))
|
|
r.end()
|
|
}
|
|
|
|
func (r *Runtime) CoStats() coroutine.Stats {
|
|
if r.co == nil {
|
|
return coroutine.Stats{}
|
|
}
|
|
return r.co.Stats()
|
|
}
|
|
|
|
func (r *Runtime) CancelChanged(changed func(name string) bool) int {
|
|
if r.co == nil {
|
|
return 0
|
|
}
|
|
return r.co.CancelTypes(func(id vm.InstanceID) bool {
|
|
in := r.live(id)
|
|
return in == nil || changed(in.typ.name)
|
|
})
|
|
}
|
|
|
|
func (r *Runtime) RebindSignals() int {
|
|
if r.sig == nil {
|
|
return 0
|
|
}
|
|
return r.sig.Rebind(r.rebind)
|
|
}
|
|
|
|
func (r *Runtime) rebind(c *signal.Conn) bool {
|
|
slots := 0
|
|
if c.Src.Kind == signal.KindScript {
|
|
src := r.live(c.Src.Instance())
|
|
if src == nil {
|
|
return false
|
|
}
|
|
def := src.typ.def
|
|
si := def.Signal(c.Signal)
|
|
if si < 0 || !(abi.Signature{Params: def.Signals[si].Params}).Equal(c.Sign) {
|
|
return false
|
|
}
|
|
c.Sig, slots = si, int(def.Signals[si].Slots)
|
|
} else {
|
|
slots = r.paramSlots(c.Sign.Params)
|
|
}
|
|
if c.Go != nil {
|
|
return true
|
|
}
|
|
dst := r.live(c.Dst)
|
|
if dst == nil {
|
|
return false
|
|
}
|
|
mi := dst.typ.methodIndex(c.Handler)
|
|
if mi < 0 {
|
|
return false
|
|
}
|
|
f := dst.typ.methods[mi]
|
|
if f.Coroutine || dst.typ.lifecycle(f) || f.Results != 0 || f.Params != slots {
|
|
return false
|
|
}
|
|
c.Method = mi
|
|
return true
|
|
}
|
|
|
|
type bridge struct {
|
|
r *Runtime
|
|
}
|
|
|
|
func (b bridge) Find(node vm.Value, script string) (vm.Value, error) {
|
|
r := b.r
|
|
h := bind.NodeOf(node)
|
|
if r.scene == nil || !r.scene.IsAlive(h) {
|
|
return vm.Value{}, errNodeGone
|
|
}
|
|
id, ok := r.InstanceOf(h, script)
|
|
if !ok {
|
|
return vm.Value{}, nil
|
|
}
|
|
return vm.Value{A: node.A, B: uint64(id)}, nil
|
|
}
|
|
|
|
func (b bridge) Instance(ref vm.Value, script string) ([]vm.Value, vm.InstanceID, bool) {
|
|
in, id := b.r.instance(ref, script)
|
|
if in == nil {
|
|
return nil, vm.NoInstance, false
|
|
}
|
|
t := in.typ
|
|
return t.vals[in.base : in.base+t.slots], id, true
|
|
}
|
|
|
|
func (b bridge) Emit(src vm.InstanceID, sig int, args []vm.Value) error {
|
|
r := b.r
|
|
r.emits++
|
|
if in := r.live(src); in != nil && r.rec != nil && r.scene != nil {
|
|
r.rec.Add(recorder.Signal, r.scene.ID(in.node), ids.NodeID{}, in.typ.def.Signals[sig].Name, shortName(in.typ.name))
|
|
}
|
|
return r.sig.Emit(signal.ScriptSource(src), sig, args, r.deliverFn)
|
|
}
|
|
|
|
func (b bridge) Connect(src, dst vm.Value, c *vm.Connect) (vm.Value, error) {
|
|
r := b.r
|
|
sin, sid := r.instance(src, c.Source)
|
|
if sin == nil {
|
|
return vm.Value{}, errSourceGone
|
|
}
|
|
din, did := r.instance(dst, c.Target)
|
|
if din == nil {
|
|
return vm.Value{}, errTargetGone
|
|
}
|
|
mi := slices.Index(din.typ.methods, c.Handler)
|
|
if mi < 0 {
|
|
return vm.Value{}, errHandler
|
|
}
|
|
h := r.sig.Connect(signal.Conn{
|
|
Src: signal.ScriptSource(sid),
|
|
Sig: c.Signal,
|
|
Dst: did,
|
|
Method: mi,
|
|
Signal: c.SignalName,
|
|
Handler: shortName(c.Handler.Name),
|
|
Sign: abi.Signature{Params: sin.typ.def.Signals[c.Signal].Params},
|
|
})
|
|
return signal.Value(h), nil
|
|
}
|
|
|
|
func (b bridge) Disconnect(c vm.Value) {
|
|
b.r.sig.Disconnect(signal.Of(c))
|
|
}
|
|
|
|
func (b bridge) Connected(c vm.Value) bool {
|
|
return b.r.sig.Connected(signal.Of(c))
|
|
}
|
|
|
|
func (r *Runtime) CoroutinesUnscaled(ec *engine.Context) {
|
|
if !r.begin(ec) {
|
|
return
|
|
}
|
|
real := ec.Unscaled
|
|
if ec.Time == nil {
|
|
real = ec.Dt
|
|
}
|
|
r.co.Unscaled(float32(real))
|
|
r.end()
|
|
}
|