doodle/pkg/scripting/scripting.go

108 lines
2.6 KiB
Go

// Package scripting manages the JavaScript VMs for Doodad scripts.
package scripting
import (
"errors"
"fmt"
"time"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
)
// Supervisor manages the JavaScript VMs for each doodad by its
// unique ID.
type Supervisor struct {
scripts map[string]*VM
// Global event handlers.
onLevelExit func()
onLevelFail func(message string)
}
// NewSupervisor creates a new JavaScript Supervior.
func NewSupervisor() *Supervisor {
return &Supervisor{
scripts: map[string]*VM{},
}
}
// 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
}
// InstallScripts loads scripts for all actors in the level.
func (s *Supervisor) InstallScripts(level *level.Level) error {
for _, actor := range level.Actors {
if err := s.AddLevelScript(actor.ID()); err != nil {
return err
}
}
// 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.
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
}
thisVM.Outbound = append(thisVM.Outbound, s.scripts[id].Inbound)
}
}
}
return nil
}
// AddLevelScript adds a script to the supervisor with level hooks.
func (s *Supervisor) AddLevelScript(id string) error {
if _, ok := s.scripts[id]; ok {
return fmt.Errorf("duplicate actor ID %s in level", id)
}
s.scripts[id] = NewVM(id)
RegisterPublishHooks(s, s.scripts[id])
RegisterEventHooks(s, s.scripts[id])
if err := s.scripts[id].RegisterLevelHooks(); err != nil {
return err
}
return nil
}
// To returns the VM for a named script.
func (s *Supervisor) To(name string) *VM {
if vm, ok := s.scripts[name]; ok {
return vm
}
// TODO: put this log back in, but add PLAYER script so it doesn't spam
// the console for missing PLAYER.
log.Error("scripting.Supervisor.To(%s): no such VM but returning blank VM",
name,
)
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")
}