doodle/pkg/scripting/vm.go
Noah Petherbridge ba6892aa95 WASM Texture Caching
* Refactor texture caching in render.Engine:
  * New interface method: NewTexture(filename string, image.Image)
  * WASM immediately encodes the image to PNG and generates a JavaScript
    `Image()` object to load it with a data URI and keep it in memory.
  * SDL2 saves the bitmap to disk as it did before.
  * WASM: deprecate the sessionStorage for holding image data. Session
    storage methods panic if called. The image data is directly kept in
    Go memory as a js.Value holding an Image().
* Shared Memory workaround: the level.Chunk.ToBitmap() function is where
  chunk textures get cached, but it had no access to the render.Engine
  used in the game. The `pkg/shmem` package holds global pointers to
  common structures like the CurrentRenderEngine as a work-around.
  * Also shmem.Flash() so Doodle can make its d.Flash() function
    globally available, any sub-package can now flash text to the screen
    regardless of source code location.
  * JavaScript API for Doodads now has a global Flash() function
    available.
* WASM: Handle window resize so Doodle can recompute its dimensions
  instead of scaling/shrinking the view.
2019-06-27 12:03:52 -07:00

132 lines
3.0 KiB
Go

package scripting
import (
"fmt"
"reflect"
"time"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem"
"github.com/robertkrimen/otto"
)
// VM manages a single isolated JavaScript VM.
type VM struct {
Name string
// Globals available to the scripts.
Events *Events
Self interface{}
// 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.
Inbound chan Message
Outbound []chan Message
subscribe map[string][]otto.Value // Subscribed message handlers by name.
vm *otto.Otto
// setTimeout and setInterval variables.
timerLastID int // becomes 1 when first timer is set
timers map[int]*Timer
}
// NewVM creates a new JavaScript VM.
func NewVM(name string) *VM {
vm := &VM{
Name: name,
Events: NewEvents(),
vm: otto.New(),
timers: map[int]*Timer{},
// Pub/sub structs.
Inbound: make(chan Message),
Outbound: []chan Message{},
subscribe: map[string][]otto.Value{},
}
return vm
}
// Run code in the VM.
func (vm *VM) Run(src interface{}) (otto.Value, error) {
v, err := vm.vm.Run(src)
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 {
bindings := map[string]interface{}{
"log": log.Logger,
"Flash": shmem.Flash,
"RGBA": render.RGBA,
"Point": render.NewPoint,
"Self": vm.Self, // i.e., the uix.Actor object
"Events": vm.Events,
"TypeOf": reflect.TypeOf,
"time": map[string]interface{}{
"Now": time.Now,
"Add": func(t time.Time, ms int64) time.Time {
return t.Add(time.Duration(ms) * time.Millisecond)
},
},
// Timer functions with APIs similar to the web browsers.
"setTimeout": vm.SetTimeout,
"setInterval": vm.SetInterval,
"clearTimeout": vm.ClearTimer,
"clearInterval": vm.ClearTimer,
}
for name, v := range bindings {
err := vm.vm.Set(name, v)
if err != nil {
return fmt.Errorf("RegisterLevelHooks(%s): %s",
name, err,
)
}
}
// Alias the console.log functions to the logger.
vm.vm.Run(`
console = {};
console.log = log.Info;
console.debug = log.Debug;
console.warn = log.Warn;
console.error = log.Error;
`)
return nil
}
// Main calls the main function of the script.
func (vm *VM) Main() error {
function, err := vm.vm.Get("main")
if err != nil {
return err
}
if !function.IsFunction() {
return nil
}
// Catch panics.
defer func() {
if err := recover(); err != nil {
log.Error("Panic caught in JavaScript VM: %s", err)
}
}()
_, err = function.Call(otto.Value{})
return err
}