doodle/pkg/scripting/scripting.go
Noah Petherbridge 99eab19c5b Pub/Sub Messages Between Linked Actors (JavaScript)
* Implement the pub/sub message passing system that lets the JavaScript
  VM of one actor (say, a Button) send messages to other linked actors
  in the level (say, an Electric Door)
* Buttons now emit a "power(true)" message while pressed and
  "power(false)" when released. Sticky Buttons do not release and so do
  not send the power(false) message.
* Electric Doors listen for the "power" event and open or close
  themselves based on the boolean value received.
* If a Sticky Button receives power and is currently pressed down, it
  will pop back up (reset to "off" position) and notify its linked
  actors that they have lost power too. So if a Sticky Button held an
  Electric Door open, and another Button powers the Sticky Button, it
  would pop back up and also close the Electric Door.
2019-06-23 17:34:11 -07:00

98 lines
2.3 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
}
// 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.
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 {
log.Debug("InstallScripts: load script from Actor %s", id)
if _, ok := s.scripts[id]; ok {
return fmt.Errorf("duplicate actor ID %s in level", id)
}
s.scripts[id] = NewVM(id)
RegisterPublishHooks(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")
}