moved mixins out of middleclass, and into PÄSSION

This commit is contained in:
kikito 2010-10-04 20:30:14 +02:00
parent 3d4cc7b5b8
commit 9fd4ccee72
8 changed files with 0 additions and 720 deletions

View File

@ -1,172 +0,0 @@
-----------------------------------------------------------------------------------
-- Beholder.lua
-- Enrique García ( enrique.garcia.cota [AT] gmail [DOT] com ) - 4 Mar 2010
-- Small framework for event observers
-----------------------------------------------------------------------------------
assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it before using Callbacks')
assert(Sender~=nil, 'The Beholder module requires the Sender module in order to work. Please require Sender before requiring Beholder')
--[[ Usage:
require 'middleclass.mixins.Beholder' -- or 'middleclass.init'
MyClass = class('MyClass')
MyClass:includes(Beholder)
function MyClass:foo(x,y) ... end
local obj = MyClass:new()
-- when the 'newgame' event is fired, call method foo with parameters 100 and 200
obj:observe('newgame', 'foo', 100, 200)
-- you can add more than one callbacks to the same event:
obj:observe('newgame', 'foo', 300, 400)
-- alternatively, use a function
obj:observe('endgame', function(myself) myself.blah = 0 end)
-- trigger the event:
Beholder.trigger('newgame')
-- stop observing an event:
obj:stopObserving('newgame')
]]
--------------------------------
-- PRIVATE NODE CLASS
--------------------------------
local Node = class('Node')
function Node:initialize()
super.initialize(self)
self.children = {}
self.objects=setmetatable({}, {__mode='k'})
end
function Node:getOrCreateChild(key)
local child = self.children[key]
if child == nil then
child = Node:new()
child.parent = self
self.children[key] = child
end
return child
end
function Node:getOrCreateDescendant(key)
if type(key) ~= 'table' then return self:getOrCreateChild(key) end
local node = self
for _,v in ipairs(key) do node = node:getOrCreateChild(v) end
return node
end
function Node:getDescendant(key)
if type(key) ~= 'table' then return self.children[key] end
local node = self
for _,v in ipairs(key) do
node = node.children[v]
if node == nil then return nil end
end
return node
end
function Node:getOrRegisterObject(object)
self.objects[object] = self.objects[object] or {}
return self.objects[object]
end
function Node:addAction(object, method, ...)
local actions = self:getOrRegisterObject(object)
table.insert(actions, { method = method, params = {...} })
end
function Node:removeAction(object, method)
if method == nil then self.objects[object] = nil end
local actions = self.objects[object]
if actions==nil then return end
local index = 1
for i,v in ipairs(actions) do
if v == method then index = i break end
end
if(index~=nil) then table.remove(actions, index) end
end
-- Private variable storing the list of event callbacks that can be used
--[[ structure:
_root = { -- root node
children =
'a' = { -- root->a node
children = {
'b' = { -- root->a->b node
children = {},
objects = { -- list of objects registered on node root->a->b
obj1 = { -- list of actions to perform on object1
{ method = 'method1', params = {} },
{ method = 'method2', params = {1,2}}
}
}
}
},
objects = {} -- node root->a does not have any object registered
}
'b' = { -- root->b node
children = {}, -- no children nor objects
objects = {}
}
}
}
]]
local _root = Node:new()
-- The Beholder module
Beholder = {}
function Beholder:observe(eventId, methodOrName, ...)
assert(self~=nil, "self is nil. invoke object:observe instead of object.observe")
assert(eventId~=nil, "eventId can not be nil")
local t = type(methodOrName)
assert(t=='string' or t=='function', 'methodOrName must be a function or string')
local node = _root:getOrCreateDescendant(eventId)
node:addAction(self, methodOrName, ...)
end
function Beholder:stopObserving(eventId, methodOrName)
local node = _root:getDescendant(eventId)
if node==nil then return end
node:removeAction(self, methodOrName)
end
--[[ Triggers events
Usage:
Beholder.trigger('passion.update', dt)
All objects that are "observing" passion.update events will get their associated actions called.
]]
function Beholder.trigger(eventId, ...)
local node = _root:getDescendant(eventId)
if node==nil then return end
for object,actions in pairs(node.objects) do
for _,action in ipairs(actions) do
local params = {}
for k,v in ipairs(action.params) do params[k] = v end
for _,v in ipairs({...}) do table.insert(params, v) end
Sender.send(object, action.method, unpack(params))
end
end
end

View File

@ -1,221 +0,0 @@
-----------------------------------------------------------------------------------
-- Callbacks.lua
-- Enrique García ( enrique.garcia.cota [AT] gmail [DOT] com )
-- Mixin that adds callbacks support (i.e. beforeXXX or afterYYY) to classes)
-----------------------------------------------------------------------------------
assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it before using Callbacks')
assert(Sender~=nil, 'The Callbacks module requires the Sender module in order to work. Please require Sender before requiring Callbacks')
--[[ Usage:
MyClass = class('MyClass')
MyClass:include(Callbacks)
MyClass:addCallbacksAround('foo') -- this defines methods 'beforeFoo' and 'afterFoo'
MyClass:beforeFoo('bar') -- can use either method names or functions
MyClass:afterFoo(function() print('baz') end)
function MyClass:foo() print 'foo' end
function MyClass:bar() print 'bar' end
local obj = MyClass:new()
obj:foo() -- prints 'bar foo baz'
]]
--------------------------------
-- PRIVATE STUFF
--------------------------------
--[[ holds all the callbacks entries.
callback entries are just lists of methods to be called before / after some other method is called
_callbackEntries = {
Actor = {
beforeUpdate = { methods = {m1, m2, m3 } }, -- m1, m2, m3 & m4 can be method names or functions
afterUpdate = { methods = { 'm4' } },
update = {
before = 'beforeUpdate',
after = 'afterUpdate'
}
}
}
]]
local _callbackEntries = setmetatable({}, {__mode = "k"}) -- weak table
-- cache for not re-creating methods every time they are needed
local _methodCache = setmetatable({}, {__mode = "k"})
-- private class methods
local _getCallbackEntry
local function _getCallbackEntry(theClass, callbackName)
if theClass == nil or callbackName == nil then return nil end
if _callbackEntries[theClass] ~= nil and _callbackEntries[theClass][callbackName] ~= nil then
return _callbackEntries[theClass][callbackName]
end
return _getCallbackEntry(theClass.superclass, callbackName)
end
-- creates one of the "level 2" entries on callbacks, like beforeUpdate or afterupdate, above
local function _getOrCreateCallbackEntry(theClass, callbackName)
if not theClass or not callbackName then return {} end
_callbackEntries[theClass] = _callbackEntries[theClass] or setmetatable({}, {__mode = "k"})
local classEntries = _callbackEntries[theClass]
classEntries[callbackName] = classEntries[callbackName] or setmetatable({ methods={} }, {__mode = "k"})
return classEntries[callbackName]
end
-- returns all the methods that should be called when a callback is invoked, including superclasses
local function _getCallbackEntryChainMethods(theClass, callbackName)
if theClass==nil then return {} end
local methods = _getOrCreateCallbackEntry(theClass, callbackName).methods
local superMethods = _getCallbackEntryChainMethods(theClass.superclass, callbackName)
local result = {}
for i,method in ipairs(methods) do result[i]=method end
for _,method in ipairs(superMethods) do table.insert(result, method) end
return result
end
-- defines a callback method. These methods are used to add "methods" to the callback.
-- for example, after calling _defineCallbackMethod(Actor, 'afterUpdate') you can then do
-- Actor:afterUpdate('removeFromList', 'dance', function(actor) actor:doSomething() end)
local function _defineCallbackMethod(theClass, callbackName)
if callbackName == nil then return nil end
assert(rawget(theClass.__classDict,callbackName)==nil, "Could not define " .. theClass.name .. '.' .. callbackName .. ": already defined")
theClass[callbackName] = function(theClass, ...)
local methods = {...}
local existingMethods = _getOrCreateCallbackEntry(theClass, callbackName).methods
for _,method in ipairs(methods) do
table.insert(existingMethods, method)
end
end
_getOrCreateCallbackEntry(theClass, callbackName)
return theClass[callbackName]
end
-- private instance methods
-- given a callback entry, obtain all the methods that must be called for that callback and execute them
local function _runCallbackChain(object, entry, before_or_after)
if entry == nil then return true end
callbackName = entry[before_or_after]
if callbackName==nil then return true end
local methods = _getCallbackEntryChainMethods(object.class, callbackName)
for _,method in ipairs(methods) do
if Sender.send(object, method) == false then return false end
end
return true
end
-- given a class and a method, this returns a new version of that method that invokes callbacks
-- uses a cache for not calculating the methods every time
function _getChainedMethod(theClass, methodName, method)
local entry = _getCallbackEntry(theClass, methodName)
if(entry==nil) then return method end
_methodCache[theClass] = _methodCache[theClass] or setmetatable({}, {__mode = "k"})
local classCache = _methodCache[theClass]
local chainedMethod = classCache[methodName]
if chainedMethod == nil then
chainedMethod = function(self, ...)
if _runCallbackChain(self, entry, 'before') == false then return false end
local result = method(self, ...)
if _runCallbackChain(self, entry, 'after') == false then return false end
return result
end
classCache[methodName] = chainedMethod
end
return chainedMethod
end
-- helper function used by addCallbacksBefore, after and around
function _addCallbacks(theClass, before_or_after, methodName, callbackMethodName)
assert(type(methodName)=='string', 'methodName must be a string')
assert(before_or_after == 'before' or before_or_after == 'after', 'Parameter must be "before" or "after"')
local entry = _getOrCreateCallbackEntry(theClass, methodName)
assert(entry[before_or_after] == nil, 'The "' .. tostring(before_or_after) .. '" callback is already defined as "' .. tostring(entry[before_or_after]) .. '". Use that callback method instead or adding a new one' )
callbackMethodName = callbackMethodName or before_or_after .. methodName:gsub("^%l", string.upper)
_defineCallbackMethod(theClass, callbackMethodName)
entry[before_or_after]= callbackMethodName
end
--------------------------------
-- PUBLIC STUFF
--------------------------------
Callbacks = {}
function Callbacks:included(theClass)
if includes(Callbacks, theClass) then return end
-- Modify the instances __index metamethod so it adds callback chains to methods with callback entries
local oldNew = theClass.new
theClass.new = function(theClass, ...)
local instance = oldNew(theClass, ...)
local prevIndex = getmetatable(instance).__index
local tIndex = type(prevIndex)
setmetatable(instance, {
__index = function(instance, methodName)
local method
if tIndex == 'table' then method = prevIndex[methodName]
elseif tIndex == 'function' then method = prevIndex(instance, methodName)
end
if type(method) ~= 'function' then return method end
return _getChainedMethod(theClass, methodName, method)
end
})
-- special treatment for afterInitialize callbacks
local entry = _getCallbackEntry(theClass, 'initialize')
if _runCallbackChain(instance, entry, 'after') == false then return false end
return instance
end
end
-- usage: Actor:addCallbacksBefore('update')
-- callbackMethodName is optional, defaulting to 'beforeUpdate'
function Callbacks.addCallbacksBefore(theClass, methodName, callbackMethodName)
_addCallbacks(theClass, 'before', methodName, callbackMethodName)
end
-- usage: Actor:addCallbacksAfter('initialize')
-- callbackMethodName is optional, defaulting to 'afterInitialize'
function Callbacks.addCallbacksAfter(theClass, methodName, callbackMethodName)
_addCallbacks(theClass, 'after', methodName, callbackMethodName)
end
-- usage: Actor:addCallbackAround('update')
-- before & afterCallbackMethodName are optional, defaulting to 'beforeUpdate' and 'afterUpdate'
function Callbacks.addCallbacksAround(theClass, methodName, beforeCallbackMethodName, afterCallbackMethodName)
_addCallbacks(theClass, 'before', methodName, beforeCallbackMethodName)
_addCallbacks(theClass, 'after', methodName, afterCallbackMethodName)
end

View File

@ -1,38 +0,0 @@
-----------------------------------------------------------------------------------
-- GetterSetter.lua
-- Enrique García ( enrique.garcia.cota [AT] gmail [DOT] com ) - 11 Aug 2010
-- Small mixin for classes with getters and setters
-----------------------------------------------------------------------------------
--[[ Usage:
require 'middleclass.mixins.GetterSetter' -- or 'middleclass.init'
MyClass = class('MyClass')
MyClass:include(GetterSetter)
MyClass:getter('name', 'pete') -- default value
MyClass:setter('age')
MyClass:getterSetter('color', 'blue') -- default value
]]
assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it before using GetterSetter')
GetterSetter = {}
function GetterSetter.getterFor(theClass, attr) return 'get' .. attr:gsub("^%l", string.upper) end
function GetterSetter.setterFor(theClass, attr) return 'set' .. attr:gsub("^%l", string.upper) end
function GetterSetter.getter(theClass, attributeName, defaultValue)
theClass[theClass:getterFor(attributeName)] = function(self)
if(self[attributeName]~=nil) then return self[attributeName] end
return defaultValue
end
end
function GetterSetter.setter(theClass, attributeName)
theClass[theClass:setterFor(attributeName)] = function(self, value) self[attributeName] = value end
end
function GetterSetter.getterSetter(theClass, attributeName, defaultValue)
theClass:getter(attributeName, defaultValue)
theClass:setter(attributeName)
end

View File

@ -1,38 +0,0 @@
-----------------------------------------------------------------------------------
-- Sender.lua
-- Enrique García ( enrique.garcia.cota [AT] gmail [DOT] com ) - 4 Mar 2010
-- Helper function that simplifies method invocation via method names or functions
-----------------------------------------------------------------------------------
--[[ Usage:
require 'middleclass.mixins.Sender' -- or 'middleclass.init'
MyClass = class('MyClass')
MyClass:includes(Sender)
function MyClass:foo(x,y) print('foo executed with params', x, y) end
local obj = MyClass:new()
obj:send('foo', 1,2) -- foo executed with params 1 2
obj:send( function(self, x, y)
print('nameless function executed with params', x, y)
, 3, 4) -- nameless function executed with params 3, 4
Note that the function first parameter will allways be self
]]
assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it before using Beholder')
Sender = {
send = function(self, methodOrName, ...)
local method = methodOrName
if(type(methodOrName)=='string') then method = self[methodOrName] end
assert(type(method)=='function', 'Sender:send requires a function or function name')
return method(self, ...)
end
}

View File

@ -1,77 +0,0 @@
require('middleclass.init')
context( 'Beholder', function()
context('When included by a class', function()
local MyClass = class('MyClass')
function MyClass:initialize()
super.initialize(self)
self.counter = 0
end
function MyClass:count(increment)
self.counter = self.counter + increment
end
function MyClass:count2(increment)
self.counter = self.counter + increment
end
test('Should not throw errors', function()
assert_not_error(function() MyClass:include(Beholder) end)
end)
context('When starting observing', function()
test('It should allow calling of methods by name, with parameters', function()
local obj = MyClass:new()
obj:observe('event1', 'count', 1)
Beholder.trigger('event1')
assert_equal(obj.counter, 1)
end)
test('It should allow calling of methods by function, with parameters', function()
local obj = MyClass:new()
obj:observe('event2', function(myself, increment) myself:count(increment) end, 1)
Beholder.trigger('event2')
assert_equal(obj.counter, 1)
end)
test('It should allow chaining of calls', function()
local obj = MyClass:new()
obj:observe('event3', 'count', 1)
obj:observe('event3', 'count', 1)
Beholder.trigger('event3')
assert_equal(obj.counter, 2)
end)
end)
context('When stopping observing', function()
test('It should allow completely stopping observing one event', function()
local obj = MyClass:new()
obj:observe('event4', 'count', 1)
Beholder.trigger('event4')
obj:stopObserving('event4')
Beholder.trigger('event4')
assert_equal(obj.counter, 1)
end)
test('It should allow stopping observing individual actions on one event', function()
local obj = MyClass:new()
obj:observe('event5', 'count', 1)
obj:observe('event5', 'count2', 1)
Beholder.trigger('event5')
obj:stopObserving('event5', 'count')
Beholder.trigger('event5')
assert_equal(obj.counter, 3)
end)
end)
end)
end)

View File

@ -1,104 +0,0 @@
require('middleclass.init')
context( 'Callbacks', function()
local A
before(function()
A = class('A')
function A:initialize()
super.initialize(self)
self.calls = {}
end
end)
local function defineRegularMethods(theClass)
function theClass:foo() table.insert(self.calls, 'foo') end
function theClass:bar() table.insert(self.calls, 'bar') end
function theClass:baz() table.insert(self.calls, 'baz') end
end
local function addCallbacks(theClass)
theClass:include(Callbacks)
theClass:addCallbacksAround('bar')
theClass:beforeBar('foo')
theClass:afterBar( function(myself) myself:baz() end )
end
local function testInstance(theClass)
local obj = theClass:new()
obj:bar()
assert_equal(obj.calls[1], 'foo')
assert_equal(obj.calls[2], 'bar')
assert_equal(obj.calls[3], 'baz')
end
test('Should work when declared before the methods', function()
addCallbacks(A)
defineRegularMethods(A)
testInstance(A)
end)
test('Should work when declared after the methods', function()
defineRegularMethods(A)
addCallbacks(A)
testInstance(A)
end)
context('When subclassing', function()
local B
before(function()
B = class('B', A)
end)
test('The subclass should include Callbacks', function()
A:include(Callbacks)
assert_true(includes(Callbacks, B))
end)
test('Callbacks in subclasses should work on inherited methods, even if declared before', function()
addCallbacks(B)
defineRegularMethods(A)
testInstance(B)
end)
test('Callbacks in subclasses should work on inherited methods when declared after', function()
defineRegularMethods(A)
addCallbacks(B)
testInstance(B)
end)
test('Callbacks should be conserved in subclasses', function()
addCallbacks(A)
defineRegularMethods(A)
testInstance(B)
end)
test('Callbacks in subclasses can be used as well as in superclasses', function()
addCallbacks(A)
defineRegularMethods(A)
addCallbacks(B)
local obj = B:new()
obj:bar()
assert_equal(obj.calls[1], 'foo')
assert_equal(obj.calls[2], 'foo')
assert_equal(obj.calls[3], 'bar')
assert_equal(obj.calls[4], 'baz')
assert_equal(obj.calls[5], 'baz')
end)
end)
context('When creating an instance', function()
test('afterInitialize should be called', function()
defineRegularMethods(A)
A:include(Callbacks)
A:addCallbacksAfter('initialize')
A:afterInitialize('foo')
assert_equal(A:new().calls[1], 'foo')
end)
end)
end)

View File

@ -1,38 +0,0 @@
require('middleclass.init')
context( 'GetterSetter', function()
context('When included by a class', function()
local MyClass = class('MyClass')
test('Should not throw errors', function()
assert_not_error(function() MyClass:include(GetterSetter) end)
end)
test('It should include the 3 main methods on the class', function()
MyClass:getter('name', 'pete')
MyClass:setter('age')
MyClass:getterSetter('color', 'blue')
local obj = MyClass:new()
assert_equal(obj:getName(), 'pete')
obj.name = 'john'
assert_equal(obj:getName(), 'john')
obj:setAge(14)
assert_equal(obj.age, 14)
assert_equal(obj:getColor(), 'blue')
obj:setColor('fucsia')
assert_equal(obj:getColor(), 'fucsia')
end)
test('It should include the 2 secondary methods on the class', function()
assert_equal(MyClass:getterFor('language'), 'getLanguage')
assert_equal(MyClass:setterFor('language'), 'setLanguage')
end)
end)
end)

View File

@ -1,32 +0,0 @@
require('middleclass.init')
context( 'Sender', function()
local MyClass = class('MyClass')
MyClass:include(Sender)
function MyClass:foo(x,y) return 'foo ' .. tostring(x) .. ', ' .. tostring(y) end
function MyClass:testSelf() return instanceOf(MyClass, self) end
local obj = MyClass:new()
test('It should work with method names', function()
assert_equal(obj:send('foo', 1, 2), 'foo 1, 2')
end)
test('It should work with implicit functions', function()
assert_equal(
obj:send(
function(self, x, y) return 'bar '.. tostring(x) .. ', ' .. tostring(y) end,
3,
4
),
'bar 3, 4'
)
end)
test('It should use self as implicit parameter in all cases', function()
assert_true(obj:send('testSelf'))
assert_true(obj:send(MyClass.testSelf))
end)
end)