WIP Refactor the doodad javascript API

This commit is contained in:
Noah 2025-02-13 20:12:28 -08:00
parent 1f00af5741
commit 0d624537ef
6 changed files with 164 additions and 38 deletions

2
pkg/scripting/api/api.go Normal file
View File

@ -0,0 +1,2 @@
// Package api defines the JavaScript API surface area for Doodad scripts.
package api

View File

@ -0,0 +1,32 @@
package api
import "github.com/dop251/goja"
/*
Console is exported to the global scope with `console.log` and friends available.
These functions have an API similar to that found in web browsers and node.js.
*/
type Console struct {
Log func(string, ...interface{}) `json:"log"`
Debug func(string, ...interface{}) `json:"debug"`
Warn func(string, ...interface{}) `json:"warn"`
Error func(string, ...interface{}) `json:"error"`
}
// ToMap converts the struct into a generic hash map.
func (g Console) ToMap() map[string]interface{} {
return map[string]interface{}{
"log": g.Log,
"debug": g.Debug,
"warn": g.Warn,
"error": g.Error,
}
}
// Register the global functions in your JavaScript VM.
func (g Console) Register(vm *goja.Runtime) {
for k, v := range g.ToMap() {
vm.Set(k, v)
}
}

View File

@ -0,0 +1,34 @@
package api
import (
"git.kirsle.net/go/render"
"github.com/dop251/goja"
)
// GameplayLevelControl
type GameplayLevelControl struct {
// EndLevel ends the game in a "win" condition.
EndLevel func()
// FailLevel ends the game in a "fail" condition, with the message shown to the player.
FailLevel func(message string)
// SetCheckpoint updates the player's respawn position to the given point.
SetCheckpoint func(render.Point)
}
// ToMap converts the struct into a generic hash map.
func (g GameplayLevelControl) ToMap() map[string]interface{} {
return map[string]interface{}{
"EndLevel": g.EndLevel,
"FailLevel": g.FailLevel,
"SetCheckpoint": g.SetCheckpoint,
}
}
// Register the global functions in your JavaScript VM.
func (g GameplayLevelControl) Register(vm *goja.Runtime) {
for k, v := range g.ToMap() {
vm.Set(k, v)
}
}

61
pkg/scripting/api/time.go Normal file
View File

@ -0,0 +1,61 @@
// Package api defines the JavaScript API surface area for Doodad scripts.
package api
import (
"time"
"github.com/dop251/goja"
)
// Time functions exposed on the global scope like `time.Now()`.
//
// They are mainly a direct export of useful things from the Go `time` package.
type Time struct {
Now func() time.Time
Since func(time.Time) time.Duration
Add func(t time.Time, milliseconds int64) time.Time
// Multiples of time.Duration.
Hour time.Duration
Minute time.Duration
Second time.Duration
Millisecond time.Duration
Microsecond time.Duration
}
// NewTime creates an instanced global `time` object for your JavaScript VM.
func NewTime() Time {
return Time{
Now: time.Now,
Since: time.Since,
Add: func(t time.Time, ms int64) time.Time {
return t.Add(time.Duration(ms) * time.Millisecond)
},
Hour: time.Hour,
Minute: time.Minute,
Second: time.Second,
Millisecond: time.Millisecond,
Microsecond: time.Microsecond,
}
}
// ToMap converts the struct into a generic hash map.
func (g Time) ToMap() map[string]interface{} {
return map[string]interface{}{
"Now": g.Now,
"Since": g.Since,
"Add": g.Add,
"Hour": g.Hour,
"Minute": g.Minute,
"Second": g.Second,
"Millisecond": g.Millisecond,
"Microsecond": g.Microsecond,
}
}
// Register the global functions in your JavaScript VM.
func (g Time) Register(vm *goja.Runtime) {
for k, v := range g.ToMap() {
vm.Set(k, v)
}
}

View File

@ -2,10 +2,10 @@ package scripting
import ( import (
"fmt" "fmt"
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/physics" "git.kirsle.net/SketchyMaze/doodle/pkg/physics"
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting/api"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem" "git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/sound" "git.kirsle.net/SketchyMaze/doodle/pkg/sound"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
@ -29,14 +29,17 @@ func ProxyLog(vm *VM, fn func(string, ...interface{})) func(string, ...interface
// NewJSProxy initializes the API structure for JavaScript binding. // NewJSProxy initializes the API structure for JavaScript binding.
func NewJSProxy(vm *VM) JSProxy { func NewJSProxy(vm *VM) JSProxy {
// jp := JSProxy{}
// return jp
return JSProxy{ return JSProxy{
// Console logging. // Console logging.
"console": map[string]interface{}{ "console": api.Console{
"log": ProxyLog(vm, log.Info), Log: ProxyLog(vm, log.Info),
"debug": ProxyLog(vm, log.Debug), Debug: ProxyLog(vm, log.Debug),
"warn": ProxyLog(vm, log.Warn), Warn: ProxyLog(vm, log.Warn),
"error": ProxyLog(vm, log.Error), Error: ProxyLog(vm, log.Error),
}, }.ToMap(),
// Audio API. // Audio API.
"Sound": map[string]interface{}{ "Sound": map[string]interface{}{
@ -53,18 +56,7 @@ func NewJSProxy(vm *VM) JSProxy {
"GetTick": func() uint64 { "GetTick": func() uint64 {
return shmem.Tick return shmem.Tick
}, },
"time": map[string]interface{}{ "time": api.NewTime().ToMap(),
"Now": time.Now,
"Since": time.Since,
"Add": func(t time.Time, ms int64) time.Time {
return t.Add(time.Duration(ms) * time.Millisecond)
},
"Hour": time.Hour,
"Minute": time.Minute,
"Second": time.Second,
"Millisecond": time.Millisecond,
"Microsecond": time.Microsecond,
},
// Bindings into the VM. // Bindings into the VM.
"Events": vm.Events, "Events": vm.Events,

View File

@ -1,6 +1,9 @@
package scripting package scripting
import "git.kirsle.net/go/render" import (
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting/api"
"git.kirsle.net/go/render"
)
/* /*
RegisterEventHooks attaches the supervisor level event hooks into a JS VM. RegisterEventHooks attaches the supervisor level event hooks into a JS VM.
@ -13,24 +16,26 @@ Names registered:
- SetCheckpoint(): update the player's respawn location. - SetCheckpoint(): update the player's respawn location.
*/ */
func RegisterEventHooks(s *Supervisor, vm *VM) { func RegisterEventHooks(s *Supervisor, vm *VM) {
vm.Set("EndLevel", func() { api.GameplayLevelControl{
if s.onLevelFail == nil { EndLevel: func() {
panic("JS FailLevel(): No OnLevelFail handler attached to script supervisor") if s.onLevelFail == nil {
} panic("JS FailLevel(): No OnLevelFail handler attached to script supervisor")
s.onLevelExit() }
}) s.onLevelExit()
vm.Set("FailLevel", func(message string) { },
if s.onLevelFail == nil { FailLevel: func(message string) {
panic("JS FailLevel(): No OnLevelFail handler attached to script supervisor") if s.onLevelFail == nil {
} panic("JS FailLevel(): No OnLevelFail handler attached to script supervisor")
s.onLevelFail(message) }
}) s.onLevelFail(message)
vm.Set("SetCheckpoint", func(p render.Point) { },
if s.onSetCheckpoint == nil { SetCheckpoint: func(p render.Point) {
panic("JS SetCheckpoint(): No OnSetCheckpoint handler attached to script supervisor") if s.onSetCheckpoint == nil {
} panic("JS SetCheckpoint(): No OnSetCheckpoint handler attached to script supervisor")
s.onSetCheckpoint(p) }
}) s.onSetCheckpoint(p)
},
}.Register(vm.vm)
} }
// OnLevelExit registers an event hook for when a Level Exit doodad is reached. // OnLevelExit registers an event hook for when a Level Exit doodad is reached.