changed "beholder:foo" by "beholder.foo". Removed "self checks"

This commit is contained in:
Enrique García Cota 2012-01-29 13:56:34 +01:00
parent 6f4eff548f
commit 0b03e09803
4 changed files with 133 additions and 157 deletions

View File

@ -10,18 +10,18 @@ beholder = require 'beholder'
...
local goblin1 = {x=100, y=100}
goblin1.pauseId = beholder:observe("PAUSE", function() goblin1.paused = true end)
goblin1.pauseId = beholder.observe("PAUSE", function() goblin1.paused = true end)
...
local goblin2 = {x=200, y=100}
goblin2.pauseId = beholder:observe("PAUSE", function() goblin2.paused = true end)
goblin2.pauseId = beholder.observe("PAUSE", function() goblin2.paused = true end)
...
function pauseButtonPressed()
beholder:trigger("PAUSE")
beholder.trigger("PAUSE")
end
@ -38,23 +38,23 @@ end
...
function destroyGoblin(goblin)
beholder:stopObserving(goblin.pauseId)
beholder.stopObserving(goblin.pauseId)
end
</pre>
(Note: if you are doing lots of that "if whatever.state then ..." you might want to give a look to "stateful.lua":http://github.com/kikito/stateful.lua )
(Note. if you are doing lots of that "if whatever.state then ..." you might want to give a look to "stateful.lua".http.//github.com/kikito/stateful.lua )
h1. Why?
This library tries to solve the following problem: some actions need to be executed when some asynchronous condition is fulfilled. By "asyncronous" we mean that it something that typically doesn't depend on the code. Hence precalculating it beforehand is impractical.
This library tries to solve the following problem. some actions need to be executed when some asynchronous condition is fulfilled. By "asyncronous" we mean that it something that typically doesn't depend on the code. Hence precalculating it beforehand is impractical.
Some examples:
Some examples.
* The pause menu is brought up, and all the actors in your videogame need to be frozen.
* An image has item has been loaded from disk, and a progress bar needs to be updated.
* The user presses certain combination of keys.
The way these problems are typically handed is by continuously polling for the trigger condition. For example, on the pause menu, one would find this code on the enemy movement routines:
The way these problems are typically handed is by continuously polling for the trigger condition. For example, on the pause menu, one would find this code on the enemy movement routines.
<pre>
if pause_menu_is_up then
@ -64,72 +64,72 @@ else
end
</pre>
You will have a code similar to that on each part that needs to be stopped: on your enemy code, the bullet code, the player code, etc.
You will have a code similar to that on each part that needs to be stopped. on your enemy code, the bullet code, the player code, etc.
But the biggest problem with that code is lack of separation. The code dealign with your goblins should only deal with goblin stuff. It should not "know" about the menu system, or the keyboard actions, or the file loader. And the same goes with your bullet code, player code, etc. They don't need to know about exernal systems, such as the keyboard.
This library allows you to build "walls" between them: your keyboard code will just raise events, and your player code will observe those events. This allows for better encapsulation; if you later add multiplayer functionality, for example, the network module will just have to raise the same events just like the keyboard module did; your player logic will be unaffected.
This library allows you to build "walls" between them. your keyboard code will just raise events, and your player code will observe those events. This allows for better encapsulation; if you later add multiplayer functionality, for example, the network module will just have to raise the same events just like the keyboard module did; your player logic will be unaffected.
You can obviously attach any number of observers to any given event. Similarly, you are
h1. Halting event observation
Every call to @beholder:observe@ returns an identifier which can be stored:
Every call to @beholder.observe@ returns an identifier which can be stored.
<pre>local id = beholder:observe("FOO", bar, baz)</pre>
<pre>local id = beholder.observe("FOO", bar, baz)</pre>
That identifier can be used to cancel the observation at any moment. You can do so by using the @beholder:stopObserving@ method:
That identifier can be used to cancel the observation at any moment. You can do so by using the @beholder.stopObserving@ method.
<pre>beholder:stopObserving(id)</pre>
<pre>beholder.stopObserving(id)</pre>
h1. Composed events
Events can be any type of Lua object. On the example, we used the "PAUSE" string. It could also be a number, a function or a table. The == operator is used in all cases.
Composed events are built from more than one lua object. You can do them by simply adding more parameters to the observe/trigger functions. For example, this trigger:
Composed events are built from more than one lua object. You can do them by simply adding more parameters to the observe/trigger functions. For example, this trigger.
<pre>Beholder:trigger('PLAYERDETECTION', player1, 100, 200)</pre>
<pre>Beholder.trigger('PLAYERDETECTION', player1, 100, 200)</pre>
Will trigger this action:
Will trigger this action.
<pre>Beholder:observe('PLAYERDETECTION', player1, 100, 200, function() print("player1 detected at 100, 200") end)</pre>
<pre>Beholder.observe('PLAYERDETECTION', player1, 100, 200, function() print("player1 detected at 100, 200") end)</pre>
Composed events are inclusive. This means that this other observer will also get activated by the aforementioned trigger.
<pre>Beholder:observe('PLAYERDETECTION', player1, function(x,y) print("player1 detected at ",x,y)</pre>
<pre>Beholder.observe('PLAYERDETECTION', player1, function(x,y) print("player1 detected at ",x,y)</pre>
Notice that the two "non-observed integers" will be passed to the callback as additional parameters. That second action will be executed any time player1 is detected, no matter what coordinates.
Similarly, you can add an action that will be triggered for any player detection:
Similarly, you can add an action that will be triggered for any player detection.
<pre>Beholder:observe('PLAYERDETECTION', function(player,x,y) print(player.no," detected at ",x,y)</pre>
<pre>Beholder.observe('PLAYERDETECTION', function(player,x,y) print(player.no," detected at ",x,y)</pre>
h1. The nil event
If you want to detect all signals raised (i.e. for logging and debugging) you can do so by observing the "empty" event - simply pass a function to observe, without adding any more params:
If you want to detect all signals raised (i.e. for logging and debugging) you can do so by observing the "empty" event - simply pass a function to observe, without adding any more params.
<pre>Beholder:observe(function(...) log("Event triggered", ...) end)</pre>
<pre>Beholder.observe(function(...) log("Event triggered", ...) end)</pre>
A quick and dirty way of dumping all events in the standard output is just observing the nil event with @print@:
A quick and dirty way of dumping all events in the standard output is just observing the nil event with @print@.
<pre>Beholder:observe(print)</pre>
<pre>Beholder.observe(print)</pre>
If you want to trigger the events attached only to the nil event, you can do so by calling trigger without params:
If you want to trigger the events attached only to the nil event, you can do so by calling trigger without params.
<pre>Beholder:trigger()</pre>
<pre>Beholder.trigger()</pre>
h1. Triggering all callbacks
You can use the @triggerAll@ method to trigger all observed events (this will be useful mostly for debugging):
You can use the @triggerAll@ method to trigger all observed events (this will be useful mostly for debugging).
<pre>Beholder:triggerAll(...)</pre>
<pre>Beholder.triggerAll(...)</pre>
Note that you can pass parameters to @triggerAll@. These will be passed to all callbacks (make sure that they are prepared for this, or you will get errors).
h1. Installation
Just copy the beholder.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it:
Just copy the beholder.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it.
<pre>local Beholder = require 'beholder'</pre>
@ -141,7 +141,7 @@ Please make sure that you read the license, too (for your convenience it's now i
h1. Specs
This project uses "telescope":https://github.com/norman/telescope for its specs. If you want to run the specs, you will have to install telescope first. Then just execute the following from the root inspect folder:
This project uses "telescope".https.//github.com/norman/telescope for its specs. If you want to run the specs, you will have to install telescope first. Then just execute the following from the root inspect folder.
<pre>
tsc -f spec/*.lua

View File

@ -83,59 +83,56 @@ function Node:removeCallback(id)
Node._nodesById[id] = nil
end
------ beholder table
local beholder = {}
-- beholder private functions
local root = nil
local function falseIfZero(n)
return n > 0 and n
end
local function checkSelf(self, methodName)
assert(type(self)=="table" and self._root, "Use beholder:" .. methodName .. " instead of beholder." .. methodName)
end
local function extractEventAndCallbackFromParams(params)
assert(#params > 0, "beholder:observe requires at least one parameter - the callback. You usually want to use two, i.e.: beholder:observe('EVENT', callback)")
local callback = table.remove(params, #params)
return params, callback
end
local function initialize(self)
self._root = Node:new()
local function initialize()
root = Node:new()
end
------ Public interface
local beholder = {}
function beholder:observe(...)
checkSelf(self, 'observe')
function beholder.observe(...)
local event, callback = extractEventAndCallbackFromParams({...})
return self._root:findOrCreateDescendant(event):addCallback(callback)
return root:findOrCreateDescendant(event):addCallback(callback)
end
function beholder:stopObserving(id)
checkSelf(self, 'stopObserving')
function beholder.stopObserving(id)
local node = Node:findById(id)
if not node then return false end
node:removeCallback(id)
return true
end
function beholder:trigger(...)
checkSelf(self, 'trigger')
return falseIfZero( self._root:invokeCallbacksFromPath({...}) )
function beholder.trigger(...)
return falseIfZero( root:invokeCallbacksFromPath({...}) )
end
function beholder:triggerAll(...)
checkSelf(self, 'triggerAll')
return falseIfZero( self._root:invokeAllCallbacksInSubTree({...}) )
function beholder.triggerAll(...)
return falseIfZero( root:invokeAllCallbacksInSubTree({...}) )
end
function beholder:reset()
checkSelf(self, 'reset')
initialize(self)
function beholder.reset()
initialize()
end
initialize(beholder)
initialize()
return beholder

View File

@ -4,23 +4,23 @@ local beholder = require 'beholder'
describe("Acceptance", function()
before(function()
beholder:reset()
beholder.reset()
end)
test("Normal behavior", function()
local counter = 0
local id = beholder:observe("EVENT", function() counter = counter + 1 end)
local id = beholder.observe("EVENT", function() counter = counter + 1 end)
beholder:trigger("EVENT")
beholder:trigger("EVENT")
beholder.trigger("EVENT")
beholder.trigger("EVENT")
assert_equal(counter, 2)
beholder:stopObserving(id)
beholder.stopObserving(id)
beholder:trigger("EVENT")
beholder.trigger("EVENT")
assert_equal(counter, 2)
@ -30,24 +30,24 @@ describe("Acceptance", function()
local counter1, counter2 = 0,0
local id1 = beholder:observe("EVENT", function() counter1 = counter1 + 1 end)
local id2 = beholder:observe("EVENT", function() counter2 = counter2 + 1 end)
local id1 = beholder.observe("EVENT", function() counter1 = counter1 + 1 end)
local id2 = beholder.observe("EVENT", function() counter2 = counter2 + 1 end)
beholder:trigger("EVENT")
beholder:trigger("EVENT")
beholder.trigger("EVENT")
beholder.trigger("EVENT")
assert_equal(counter1, 2)
assert_equal(counter2, 2)
beholder:stopObserving(id1)
beholder.stopObserving(id1)
beholder:trigger("EVENT")
beholder.trigger("EVENT")
assert_equal(counter1, 2)
assert_equal(counter2, 3)
beholder:stopObserving(id2)
beholder.stopObserving(id2)
beholder:trigger("EVENT")
beholder.trigger("EVENT")
assert_equal(counter1, 2)
assert_equal(counter2, 3)
@ -59,17 +59,17 @@ describe("Acceptance", function()
local enterPressed = false
local escapePressed = false
beholder:observe("KEYPRESS", function() counter = counter + 1 end)
beholder:observe("KEYPRESS", function(key) lastKey = key end)
beholder:observe("KEYPRESS", "enter", function() enterPressed = true end)
beholder.observe("KEYPRESS", function() counter = counter + 1 end)
beholder.observe("KEYPRESS", function(key) lastKey = key end)
beholder.observe("KEYPRESS", "enter", function() enterPressed = true end)
beholder:trigger("KEYPRESS", "space")
beholder.trigger("KEYPRESS", "space")
assert_equal(counter, 1)
assert_equal(lastKey, "space")
assert_false(enterPressed)
assert_false(escapePressed)
beholder:trigger("KEYPRESS", "enter")
beholder.trigger("KEYPRESS", "enter")
assert_equal(counter, 2)
assert_equal(lastKey, "enter")
assert_true(enterPressed)
@ -79,18 +79,18 @@ describe("Acceptance", function()
test("nil events", function()
local counter = 0
local id = beholder:observe(function(_, x) counter = counter + x end)
local id = beholder.observe(function(_, x) counter = counter + x end)
beholder:trigger("FOO", 1)
beholder:trigger("BAR", 2)
beholder.trigger("FOO", 1)
beholder.trigger("BAR", 2)
assert_equal(3, counter)
beholder:stopObserving(id)
beholder.stopObserving(id)
beholder:observe(function() counter = 10 end)
beholder.observe(function() counter = 10 end)
beholder:trigger()
beholder.trigger()
assert_equal(10, counter)
end)
@ -99,15 +99,15 @@ describe("Acceptance", function()
local even = 0
local uneven = 1
beholder:observe("EVEN", function(x) even = even + 2*x end)
beholder:observe("UNEVEN", function(x) uneven = uneven + 2*x end)
beholder.observe("EVEN", function(x) even = even + 2*x end)
beholder.observe("UNEVEN", function(x) uneven = uneven + 2*x end)
beholder:triggerAll(1)
beholder.triggerAll(1)
assert_equal(even, 2)
assert_equal(uneven, 3)
beholder:triggerAll(2)
beholder.triggerAll(2)
assert_equal(even, 6)
assert_equal(uneven, 7)

View File

@ -3,84 +3,77 @@ local beholder = require 'beholder'
describe("Unit", function()
before(function()
beholder:reset()
beholder.reset()
end)
describe(":observe", function()
it("checks self", function()
assert_error(function() beholder.observe("X", function() end) end)
end)
describe(".observe", function()
it("notices simple events so that trigger works", function()
local counter = 0
beholder:observe("EVENT", function() counter = counter + 1 end)
beholder:trigger("EVENT")
beholder.observe("EVENT", function() counter = counter + 1 end)
beholder.trigger("EVENT")
assert_equal(counter, 1)
end)
it("remembers if more than one action is associated to the same event", function()
local counter1, counter2 = 0,0
beholder:observe("EVENT", function() counter1 = counter1 + 1 end)
beholder:observe("EVENT", function() counter2 = counter2 + 1 end)
beholder:trigger("EVENT")
beholder.observe("EVENT", function() counter1 = counter1 + 1 end)
beholder.observe("EVENT", function() counter2 = counter2 + 1 end)
beholder.trigger("EVENT")
assert_equal(counter1, 1)
assert_equal(counter2, 1)
end)
it("allows observing composed events", function()
local counter = 0
beholder:observe("KEYPRESS", "start", function() counter = counter + 1 end)
beholder:trigger("KEYPRESS", "start")
beholder.observe("KEYPRESS", "start", function() counter = counter + 1 end)
beholder.trigger("KEYPRESS", "start")
assert_equal(counter, 1)
end)
it("observes all events with the nil event", function()
local counter = 0
beholder:observe(function(_,x) counter = counter + x end)
beholder:trigger("FOO", 1)
beholder:trigger("BAR", 2)
beholder.observe(function(_,x) counter = counter + x end)
beholder.trigger("FOO", 1)
beholder.trigger("BAR", 2)
assert_equal(3, counter)
end)
it("throws an error if called without at least one parameter", function()
assert_error(function() beholder:observe() end)
assert_error(function() beholder.observe() end)
end)
it("does not store hard references to variables", function()
local counter = 0
local x = {}
beholder:observe(x, function() counter = counter + 1 end)
beholder:triggerAll()
beholder.observe(x, function() counter = counter + 1 end)
beholder.triggerAll()
x = nil
collectgarbage("collect")
beholder:triggerAll()
beholder.triggerAll()
assert_equal(1, counter)
end)
end)
describe(":stopObserving", function()
it("checks self", function()
assert_error(function() beholder.stopObserving() end)
end)
describe(".stopObserving", function()
it("stops noticing events so trigger doesn't work any more", function()
local counter = 0
local id = beholder:observe("EVENT", function() counter = counter + 1 end)
beholder:trigger("EVENT")
beholder:stopObserving(id)
beholder:trigger("EVENT")
local id = beholder.observe("EVENT", function() counter = counter + 1 end)
beholder.trigger("EVENT")
beholder.stopObserving(id)
beholder.trigger("EVENT")
assert_equal(counter, 1)
end)
it("stops observing one id without disturbing the others", function()
local counter1, counter2 = 0,0
local id1 = beholder:observe("EVENT", function() counter1 = counter1 + 1 end)
beholder:observe("EVENT", function() counter2 = counter2 + 1 end)
beholder:trigger("EVENT")
local id1 = beholder.observe("EVENT", function() counter1 = counter1 + 1 end)
beholder.observe("EVENT", function() counter2 = counter2 + 1 end)
beholder.trigger("EVENT")
assert_equal(counter1, 1)
assert_equal(counter2, 1)
beholder:stopObserving(id1)
beholder:trigger("EVENT")
beholder.stopObserving(id1)
beholder.trigger("EVENT")
assert_equal(counter1, 1)
assert_equal(counter2, 2)
@ -90,95 +83,81 @@ describe("Unit", function()
it("passes parameters to the actions", function()
local counter = 0
beholder:observe("EVENT", function(x) counter = counter + x end)
beholder:trigger("EVENT", 1)
beholder.observe("EVENT", function(x) counter = counter + x end)
beholder.trigger("EVENT", 1)
assert_equal(counter, 1)
beholder:trigger("EVENT", 5)
beholder.trigger("EVENT", 5)
assert_equal(counter, 6)
end)
it("does not raise an error when stopping observing an inexisting event", function()
assert_not_error(function() beholder:stopObserving({}) end)
assert_not_error(function() beholder.stopObserving({}) end)
end)
it("returns false when no action was found for an id", function()
assert_equal(false, beholder:stopObserving({}))
assert_equal(false, beholder.stopObserving({}))
end)
it("returns true when an action was found and removed", function()
local id = beholder:observe("X", function() end)
assert_true(beholder:stopObserving(id))
local id = beholder.observe("X", function() end)
assert_true(beholder.stopObserving(id))
end)
end)
describe(":trigger", function()
it("checks self", function()
assert_error(function() beholder.trigger() end)
end)
describe(".trigger", function()
it("does not error on random stuff", function()
assert_not_error(function() beholder:trigger("FOO") end)
assert_not_error(function() beholder.trigger("FOO") end)
end)
it("returns false on events with no actions", function()
assert_equal(false, beholder:trigger("FOO"))
assert_equal(false, beholder.trigger("FOO"))
end)
it("returns false if there was a node with no actions", function()
beholder:observe("ONE","TWO", function() end)
assert_equal(false, beholder:trigger("ONE"))
beholder.observe("ONE","TWO", function() end)
assert_equal(false, beholder.trigger("ONE"))
end)
it("returns the number of actions executed", function()
beholder:observe("X", function() end)
beholder:observe("X", function() end)
assert_equal(2, beholder:trigger("X"))
beholder.observe("X", function() end)
beholder.observe("X", function() end)
assert_equal(2, beholder.trigger("X"))
end)
it("triggers callbacks within the nil event only", function()
local counter = 0
beholder:observe("X", function() counter = counter + 10 end)
beholder:observe(function() counter = counter + 5 end)
beholder.observe("X", function() counter = counter + 10 end)
beholder.observe(function() counter = counter + 5 end)
beholder:trigger()
beholder.trigger()
assert_equal(5, counter)
end)
end)
describe(":triggerAll", function()
it("checks self", function()
assert_error(function() beholder.triggerAll() end)
end)
describe(".triggerAll", function()
it("calls all registered callbacks", function()
local counter = 0
beholder:observe("X", function() counter = counter + 1 end)
beholder:triggerAll()
beholder.observe("X", function() counter = counter + 1 end)
beholder.triggerAll()
assert_equal(1, counter)
end)
it("passes parameters to callbacks", function()
local counter = 0
beholder:observe(function(x) counter = counter + x end)
beholder:triggerAll(2)
beholder.observe(function(x) counter = counter + x end)
beholder.triggerAll(2)
assert_equal(2, counter)
end)
it("returns false if no actions where found", function()
assert_false(beholder:triggerAll())
assert_false(beholder.triggerAll())
end)
it("returns the number of actions executed", function()
beholder:observe("X", function() end)
beholder:observe("Y", function() end)
assert_equal(2, beholder:triggerAll())
beholder.observe("X", function() end)
beholder.observe("Y", function() end)
assert_equal(2, beholder.triggerAll())
end)
end)
describe(":reset", function()
it("checks self", function()
assert_error(function() beholder.reset() end)
end)
end)
end)