Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Callbacks Refactoring

  • Loading branch information...
commit 71f55dc53a47e79435e01461bc0c751d28d68e97 1 parent ab24e10
Enrique García authored

Showing 1 changed file with 62 additions and 53 deletions. Show diff stats Hide diff stats

  1. +62 53 Callbacks.lua
115 Callbacks.lua
@@ -57,6 +57,10 @@ assert(Invoker~=nil, 'Invoker not detected. Please require it before requiring C
57 57 local _entries = setmetatable({}, {__mode = "k"}) -- weak table
58 58 local _methodCache = setmetatable({}, {__mode = "k"}) -- weak table
59 59
  60 +local _metamethods = { -- all metamethods except __index
  61 + '__add', '__call', '__concat', '__div', '__le', '__lt', '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
  62 +}
  63 +
60 64 -- private class methods
61 65
62 66 local function _getEntry(theClass, methodName)
@@ -117,6 +121,19 @@ function _invokeActions(instance, actions)
117 121 end
118 122 end
119 123
  124 +local function _createCallcackizedMethod(methodName)
  125 + return function(instance, ...)
  126 + local before, after = _getActions(instance, methodName)
  127 +
  128 + if _invokeActions(instance, before) == false then return false end
  129 +
  130 + local result = { instance[methodName .. 'WithoutCallbacks'](instance, ...) }
  131 +
  132 + if _invokeActions(instance, after) == false then return false end
  133 + return unpack(result)
  134 + end
  135 +end
  136 +
120 137 -- returns a function that executes "method", but with before and after actions
121 138 -- it also does some optimizations. It uses a cache, and returns the method itself when it
122 139 -- doesn't have any entries on the entry list (hence no callbacks)
@@ -125,78 +142,78 @@ local function _callbackizeMethod(theClass, methodName, method)
125 142 if type(method)~='function' or not _hasEntry(theClass, methodName) then return method end
126 143
127 144 _methodCache[theClass] = _methodCache[theClass] or {}
  145 + _methodCache[theClass][method] = _methodCache[theClass][method] or _createCallcackizedMethod(methodName)
128 146
129   - _methodCache[theClass][method] = _methodCache[theClass][method] or function(instance, ...)
130   - local before, after = _getActions(instance, methodName)
  147 + return _methodCache[theClass][method]
  148 +end
131 149
132   - if _invokeActions(instance, before) == false then return false end
  150 +local function _assertFunctionOrTable(classDict)
  151 + local tcd = type(classDict)
  152 + assert(tcd == 'function' or tcd == 'table', 'invalid type for an index; must be function or table, was ' .. tcd)
  153 +end
133 154
134   - local result = { instance[methodName .. 'WithoutCallbacks'](instance, ...) }
  155 +local function _createIndexFunction(theClass, methodName, classDictFunction)
  156 + return function(instance, methodName)
  157 + local method = classDictFunction(instance, methodName)
135 158
136   - if _invokeActions(instance, after) == false then return false end
137   - return unpack(result)
  159 + if method then return _callbackizeMethod(theClass, methodName, method) end
  160 +
  161 + -- If method not found, test if methoName ends in "WithoutCallbacks".
  162 + -- If it does yes, return the method without callbacks
  163 + methodName = methodName:match('(.+)WithoutCallbacks')
  164 + if methodName ~= nil then return classDictFunction(instance, methodName) end
138 165 end
139   -
140   - return _methodCache[theClass][method]
141 166 end
142 167
143   --- modifies a class so:
144   --- * Its instances return callbackized versions of their methods
145   --- * But they returns the un-callbackized version of method 'foo' when asked for 'fooWithoutCallbacks'
146   -local function _changeClassDict(theClass)
147   -
148   - -- throw an error when attempting to override an already-overriden new method.
149   - -- if theClass is the class that originally implemented Callbacks, (not a subclass of it)
150   - -- and it has a non-standard implementation of new, then throw the error.
151   - assert( includes(Callbacks, theClass.superclass) or
152   - theClass.allocate == Object.allocate,
153   - "Could not override the allocate method twice. Include Callbacks before modifying the 'allocate' method on " .. tostring(theclass) )
  168 +local function _buildClassDictFunction(classDict)
  169 + local classDictFunction = classDict
  170 + if type(classDict) == 'table' then classDictFunction = function(_, x) return classDict[x] end end
  171 + return classDictFunction
  172 +end
154 173
  174 +local function _createInstanceDict(theClass)
155 175 local classDict = theClass.__classDict
156   - local tcd = type(classDict)
157   - assert(tcd == 'function' or tcd == 'table', 'invalid type for an index; must be function or table, was ' .. tostring(tcd))
158   -
159   - -- aux function used to look on the class index. Changes depending on whether classIndex is a table or function
160   - local searchOnClassIndex = tdc == 'function' and classIndex or function(_, x) return classDict[x] end
  176 + _assertFunctionOrTable(classDict)
161 177
162 178 -- a copy of classDict, with a modified __index that adds/removes callbacks when needed
163 179 local instanceDict = {}
164   -
165   - for k,v in pairs(classDict) do instanceDict[k] = v end
166   -
167   - instanceDict.__index = function(instance, methodName)
168   - -- try to obtain method normally
169   - local method = searchOnClassIndex(instance, methodName)
170   -
171   - -- if method found, return it callbackized
172   - if method ~= nil then return _callbackizeMethod(theClass, methodName, method) end
173   -
174   - -- if method not found, test if methoName ends in "WithoutCallbacks". If yes, return the method without callbacks
175   - methodName = methodName:match('(.+)WithoutCallbacks')
176   - if methodName ~= nil then return searchOnClassIndex(instance, methodName) end
177   - end
  180 + for k,v in pairs(_metamethods) do instanceDict[k] = v end
  181 + instanceDict.__index = _createIndexFunction(theClass, methodName, _buildClassDictFunction(classDict))
  182 + return instanceDict
  183 +end
178 184
179   - -- modify theClass:new so instances use callbacks when needed.
  185 +local function _modifyAllocateMethod(theClass)
180 186 local oldAllocate = theClass.allocate
181 187 function theClass.allocate(theClass, ...)
182   - return setmetatable(oldAllocate(theClass, ...), instanceDict) -- using instanceDict instead of classDict here
  188 + return setmetatable(oldAllocate(theClass, ...), _createInstanceDict(theClass))
183 189 end
  190 +end
184 191
  192 +local function _modifySubclassMethod(theClass)
  193 + local prevSubclass = theClass.subclass
  194 + theClass.subclass = function(aClass, name, ...)
  195 + local theSubClass = prevSubclass(aClass, name, ...)
  196 + _modifyAllocateMethod(theSubClass)
  197 + return theSubClass
  198 + end
185 199 end
186 200
187 201
  202 +function _assertFunctionOrString(callback)
  203 + local tCallback = type(callback)
  204 + assert(tCallback == 'string' or tCallback == 'function', 'callback must be a method name or a function')
  205 +end
  206 +
188 207 -- adds callbacks to a method. Used by addCallbacksBefore and addCallbacksAfter, below
189 208 local function _addCallback( theClass, beforeOrAfter, methodName, callback, ...)
190 209 assert(type(methodName)=='string', 'methodName must be a string')
191   - local tCallback = type(callback)
192   - assert(tCallback == 'string' or tCallback == 'function', 'callback must be a method name or a function')
  210 + _assertFunctionOrString(callback)
193 211
194 212 local entry = _getOrCreateEntry(theClass, methodName)
195 213
196 214 table.insert(entry[beforeOrAfter], {method = callback, params = {...}})
197 215 end
198 216
199   -
200 217 --------------------------------
201 218 -- PUBLIC STUFF
202 219 --------------------------------
@@ -206,16 +223,8 @@ Callbacks = {}
206 223 function Callbacks:included(theClass)
207 224 if includes(Callbacks, theClass) then return end
208 225
209   - -- change how __index works on the class itself
210   - _changeClassDict(theClass)
211   -
212   - -- change how __index works on on subclasses
213   - local prevSubclass = theClass.subclass
214   - theClass.subclass = function(aClass, name, ...)
215   - local theSubClass = prevSubclass(aClass, name, ...)
216   - _changeClassDict(theSubClass)
217   - return theSubClass
218   - end
  226 + _modifyAllocateMethod(theClass)
  227 + _modifySubclassMethod(theClass)
219 228
220 229 end
221 230

0 comments on commit 71f55dc

Please sign in to comment.
Something went wrong with that request. Please try again.