Files
aego/script/runtime/signals.go
T

271 lines
6.8 KiB
Go

package runtime
import (
"errors"
"fmt"
rtdebug "runtime/debug"
"aego/contracts/signals"
"aego/core/ids"
"aego/core/log"
"aego/engine"
"aego/engine/recorder"
"aego/scene"
"aego/schema"
"aego/script/abi"
"aego/script/bind"
"aego/script/signal"
"aego/script/vm"
)
var (
faultSignalSource = &vm.Fault{Code: vm.CodeNil, Message: "signal source is nil or gone"}
errNoSignal = errors.New("signal is not part of the linked bindings")
)
type hostHandle struct {
name string
sigs []abi.SignalDef
}
func (r *Runtime) linkHandles() {
tab := r.opt.Bindings.Table()
r.handles = map[uint64]hostHandle{}
for _, h := range tab.HandleList() {
r.handles[abi.HandleID(h.Name)] = hostHandle{name: h.Name, sigs: tab.HandleSignals(h.Name)}
}
}
func (r *Runtime) paramSlots(ps []abi.TypeRef) int {
tab := r.opt.Bindings.Table()
n := 0
for _, p := range ps {
n += tab.Slots(p)
}
return n
}
func (r *Runtime) callGo(c signal.Conn, args []vm.Value) (ok bool) {
defer func() {
v := recover()
if v == nil {
return
}
ok = false
r.log.Error("signal handler panicked and is disconnected", log.F("signal", c.Signal), log.F("panic", fmt.Sprint(v)))
if r.opt.OnPanic != nil {
r.opt.OnPanic("signal "+c.Signal, v, rtdebug.Stack())
}
}()
c.Go(args)
return true
}
func connectIntrinsic(kind uint8) vm.HostFunc {
return func(m *vm.Machine, w []vm.Value) error {
b, ok := m.Signals.(bridge)
if !ok {
return errNoScene
}
return b.r.connectScript(kind, w)
}
}
func (r *Runtime) hostSource(kind uint8, v vm.Value, sig int) (signal.Source, abi.SignalDef, error) {
if v.IsZero() {
return signal.Source{}, abi.SignalDef{}, faultSignalSource
}
switch kind {
case signal.KindComponent:
idx := int(v.B)
if idx >= len(r.comps) {
return signal.Source{}, abi.SignalDef{}, errBadComponent
}
c := &r.comps[idx]
if sig < 0 || sig >= len(c.sigs) {
return signal.Source{}, abi.SignalDef{}, errNoSignal
}
h := bind.NodeOf(v)
if r.scene == nil || !r.scene.IsAlive(h) {
return signal.Source{}, abi.SignalDef{}, faultNodeGone
}
if _, ok := r.scene.GetByType(h, c.typ); !ok {
return signal.Source{}, abi.SignalDef{}, faultComponentGone
}
return signal.Source{Kind: signal.KindComponent, A: v.A, B: uint64(c.typ)}, c.sigs[sig], nil
case signal.KindHandle:
hh, ok := r.handles[v.B]
if !ok || sig < 0 || sig >= len(hh.sigs) {
return signal.Source{}, abi.SignalDef{}, errNoSignal
}
return signal.Source{Kind: signal.KindHandle, A: v.A, B: v.B}, hh.sigs[sig], nil
}
return signal.Source{}, abi.SignalDef{}, errNoSignal
}
func (r *Runtime) handleAlive(src signal.Source) bool {
if r.ctx.World == nil {
return true
}
hub, ok := engine.ServiceOf[signals.Hub](r.ctx.World, signals.ServiceID)
return !ok || hub.HandleAlive(src.B, src.A)
}
func (r *Runtime) connectScript(kind uint8, w []vm.Value) error {
sig := int(w[2].Int())
src, def, err := r.hostSource(kind, w[0], sig)
if err != nil {
return err
}
if src.Kind == signal.KindHandle && !r.handleAlive(src) {
w[0] = vm.Value{}
return nil
}
id := vm.InstanceID(w[1].B)
in := r.live(id)
if in == nil || bind.NodeValue(in.node).A != w[1].A {
return errTargetGone
}
name, _ := r.m.Str(w[3])
mi := in.typ.methodIndex(name)
if mi < 0 {
return errHandler
}
h := r.sig.Connect(signal.Conn{
Src: src, Sig: sig, Dst: id, Method: mi,
Signal: def.Name, Handler: name, Sign: abi.Signature{Params: def.Params},
})
w[0] = signal.Value(h)
return nil
}
func (r *Runtime) signalDef(src signal.Source, sig int) (abi.SignalDef, bool) {
switch src.Kind {
case signal.KindComponent:
i, ok := r.compByType[schema.TypeID(src.B)]
if !ok || sig < 0 || sig >= len(r.comps[i].sigs) {
return abi.SignalDef{}, false
}
h := bind.NodeOf(vm.Value{A: src.A})
if r.scene == nil || !r.scene.IsAlive(h) {
return abi.SignalDef{}, false
}
if _, has := r.scene.GetByType(h, r.comps[i].typ); !has {
return abi.SignalDef{}, false
}
return r.comps[i].sigs[sig], true
case signal.KindHandle:
hh, ok := r.handles[src.B]
if !ok || sig < 0 || sig >= len(hh.sigs) {
return abi.SignalDef{}, false
}
return hh.sigs[sig], true
}
return abi.SignalDef{}, false
}
func (r *Runtime) WantsSignal(src signal.Source) bool {
return r.sig != nil && r.sig.Has(src)
}
func (r *Runtime) SignalArgs(n int) []vm.Value {
if need := r.argTop + n; need > len(r.argStack) {
grown := make([]vm.Value, max(need, 2*len(r.argStack), 64))
copy(grown, r.argStack[:r.argTop])
r.argStack = grown
}
a := r.argStack[r.argTop : r.argTop+n]
clear(a)
r.argTop += n
return a
}
func (r *Runtime) EmitHost(src signal.Source, sig int, args []vm.Value) {
if r.sig != nil && r.m != nil {
r.emits++
r.recordHost(src, sig)
if err := r.sig.Emit(src, sig, args, r.deliverFn); err != nil {
r.log.Error("signal re-emits itself without end, the emit is dropped", log.F("err", err.Error()))
}
}
r.argTop = max(r.argTop-len(args), 0)
}
func (r *Runtime) EndSource(src signal.Source) {
if r.sig != nil {
r.sig.DropSource(src)
}
}
func (r *Runtime) ConnectHost(src signal.Source, sig int, fn signal.Handler) vm.Value {
if r.sig == nil || fn == nil {
return vm.Value{}
}
def, ok := r.signalDef(src, sig)
if !ok {
return vm.Value{}
}
h := r.sig.Connect(signal.Conn{Src: src, Sig: sig, Go: fn, Signal: def.Name, Handler: "go", Sign: abi.Signature{Params: def.Params}})
return signal.Value(h)
}
func (r *Runtime) ConnectScriptHost(node scene.NodeHandle, script, name string, fn signal.Handler) vm.Value {
if r.sig == nil || fn == nil {
return vm.Value{}
}
id, ok := r.InstanceOf(node, script)
in := r.live(id)
if !ok || in == nil {
return vm.Value{}
}
def := in.typ.def
si := def.Signal(name)
if si < 0 {
return vm.Value{}
}
h := r.sig.Connect(signal.Conn{
Src: signal.ScriptSource(id), Sig: si, Go: fn, Signal: name, Handler: "go",
Sign: abi.Signature{Params: def.Signals[si].Params},
})
return signal.Value(h)
}
func (r *Runtime) DisconnectSignal(c vm.Value) {
if r.sig != nil {
r.sig.Disconnect(signal.Of(c))
}
}
func (r *Runtime) SignalConnected(c vm.Value) bool {
return r.sig != nil && r.sig.Connected(signal.Of(c))
}
func (r *Runtime) SetRecorder(rec *recorder.Recorder) {
r.rec = rec
}
func (r *Runtime) recordHost(src signal.Source, sig int) {
rec := r.rec
if rec == nil || r.scene == nil {
return
}
switch src.Kind {
case signal.KindComponent:
i, ok := r.compByType[schema.TypeID(src.B)]
if !ok || sig >= len(r.comps[i].sigs) {
return
}
var id ids.NodeID
if h := bind.NodeOf(vm.Value{A: src.A}); r.scene.IsAlive(h) {
id = r.scene.ID(h)
}
rec.Add(recorder.Signal, id, ids.NodeID{}, r.comps[i].sigs[sig].Name, shortName(r.comps[i].name))
case signal.KindHandle:
hh, ok := r.handles[src.B]
if !ok || sig >= len(hh.sigs) {
return
}
rec.Add(recorder.Signal, ids.NodeID{}, ids.NodeID{}, hh.sigs[sig].Name, shortName(hh.name))
}
}