2020-04-22 06:50:45 +00:00
|
|
|
// Package scripting manages the JavaScript VMs for Doodad scripts.
|
2019-04-16 06:07:15 +00:00
|
|
|
package scripting
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-04-19 01:15:05 +00:00
|
|
|
"time"
|
2019-04-16 06:07:15 +00:00
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/level"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
2021-08-16 03:17:53 +00:00
|
|
|
"git.kirsle.net/go/render"
|
2019-04-16 06:07:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Supervisor manages the JavaScript VMs for each doodad by its
|
|
|
|
// unique ID.
|
|
|
|
type Supervisor struct {
|
|
|
|
scripts map[string]*VM
|
2019-07-02 22:24:46 +00:00
|
|
|
|
|
|
|
// Global event handlers.
|
2021-08-16 03:17:53 +00:00
|
|
|
onLevelExit func()
|
|
|
|
onLevelFail func(message string)
|
|
|
|
onSetCheckpoint func(where render.Point)
|
2019-04-16 06:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewSupervisor creates a new JavaScript Supervior.
|
|
|
|
func NewSupervisor() *Supervisor {
|
|
|
|
return &Supervisor{
|
|
|
|
scripts: map[string]*VM{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-10 19:39:27 +00:00
|
|
|
// Teardown the supervisor to clean up goroutines.
|
|
|
|
func (s *Supervisor) Teardown() {
|
|
|
|
log.Info("scripting.Teardown(): stop all (%d) scripts", len(s.scripts))
|
|
|
|
for _, vm := range s.scripts {
|
|
|
|
vm.stop <- true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 01:15:05 +00:00
|
|
|
// Loop the supervisor to invoke timer events in any running scripts.
|
|
|
|
func (s *Supervisor) Loop() error {
|
|
|
|
now := time.Now()
|
|
|
|
for _, vm := range s.scripts {
|
|
|
|
vm.TickTimer(now)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-16 06:07:15 +00:00
|
|
|
// InstallScripts loads scripts for all actors in the level.
|
|
|
|
func (s *Supervisor) InstallScripts(level *level.Level) error {
|
|
|
|
for _, actor := range level.Actors {
|
2021-10-10 03:45:38 +00:00
|
|
|
if err := s.AddLevelScript(actor.ID(), actor.Filename); err != nil {
|
2019-04-16 06:07:15 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2019-06-24 00:30:12 +00:00
|
|
|
|
|
|
|
// Loop again to bridge channels together for linked VMs.
|
|
|
|
for _, actor := range level.Actors {
|
|
|
|
// Add linked actor IDs.
|
|
|
|
if len(actor.Links) > 0 {
|
|
|
|
// Bridge the links up.
|
|
|
|
var thisVM = s.scripts[actor.ID()]
|
|
|
|
for _, id := range actor.Links {
|
|
|
|
// Assign this target actor's Inbound channel to the source
|
|
|
|
// actor's array of Outbound channels.
|
2019-07-03 23:51:23 +00:00
|
|
|
if _, ok := s.scripts[id]; !ok {
|
|
|
|
log.Error("scripting.InstallScripts: actor %s is linked to %s but %s was not found",
|
|
|
|
actor.ID(),
|
|
|
|
id,
|
|
|
|
id,
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
}
|
2019-06-24 00:30:12 +00:00
|
|
|
thisVM.Outbound = append(thisVM.Outbound, s.scripts[id].Inbound)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-16 06:07:15 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-02 01:30:30 +00:00
|
|
|
// AddLevelScript adds a script to the supervisor with level hooks.
|
2021-10-10 03:45:38 +00:00
|
|
|
// The `id` will key the VM and should be the Actor ID in the level.
|
|
|
|
// The `name` is used to name the VM for debug logging.
|
|
|
|
func (s *Supervisor) AddLevelScript(id string, name string) error {
|
2019-05-02 01:30:30 +00:00
|
|
|
if _, ok := s.scripts[id]; ok {
|
|
|
|
return fmt.Errorf("duplicate actor ID %s in level", id)
|
|
|
|
}
|
|
|
|
|
2021-10-10 03:45:38 +00:00
|
|
|
s.scripts[id] = NewVM(fmt.Sprintf("%s#%s", name, id))
|
2019-12-31 02:13:28 +00:00
|
|
|
RegisterPublishHooks(s, s.scripts[id])
|
2019-07-02 22:24:46 +00:00
|
|
|
RegisterEventHooks(s, s.scripts[id])
|
2019-05-02 01:30:30 +00:00
|
|
|
if err := s.scripts[id].RegisterLevelHooks(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-16 06:07:15 +00:00
|
|
|
// To returns the VM for a named script.
|
|
|
|
func (s *Supervisor) To(name string) *VM {
|
|
|
|
if vm, ok := s.scripts[name]; ok {
|
|
|
|
return vm
|
|
|
|
}
|
|
|
|
|
2019-04-19 01:15:05 +00:00
|
|
|
// TODO: put this log back in, but add PLAYER script so it doesn't spam
|
|
|
|
// the console for missing PLAYER.
|
2021-01-03 23:19:21 +00:00
|
|
|
log.Error("scripting.Supervisor.To(%s): no such VM but returning blank VM",
|
|
|
|
name,
|
|
|
|
)
|
2019-04-16 06:07:15 +00:00
|
|
|
return NewVM(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetVM returns a script VM from the supervisor.
|
|
|
|
func (s *Supervisor) GetVM(name string) (*VM, error) {
|
|
|
|
if vm, ok := s.scripts[name]; ok {
|
|
|
|
return vm, nil
|
|
|
|
}
|
|
|
|
return nil, errors.New("not found")
|
|
|
|
}
|
2022-01-19 05:24:36 +00:00
|
|
|
|
|
|
|
// RemoveVM removes a script from the supervisor, stopping it.
|
|
|
|
func (s *Supervisor) RemoveVM(name string) error {
|
|
|
|
if _, ok := s.scripts[name]; ok {
|
|
|
|
delete(s.scripts, name)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.New("not found")
|
|
|
|
}
|