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.
This commit is contained in:
parent
87416f9740
commit
99eab19c5b
|
@ -1,9 +1,6 @@
|
||||||
function main() {
|
function main() {
|
||||||
log.Info("Azulian '%s' initialized!", Self.Doodad.Title);
|
log.Info("Azulian '%s' initialized!", Self.Doodad.Title);
|
||||||
|
|
||||||
|
|
||||||
Self.Canvas.SetBackground(RGBA(0, 153, 255, 100));
|
|
||||||
|
|
||||||
var playerSpeed = 12;
|
var playerSpeed = 12;
|
||||||
var gravity = 4;
|
var gravity = 4;
|
||||||
var Vx = Vy = 0;
|
var Vx = Vy = 0;
|
||||||
|
|
|
@ -6,11 +6,10 @@ function main() {
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
// Verify they've touched the button.
|
// Verify they've touched the button.
|
||||||
if (e.Overlap.Y + e.Overlap.H < 24) {
|
if (e.Overlap.Y + e.Overlap.H < 24) {
|
||||||
Self.Canvas.SetBackground(RGBA(0, 255, 0, 153));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self.Canvas.SetBackground(RGBA(255, 255, 0, 153));
|
Message.Publish("power", true);
|
||||||
|
|
||||||
if (timer > 0) {
|
if (timer > 0) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
@ -19,12 +18,13 @@ function main() {
|
||||||
Self.ShowLayer(1);
|
Self.ShowLayer(1);
|
||||||
timer = setTimeout(function() {
|
timer = setTimeout(function() {
|
||||||
Self.ShowLayer(0);
|
Self.ShowLayer(0);
|
||||||
|
Message.Publish("power", false);
|
||||||
timer = 0;
|
timer = 0;
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.OnLeave(function(e) {
|
// Events.OnLeave(function(e) {
|
||||||
console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
// console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
||||||
Self.Canvas.SetBackground(RGBA(0, 0, 1, 0));
|
// Self.Canvas.SetBackground(RGBA(0, 0, 1, 0));
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,15 @@ function main() {
|
||||||
|
|
||||||
var pressed = false;
|
var pressed = false;
|
||||||
|
|
||||||
|
// When a sticky button receives power, it pops back up.
|
||||||
|
Message.Subscribe("power", function(powered) {
|
||||||
|
if (powered && pressed) {
|
||||||
|
Self.ShowLayer(0);
|
||||||
|
pressed = false;
|
||||||
|
Message.Publish("power", false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
return;
|
return;
|
||||||
|
@ -15,5 +24,6 @@ function main() {
|
||||||
|
|
||||||
Self.ShowLayer(1);
|
Self.ShowLayer(1);
|
||||||
pressed = true;
|
pressed = true;
|
||||||
|
Message.Publish("power", true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,24 +6,42 @@ function main() {
|
||||||
var animating = false;
|
var animating = false;
|
||||||
var opened = false;
|
var opened = false;
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Self.SetHitbox(16, 0, 32, 64);
|
||||||
|
|
||||||
|
Message.Subscribe("power", function(powered) {
|
||||||
|
console.log("%s got power=%+v", Self.Doodad.Title, powered);
|
||||||
|
|
||||||
|
if (powered) {
|
||||||
if (animating || opened) {
|
if (animating || opened) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Overlap.X + e.Overlap.W >= 16 && e.Overlap.X < 48) {
|
|
||||||
animating = true;
|
animating = true;
|
||||||
Self.PlayAnimation("open", function() {
|
Self.PlayAnimation("open", function() {
|
||||||
opened = true;
|
opened = true;
|
||||||
animating = false;
|
animating = false;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
animating = true;
|
||||||
|
Self.PlayAnimation("close", function() {
|
||||||
|
opened = false;
|
||||||
|
animating = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Events.OnCollide(function(e) {
|
||||||
|
if (e.InHitbox) {
|
||||||
|
if (!opened) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Events.OnLeave(function() {
|
Events.OnLeave(function() {
|
||||||
if (opened) {
|
// if (opened) {
|
||||||
Self.PlayAnimation("close", function() {
|
// Self.PlayAnimation("close", function() {
|
||||||
opened = false;
|
// opened = false;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ function main() {
|
||||||
Self.AddAnimation("open", 0, [1]);
|
Self.AddAnimation("open", 0, [1]);
|
||||||
var unlocked = false;
|
var unlocked = false;
|
||||||
|
|
||||||
Self.Canvas.SetBackground(RGBA(0, 255, 255, 100));
|
// Self.Canvas.SetBackground(RGBA(0, 255, 255, 100));
|
||||||
|
|
||||||
// Map our door names to key names.
|
// Map our door names to key names.
|
||||||
var KeyMap = {
|
var KeyMap = {
|
||||||
|
@ -12,8 +12,8 @@ function main() {
|
||||||
"Yellow Door": "Yellow Key"
|
"Yellow Door": "Yellow Key"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn("%s loaded!", Self.Doodad.Title);
|
// log.Warn("%s loaded!", Self.Doodad.Title);
|
||||||
console.log("%s Setting hitbox", Self.Doodad.Title);
|
// console.log("%s Setting hitbox", Self.Doodad.Title);
|
||||||
Self.SetHitbox(16, 0, 32, 64);
|
Self.SetHitbox(16, 0, 32, 64);
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
|
@ -32,7 +32,7 @@ function main() {
|
||||||
Self.PlayAnimation("open", null);
|
Self.PlayAnimation("open", null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Events.OnLeave(function(e) {
|
// Events.OnLeave(function(e) {
|
||||||
console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
// console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
60
pkg/scripting/pubsub.go
Normal file
60
pkg/scripting/pubsub.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package scripting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message holds data being published from one script VM with information sent
|
||||||
|
// to the linked VMs.
|
||||||
|
type Message struct {
|
||||||
|
Name string
|
||||||
|
Args []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
RegisterPublishHooks adds the pub/sub hooks to a JavaScript VM.
|
||||||
|
|
||||||
|
This adds the global methods `Message.Subscribe(name, func)` and
|
||||||
|
`Message.Publish(name, args)` to the JavaScript VM's scope.
|
||||||
|
*/
|
||||||
|
func RegisterPublishHooks(vm *VM) {
|
||||||
|
// Goroutine to watch the VM's inbound channel and invoke Subscribe handlers
|
||||||
|
// for any matching messages received.
|
||||||
|
go func() {
|
||||||
|
for msg := range vm.Inbound {
|
||||||
|
log.Warn("vm: %s msg: %+v", vm.Name, msg)
|
||||||
|
if _, ok := vm.subscribe[msg.Name]; ok {
|
||||||
|
for _, callback := range vm.subscribe[msg.Name] {
|
||||||
|
callback.Call(otto.Value{}, msg.Args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Register the Message.Subscribe and Message.Publish functions.
|
||||||
|
vm.vm.Set("Message", map[string]interface{}{
|
||||||
|
"Subscribe": func(name string, callback otto.Value) {
|
||||||
|
log.Error("SUBSCRIBE: %s", name)
|
||||||
|
if !callback.IsFunction() {
|
||||||
|
log.Error("SUBSCRIBE(%s): callback is not a function", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := vm.subscribe[name]; !ok {
|
||||||
|
vm.subscribe[name] = []otto.Value{}
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.subscribe[name] = append(vm.subscribe[name], callback)
|
||||||
|
},
|
||||||
|
|
||||||
|
"Publish": func(name string, v ...interface{}) {
|
||||||
|
log.Error("PUBLISH: %s %+v", name, v)
|
||||||
|
for _, channel := range vm.Outbound {
|
||||||
|
channel <- Message{
|
||||||
|
Name: name,
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -40,6 +40,20 @@ func (s *Supervisor) InstallScripts(level *level.Level) error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +66,7 @@ func (s *Supervisor) AddLevelScript(id string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scripts[id] = NewVM(id)
|
s.scripts[id] = NewVM(id)
|
||||||
|
RegisterPublishHooks(s.scripts[id])
|
||||||
if err := s.scripts[id].RegisterLevelHooks(); err != nil {
|
if err := s.scripts[id].RegisterLevelHooks(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,16 @@ type VM struct {
|
||||||
Events *Events
|
Events *Events
|
||||||
Self interface{}
|
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
|
vm *otto.Otto
|
||||||
|
|
||||||
// setTimeout and setInterval variables.
|
// setTimeout and setInterval variables.
|
||||||
|
@ -32,6 +42,11 @@ func NewVM(name string) *VM {
|
||||||
Events: NewEvents(),
|
Events: NewEvents(),
|
||||||
vm: otto.New(),
|
vm: otto.New(),
|
||||||
timers: map[int]*Timer{},
|
timers: map[int]*Timer{},
|
||||||
|
|
||||||
|
// Pub/sub structs.
|
||||||
|
Inbound: make(chan Message),
|
||||||
|
Outbound: []chan Message{},
|
||||||
|
subscribe: map[string][]otto.Value{},
|
||||||
}
|
}
|
||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user