2019-04-16 06:07:15 +00:00
|
|
|
package scripting
|
|
|
|
|
|
|
|
import (
|
2022-01-17 04:09:27 +00:00
|
|
|
"errors"
|
2019-04-16 06:07:15 +00:00
|
|
|
"fmt"
|
2020-01-03 01:58:22 +00:00
|
|
|
"sync"
|
2019-04-16 06:07:15 +00:00
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
2022-01-17 04:09:27 +00:00
|
|
|
"github.com/dop251/goja"
|
2019-04-16 06:07:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// VM manages a single isolated JavaScript VM.
|
|
|
|
type VM struct {
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// Globals available to the scripts.
|
|
|
|
Events *Events
|
|
|
|
Self interface{}
|
|
|
|
|
2019-06-24 00:30:12 +00:00
|
|
|
// Channels for inbound and outbound PubSub messages.
|
|
|
|
// Each VM has a single Inbound channel that watches for received messages
|
|
|
|
// and invokes the Message.Subscribe() handlers for relevant ones.
|
|
|
|
// Each VM also has an array of Outbound channels which map to the Inbound
|
|
|
|
// channel of the VMs it is linked to, for pushing out Message.Publish()
|
|
|
|
// messages.
|
2020-01-03 01:58:22 +00:00
|
|
|
Inbound chan Message
|
|
|
|
Outbound []chan Message
|
2022-04-10 19:39:27 +00:00
|
|
|
stop chan bool
|
2022-01-17 04:09:27 +00:00
|
|
|
subscribe map[string][]goja.Value // Subscribed message handlers by name.
|
2020-01-03 01:58:22 +00:00
|
|
|
muSubscribe sync.RWMutex
|
2022-05-01 22:18:23 +00:00
|
|
|
muPublish sync.Mutex // serialize PubSub publishes
|
2019-06-24 00:30:12 +00:00
|
|
|
|
2022-01-17 04:09:27 +00:00
|
|
|
vm *goja.Runtime
|
2019-04-19 01:15:05 +00:00
|
|
|
|
|
|
|
// setTimeout and setInterval variables.
|
|
|
|
timerLastID int // becomes 1 when first timer is set
|
|
|
|
timers map[int]*Timer
|
2019-04-16 06:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewVM creates a new JavaScript VM.
|
|
|
|
func NewVM(name string) *VM {
|
|
|
|
vm := &VM{
|
|
|
|
Name: name,
|
2022-01-17 04:09:27 +00:00
|
|
|
vm: goja.New(),
|
2019-04-19 01:15:05 +00:00
|
|
|
timers: map[int]*Timer{},
|
2019-06-24 00:30:12 +00:00
|
|
|
|
|
|
|
// Pub/sub structs.
|
2022-05-01 22:18:23 +00:00
|
|
|
Inbound: make(chan Message, 100),
|
2019-06-24 00:30:12 +00:00
|
|
|
Outbound: []chan Message{},
|
2022-04-10 19:39:27 +00:00
|
|
|
stop: make(chan bool, 1),
|
2022-01-17 04:09:27 +00:00
|
|
|
subscribe: map[string][]goja.Value{},
|
2019-04-16 06:07:15 +00:00
|
|
|
}
|
2022-01-17 04:09:27 +00:00
|
|
|
vm.Events = NewEvents(vm.vm)
|
2019-04-16 06:07:15 +00:00
|
|
|
return vm
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run code in the VM.
|
2022-01-17 04:09:27 +00:00
|
|
|
func (vm *VM) Run(src string) (goja.Value, error) {
|
|
|
|
v, err := vm.vm.RunString(src)
|
2019-04-16 06:07:15 +00:00
|
|
|
return v, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a value in the VM.
|
|
|
|
func (vm *VM) Set(name string, v interface{}) error {
|
|
|
|
return vm.vm.Set(name, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterLevelHooks registers accessors to the level hooks
|
|
|
|
// and Doodad API for Play Mode.
|
|
|
|
func (vm *VM) RegisterLevelHooks() error {
|
2020-04-22 06:50:45 +00:00
|
|
|
bindings := NewJSProxy(vm)
|
2019-04-16 06:07:15 +00:00
|
|
|
for name, v := range bindings {
|
|
|
|
err := vm.vm.Set(name, v)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("RegisterLevelHooks(%s): %s",
|
|
|
|
name, err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Main calls the main function of the script.
|
|
|
|
func (vm *VM) Main() error {
|
2022-01-17 04:09:27 +00:00
|
|
|
function, ok := goja.AssertFunction(vm.vm.Get("main"))
|
|
|
|
if !ok {
|
|
|
|
return errors.New("didn't find function main()")
|
2019-04-16 06:07:15 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 05:02:59 +00:00
|
|
|
// Catch panics.
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
log.Error("Panic caught in JavaScript VM: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-01-17 04:09:27 +00:00
|
|
|
_, err := function(goja.Undefined())
|
2019-04-16 06:07:15 +00:00
|
|
|
return err
|
|
|
|
}
|