Fix Two-State Blocks & Collision Detection
* Two-state Buttons now also subscribe to the state change message, so other on/off buttons in the same level update to match the state of the button that was hit. * Add lock mutexes around the scripting engine to protect from concurrent event handlers.
This commit is contained in:
parent
cd31868a13
commit
7b3aec0fef
|
@ -7,15 +7,10 @@ function main() {
|
||||||
|
|
||||||
Message.Subscribe("broadcast:state-change", function(newState) {
|
Message.Subscribe("broadcast:state-change", function(newState) {
|
||||||
state = !newState;
|
state = !newState;
|
||||||
console.warn("BLUE BLOCK Received state=%+v, set mine to %+v", newState, state);
|
|
||||||
|
|
||||||
// Layer 0: ON
|
// Layer 0: ON
|
||||||
// Layer 1: OFF
|
// Layer 1: OFF
|
||||||
if (state) {
|
Self.ShowLayer(state ? 0 : 1);
|
||||||
Self.ShowLayer(0);
|
|
||||||
} else {
|
|
||||||
Self.ShowLayer(1);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
|
|
|
@ -7,15 +7,10 @@ function main() {
|
||||||
|
|
||||||
Message.Subscribe("broadcast:state-change", function(newState) {
|
Message.Subscribe("broadcast:state-change", function(newState) {
|
||||||
state = newState;
|
state = newState;
|
||||||
console.warn("ORANGE BLOCK Received state=%+v, set mine to %+v", newState, state);
|
|
||||||
|
|
||||||
// Layer 0: OFF
|
// Layer 0: OFF
|
||||||
// Layer 1: ON
|
// Layer 1: ON
|
||||||
if (state) {
|
Self.ShowLayer(state ? 1 : 0);
|
||||||
Self.ShowLayer(1);
|
|
||||||
} else {
|
|
||||||
Self.ShowLayer(0);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
// State Block Control Button
|
// State Block Control Button
|
||||||
|
|
||||||
|
// Button is "OFF" by default.
|
||||||
|
var state = false;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
console.log("%s initialized!", Self.Doodad.Title);
|
console.log("%s ID '%s' initialized!", Self.Doodad.Title, Self.ID());
|
||||||
Self.SetHitbox(0, 0, 33, 33);
|
Self.SetHitbox(0, 0, 33, 33);
|
||||||
|
|
||||||
// When the button is activated, don't keep toggling state until we're not
|
// When the button is activated, don't keep toggling state until we're not
|
||||||
// being touched again.
|
// being touched again.
|
||||||
var colliding = false;
|
var colliding = false;
|
||||||
|
|
||||||
// Button is "OFF" by default.
|
// If we receive a state change event from a DIFFERENT on/off button, update
|
||||||
var state = false;
|
// ourself to match the state received.
|
||||||
|
Message.Subscribe("broadcast:state-change", function(value) {
|
||||||
|
state = value;
|
||||||
|
showSprite();
|
||||||
|
});
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
if (colliding) {
|
if (colliding) {
|
||||||
|
@ -17,24 +25,16 @@ function main() {
|
||||||
|
|
||||||
// Only trigger for mobile characters.
|
// Only trigger for mobile characters.
|
||||||
if (e.Actor.IsMobile()) {
|
if (e.Actor.IsMobile()) {
|
||||||
console.log("Mobile actor %s touched the on/off button!", e.Actor.Actor.Filename);
|
|
||||||
|
|
||||||
// Only activate if touched from the bottom or sides.
|
// Only activate if touched from the bottom or sides.
|
||||||
if (e.Overlap.Y === 0) {
|
if (e.Overlap.Y === 0) {
|
||||||
console.log("... but touched the top!");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
colliding = true;
|
colliding = true;
|
||||||
console.log(" -> emit state change");
|
|
||||||
state = !state;
|
state = !state;
|
||||||
Message.Broadcast("broadcast:state-change", state);
|
Message.Broadcast("broadcast:state-change", state);
|
||||||
|
|
||||||
if (state) {
|
showSprite();
|
||||||
Self.ShowLayer(1);
|
|
||||||
} else {
|
|
||||||
Self.ShowLayer(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always a solid button.
|
// Always a solid button.
|
||||||
|
@ -45,3 +45,12 @@ function main() {
|
||||||
colliding = false;
|
colliding = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the active layer based on the current button state.
|
||||||
|
function showSprite() {
|
||||||
|
if (state) {
|
||||||
|
Self.ShowLayer(1);
|
||||||
|
} else {
|
||||||
|
Self.ShowLayer(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -99,8 +99,6 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
// Set up the default scene.
|
// Set up the default scene.
|
||||||
if d.Scene == nil {
|
if d.Scene == nil {
|
||||||
// d.Goto(&GUITestScene{})
|
|
||||||
// d.NewMap()
|
|
||||||
d.Goto(&MainScene{})
|
d.Goto(&MainScene{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package scripting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"git.kirsle.net/go/render/event"
|
"git.kirsle.net/go/render/event"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
@ -25,6 +26,7 @@ var (
|
||||||
// Events API for Doodad scripts.
|
// Events API for Doodad scripts.
|
||||||
type Events struct {
|
type Events struct {
|
||||||
registry map[string][]otto.Value
|
registry map[string][]otto.Value
|
||||||
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEvents initializes the Events API.
|
// NewEvents initializes the Events API.
|
||||||
|
@ -70,6 +72,9 @@ func (e *Events) register(name string, callback otto.Value) otto.Value {
|
||||||
return otto.Value{} // TODO
|
return otto.Value{} // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.lock.Lock()
|
||||||
|
defer e.lock.Unlock()
|
||||||
|
|
||||||
if _, ok := e.registry[name]; !ok {
|
if _, ok := e.registry[name]; !ok {
|
||||||
e.registry[name] = []otto.Value{}
|
e.registry[name] = []otto.Value{}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +86,9 @@ func (e *Events) register(name string, callback otto.Value) otto.Value {
|
||||||
// Run an event handler. Returns an error only if there was a JavaScript error
|
// Run an event handler. Returns an error only if there was a JavaScript error
|
||||||
// inside the function. If there are no event handlers, just returns nil.
|
// inside the function. If there are no event handlers, just returns nil.
|
||||||
func (e *Events) run(name string, args ...interface{}) error {
|
func (e *Events) run(name string, args ...interface{}) error {
|
||||||
|
e.lock.RLock()
|
||||||
|
defer e.lock.RUnlock()
|
||||||
|
|
||||||
if _, ok := e.registry[name]; !ok {
|
if _, ok := e.registry[name]; !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) {
|
||||||
// for any matching messages received.
|
// for any matching messages received.
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range vm.Inbound {
|
for msg := range vm.Inbound {
|
||||||
log.Warn("vm: %s msg: %+v", vm.Name, msg)
|
vm.muSubscribe.RLock()
|
||||||
|
defer vm.muSubscribe.RUnlock()
|
||||||
|
|
||||||
if _, ok := vm.subscribe[msg.Name]; ok {
|
if _, ok := vm.subscribe[msg.Name]; ok {
|
||||||
for _, callback := range vm.subscribe[msg.Name] {
|
for _, callback := range vm.subscribe[msg.Name] {
|
||||||
callback.Call(otto.Value{}, msg.Args...)
|
callback.Call(otto.Value{}, msg.Args...)
|
||||||
|
@ -35,6 +37,9 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) {
|
||||||
// Register the Message.Subscribe and Message.Publish functions.
|
// Register the Message.Subscribe and Message.Publish functions.
|
||||||
vm.vm.Set("Message", map[string]interface{}{
|
vm.vm.Set("Message", map[string]interface{}{
|
||||||
"Subscribe": func(name string, callback otto.Value) {
|
"Subscribe": func(name string, callback otto.Value) {
|
||||||
|
vm.muSubscribe.Lock()
|
||||||
|
defer vm.muSubscribe.Unlock()
|
||||||
|
|
||||||
if !callback.IsFunction() {
|
if !callback.IsFunction() {
|
||||||
log.Error("SUBSCRIBE(%s): callback is not a function", name)
|
log.Error("SUBSCRIBE(%s): callback is not a function", name)
|
||||||
return
|
return
|
||||||
|
@ -57,8 +62,13 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) {
|
||||||
|
|
||||||
"Broadcast": func(name string, v ...interface{}) {
|
"Broadcast": func(name string, v ...interface{}) {
|
||||||
// Send the message to all actor VMs.
|
// Send the message to all actor VMs.
|
||||||
for _, vm := range s.scripts {
|
for _, toVM := range s.scripts {
|
||||||
vm.Inbound <- Message{
|
if vm.Name == toVM.Name {
|
||||||
|
log.Debug("Broadcast(%s): skip to vm '%s' cuz it is the sender", name, toVM.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
toVM.Inbound <- Message{
|
||||||
Name: name,
|
Name: name,
|
||||||
Args: v,
|
Args: v,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package scripting
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
@ -25,9 +26,10 @@ type VM struct {
|
||||||
// Each VM also has an array of Outbound channels which map to the Inbound
|
// 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()
|
// channel of the VMs it is linked to, for pushing out Message.Publish()
|
||||||
// messages.
|
// messages.
|
||||||
Inbound chan Message
|
Inbound chan Message
|
||||||
Outbound []chan Message
|
Outbound []chan Message
|
||||||
subscribe map[string][]otto.Value // Subscribed message handlers by name.
|
subscribe map[string][]otto.Value // Subscribed message handlers by name.
|
||||||
|
muSubscribe sync.RWMutex
|
||||||
|
|
||||||
vm *otto.Otto
|
vm *otto.Otto
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,12 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
var collidingActors = map[string]string{}
|
var collidingActors = map[string]string{}
|
||||||
for tuple := range collision.BetweenBoxes(boxes) {
|
for tuple := range collision.BetweenBoxes(boxes) {
|
||||||
a, b := w.actors[tuple.A], w.actors[tuple.B]
|
a, b := w.actors[tuple.A], w.actors[tuple.B]
|
||||||
|
|
||||||
|
// If neither actor is mobile, don't run collision handlers.
|
||||||
|
if !(a.IsMobile() || b.IsMobile()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
collidingActors[a.ID()] = b.ID()
|
collidingActors[a.ID()] = b.ID()
|
||||||
|
|
||||||
// Call the OnCollide handler for A informing them of B's intersection.
|
// Call the OnCollide handler for A informing them of B's intersection.
|
||||||
|
@ -219,7 +225,7 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
InHitbox: info.Overlap.Intersects(a.Hitbox()),
|
InHitbox: info.Overlap.Intersects(a.Hitbox()),
|
||||||
Settled: true,
|
Settled: true,
|
||||||
}); err != nil && err != scripting.ErrReturnFalse {
|
}); err != nil && err != scripting.ErrReturnFalse {
|
||||||
log.Error(err.Error())
|
log.Error("VM(%s).RunCollide: %s", a.ID(), err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user