mirror of
https://github.com/kikito/middleclass.git
synced 2024-10-05 23:24:17 +00:00
removed getterSetters from main MiddleClass (use a mixin instead). Added destroy. Fixed bug with destroying stateful objects
This commit is contained in:
parent
bf24742c9c
commit
64f2d89281
49
MiddleClass.lua
Normal file → Executable file
49
MiddleClass.lua
Normal file → Executable file
@ -7,9 +7,9 @@
|
||||
local _classes = setmetatable({}, {__mode = "k"}) -- weak table storing references to all declared _classes
|
||||
|
||||
-- The 'Object' class
|
||||
Object = { name = "Object" }
|
||||
|
||||
_classes[Object]=Object -- adds Object to the list of _classes
|
||||
Object = { name = "Object" }
|
||||
|
||||
_classes[Object]=Object -- adds Object to the list of _classes
|
||||
|
||||
-- creates a new instance
|
||||
Object.new = function(theClass, ...)
|
||||
@ -44,8 +44,8 @@ Object.subclass = function(theClass, name)
|
||||
-- FIXME add support for __index method here
|
||||
__newindex = function(_, methodName, method) -- when adding new methods, include a "super" function
|
||||
if type(method) == 'function' then
|
||||
local fenv = getfenv(method)
|
||||
local newenv = setmetatable( {super = theClass.__classDict}, {__index = fenv, __newindex = fenv} )
|
||||
local fenv = getfenv(method)
|
||||
local newenv = setmetatable( {super = theClass.__classDict}, {__index = fenv, __newindex = fenv} )
|
||||
setfenv( method, newenv )
|
||||
end
|
||||
rawset(classDict, methodName, method)
|
||||
@ -58,6 +58,8 @@ Object.subclass = function(theClass, name)
|
||||
|
||||
_classes[theSubclass]=theSubclass --registers the new class on the list of _classes
|
||||
|
||||
theClass:subclassed(theSubclass) -- hook method. By default it does nothing
|
||||
|
||||
return theSubclass
|
||||
end
|
||||
|
||||
@ -72,49 +74,34 @@ Object.includes = function(theClass, module, ... )
|
||||
if type(module.included)=="function" then module:included(theClass, ... ) end
|
||||
end
|
||||
|
||||
-- root of initialize and __tostring methods
|
||||
-- built-in methods
|
||||
Object.__classDict = {
|
||||
initialize = function(instance, ...) end, -- end of the initialize() call chain
|
||||
__tostring = function(instance) return ("instance of ".. instance.class.name) end
|
||||
initialize = function(instance, ...) end, -- empty method
|
||||
destroy = function(instance) end, -- empty method
|
||||
__tostring = function(instance) return ("instance of ".. instance.class.name) end,
|
||||
subclassed = function(theClass, other) end -- empty method
|
||||
}
|
||||
|
||||
|
||||
-- This allows doing tostring(obj) and Object() instead of Object:new()
|
||||
setmetatable(Object, { __index = Object.__classDict, __newindex = Object.__classDict,
|
||||
__tostring = function() return ("class Object") end,
|
||||
__call = Object.new
|
||||
})
|
||||
|
||||
-- Getter/Setter related methods
|
||||
function Object.getterFor(theClass, attr) return 'get' .. attr:gsub("^%l", string.upper) end
|
||||
function Object.setterFor(theClass, attr) return 'set' .. attr:gsub("^%l", string.upper) end
|
||||
function Object.getter(theClass, attributeName, defaultValue)
|
||||
theClass[theClass:getterFor(attributeName)] = function(self)
|
||||
if(self[attributeName]~=nil) then return self[attributeName] end
|
||||
return defaultValue
|
||||
end
|
||||
end
|
||||
function Object.setter(theClass, attributeName)
|
||||
theClass[theClass:setterFor(attributeName)] = function(self, value) self[attributeName] = value end
|
||||
end
|
||||
function Object.getterSetter(theClass, attributeName, defaultValue)
|
||||
theClass:getter(attributeName, defaultValue)
|
||||
theClass:setter(attributeName)
|
||||
end
|
||||
|
||||
-- Returns true if aClass is a subclass of other, false otherwise
|
||||
function subclassOf(other, aClass)
|
||||
function subclassOf(other, aClass)
|
||||
if aClass == nil or other==nil then return false end
|
||||
if aClass.superclass==nil then return false end -- aClass is Object, or a non-class
|
||||
return aClass.superclass == other or subclassOf(other, aClass.superclass)
|
||||
end
|
||||
|
||||
-- Returns true if obj is an instance of aClass (or one of its subclasses) false otherwise
|
||||
function instanceOf(aClass, obj)
|
||||
if obj==nil or _classes[aClass]==nil or _classes[obj.class]==nil then return false end
|
||||
if obj.class==aClass then return true end
|
||||
function instanceOf(aClass, obj)
|
||||
if obj==nil or _classes[aClass]==nil or _classes[obj.class]==nil then return false end
|
||||
if obj.class==aClass then return true end
|
||||
return subclassOf(aClass, obj.class)
|
||||
end
|
||||
|
||||
|
||||
-- Creates a new class named 'name'. It uses baseClass as the parent (Object if none specified)
|
||||
function class(name, baseClass)
|
||||
baseClass = baseClass or Object
|
||||
|
350
MindState.lua
Normal file → Executable file
350
MindState.lua
Normal file → Executable file
@ -4,8 +4,8 @@
|
||||
-- Based on Unrealscript's stateful objects
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it before using MindState')
|
||||
|
||||
assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it before using MindState')
|
||||
|
||||
--[[ StatefulObject declaration
|
||||
* Stateful classes have a list of states (accesible through class.states).
|
||||
* When a method is invoked on an instance of such classes, it is first looked up on the class current state (accesible through class.currentState)
|
||||
@ -14,39 +14,61 @@ assert(Object~=nil and class~=nil, 'MiddleClass not detected. Please require it
|
||||
]]
|
||||
StatefulObject = class('StatefulObject')
|
||||
|
||||
StatefulObject.states = {} -- the root state list
|
||||
|
||||
------------------------------------
|
||||
-- PRIVATE ATTRIBUTES AND METHODS
|
||||
------------------------------------
|
||||
local _private = setmetatable({}, {__mode = "k"}) -- weak table storing private references
|
||||
|
||||
-- helper function used to call state callbacks (enterState, exitState, etc)
|
||||
local _invokeCallback = function(self, state, callbackName, ... )
|
||||
if state==nil then return end
|
||||
local callback = state[callbackName]
|
||||
if(type(callback)=='function') then callback(self, ...) end
|
||||
end
|
||||
|
||||
local _getStack=function(self)
|
||||
local stack = _private[self].stateStack
|
||||
assert(stack~=nil, "Could not find the stack for the object. Make sure you invoked super.initialize(self) on the constructor.")
|
||||
return stack
|
||||
end
|
||||
|
||||
-- These methods will not be overriden by the states.
|
||||
local _ignoredMethods = {
|
||||
states=1, initialize=1,
|
||||
gotoState=1, pushState=1, popState=1, popAllStates=1, getCurrentState=1, isInState=1,
|
||||
enterState=1, exitState=1, pushedState=1, poppedState=1, pausedState=1, continuedState=1,
|
||||
addState=1, subclass=1, includes=1
|
||||
}
|
||||
|
||||
StatefulObject.states = {} -- the root state list
|
||||
|
||||
------------------------------------
|
||||
-- PRIVATE ATTRIBUTES AND METHODS
|
||||
------------------------------------
|
||||
local _private = setmetatable({}, {__mode = "k"}) -- weak table storing private references
|
||||
|
||||
-- helper function used to call state callbacks (enterState, exitState, etc)
|
||||
local _invokeCallback = function(self, state, callbackName, ... )
|
||||
if state==nil then return end
|
||||
local callback = state[callbackName]
|
||||
if(type(callback)=='function') then callback(self, ...) end
|
||||
end
|
||||
|
||||
local _getStack=function(self)
|
||||
local stack = _private[self].stateStack
|
||||
assert(stack~=nil, "Could not find the stack for the object. Make sure you invoked super.initialize(self) on the constructor.")
|
||||
return stack
|
||||
end
|
||||
|
||||
-- These methods will not be overriden by the states.
|
||||
local _ignoredMethods = {
|
||||
states=1, initialize=1,
|
||||
gotoState=1, pushState=1, popState=1, popAllStates=1, getCurrentState=1, isInState=1,
|
||||
enterState=1, exitState=1, pushedState=1, poppedState=1, pausedState=1, continuedState=1,
|
||||
addState=1, subclass=1, includes=1, destroy=1
|
||||
}
|
||||
|
||||
local _prevSubclass = StatefulObject.subclass -- previous way of creating subclasses (used to redefine subclass itself)
|
||||
|
||||
|
||||
|
||||
-- The State class; is the father of all State objects
|
||||
local State = class('State', Object)
|
||||
|
||||
function State.subclass(theClass, name, theStatefulClass)
|
||||
local theSubClass = Object.subclass(theClass, name)
|
||||
local superDict = (theClass==State and theClass.__classDict or theStatefulClass.superclass.__classDict)
|
||||
theSubClass.subclass = State.subclass
|
||||
|
||||
local mt = getmetatable(theSubClass)
|
||||
mt.__newindex = function(_, methodName, method)
|
||||
if type(method) == 'function' then
|
||||
local fenv = getfenv(method)
|
||||
local newenv = setmetatable( {super = superDict}, {__index = fenv, __newindex = fenv} )
|
||||
setfenv( method, newenv )
|
||||
end
|
||||
rawset(theSubClass.__classDict, methodName, method)
|
||||
end
|
||||
|
||||
return theSubClass
|
||||
end
|
||||
|
||||
------------------------------------
|
||||
-- INSTANCE METHODS
|
||||
------------------------------------
|
||||
------------------------------------
|
||||
|
||||
--[[ constructor
|
||||
If your states need initialization, they can receive parameters via the initParameters parameter
|
||||
@ -56,11 +78,11 @@ local _prevSubclass = StatefulObject.subclass -- previous way of creating subcla
|
||||
function StatefulObject:initialize(initParameters)
|
||||
super.initialize(self)
|
||||
initParameters = initParameters or {} --initialize to empty table if nil
|
||||
|
||||
_private[self] = {
|
||||
states = {},
|
||||
stateStack = {}
|
||||
}
|
||||
|
||||
_private[self] = {
|
||||
states = {},
|
||||
stateStack = {}
|
||||
}
|
||||
|
||||
for stateName,stateClass in pairs(self.class.states) do
|
||||
local state = stateClass:new(unpack(initParameters[stateName] or {}))
|
||||
@ -73,157 +95,159 @@ end
|
||||
If the current state has a method called onExitState, it will be called, with the instance as a parameter.
|
||||
If the "next" state exists and has a method called onExitState, it will be called, with the instance as a parameter.
|
||||
use gotoState(nil) for setting states to nothing
|
||||
This method invokes the exitState and enterState functions if they exist on the current state
|
||||
This method invokes the exitState and enterState functions if they exist on the current state
|
||||
Second parameter is optional. If true, the stack will be conserved. Otherwise, it will be popped.
|
||||
]]
|
||||
function StatefulObject:gotoState(newStateName, keepStack)
|
||||
assert(_private[self].states~=nil, "Attribute 'states' not detected. check that you called instance:gotoState and not instance.gotoState, and that you invoked super.initialize(self) in the constructor.")
|
||||
|
||||
local prevState = self:getCurrentState()
|
||||
|
||||
-- If we're trying to go to a state in which we already are, return (do nothing)
|
||||
if(prevState~=nil and prevState.name == newStateName) then return end
|
||||
|
||||
local nextState
|
||||
if(newStateName~=nil) then
|
||||
nextState = _private[self].states[newStateName]
|
||||
assert(nextState~=nil, "State '" .. newStateName .. "' not found")
|
||||
|
||||
local prevState = self:getCurrentState()
|
||||
|
||||
-- If we're trying to go to a state in which we already are, return (do nothing)
|
||||
if(prevState~=nil and prevState.name == newStateName) then return end
|
||||
|
||||
local nextState
|
||||
if(newStateName~=nil) then
|
||||
nextState = _private[self].states[newStateName]
|
||||
assert(nextState~=nil, "State '" .. newStateName .. "' not found")
|
||||
end
|
||||
|
||||
-- Either empty completely the stack, or just call the exitstate callback on current state
|
||||
if(keepStack~=true) then
|
||||
self:popAllStates()
|
||||
else
|
||||
_invokeCallback(self, prevState, 'exitState', newStateName )
|
||||
end
|
||||
|
||||
-- replace the top of the stack with the new state
|
||||
local stack = _getStack(self)
|
||||
|
||||
-- Either empty completely the stack, or just call the exitstate callback on current state
|
||||
if(keepStack~=true) then
|
||||
self:popAllStates()
|
||||
else
|
||||
_invokeCallback(self, prevState, 'exitState', newStateName )
|
||||
end
|
||||
|
||||
-- replace the top of the stack with the new state
|
||||
local stack = _getStack(self)
|
||||
stack[math.max(#stack,1)] = nextState
|
||||
|
||||
-- Invoke enterState on the new state. 2nd parameter is the name of the previous state, or nil
|
||||
_invokeCallback(self, nextState, 'enterState', prevState~=nil and prevState.name or nil)
|
||||
-- Invoke enterState on the new state. 2nd parameter is the name of the previous state, or nil
|
||||
_invokeCallback(self, nextState, 'enterState', prevState~=nil and prevState.name or nil)
|
||||
|
||||
end
|
||||
|
||||
--[[ Changes the current state, by pushing a new state on the stack.
|
||||
If the pushed state is already on the stack, this function does nothing.
|
||||
Invokes 'pausedState' on the previous state, if existing
|
||||
The new state is pushed on the top of the stack and then
|
||||
Invokes 'pushedState' and 'enterState' on the new state, if existing
|
||||
]]
|
||||
function StatefulObject:pushState(newStateName)
|
||||
assert(type(newStateName)=='string', "newStateName must be a string.")
|
||||
end
|
||||
|
||||
--[[ Changes the current state, by pushing a new state on the stack.
|
||||
If the pushed state is already on the stack, this function does nothing.
|
||||
Invokes 'pausedState' on the previous state, if existing
|
||||
The new state is pushed on the top of the stack and then
|
||||
Invokes 'pushedState' and 'enterState' on the new state, if existing
|
||||
]]
|
||||
function StatefulObject:pushState(newStateName)
|
||||
assert(type(newStateName)=='string', "newStateName must be a string.")
|
||||
assert(_private[self].states~=nil, "Attribute 'states' not detected. check that you called instance:pushState and not instance.pushState, and that you invoked super.initialize(self) in the constructor.")
|
||||
|
||||
local nextState = _private[self].states[newStateName]
|
||||
assert(nextState~=nil, "State '" .. newStateName .. "' not found")
|
||||
|
||||
-- If we attempt to push a state and the state is already on return (do nothing)
|
||||
local stack = _getStack(self)
|
||||
|
||||
local nextState = _private[self].states[newStateName]
|
||||
assert(nextState~=nil, "State '" .. newStateName .. "' not found")
|
||||
|
||||
-- If we attempt to push a state and the state is already on return (do nothing)
|
||||
local stack = _getStack(self)
|
||||
for _,state in ipairs(stack) do
|
||||
if(state.name == newStateName) then return end
|
||||
end
|
||||
|
||||
-- Invoke pausedState on the previous state
|
||||
_invokeCallback(self, self:getCurrentState(), 'pausedState')
|
||||
|
||||
-- Do the push
|
||||
table.insert(stack, nextState)
|
||||
|
||||
-- Invoke pushedState & enterState on the next state
|
||||
_invokeCallback(self, nextState, 'pushedState')
|
||||
_invokeCallback(self, nextState, 'enterState')
|
||||
|
||||
return nextState
|
||||
end
|
||||
|
||||
--[[ Removes a state from the state stack
|
||||
If a state name is given, it will attempt to remove it from the stack. If not found on the stack it will do nothing.
|
||||
If no state name is give, this pops the top state from the stack, if any. Otherwise it does nothing.
|
||||
Callbacks will be called when needed.
|
||||
]]
|
||||
function StatefulObject:popState(stateName)
|
||||
assert(_private[self].states~=nil, "Attribute 'states' not detected. check that you called instance:popState and not instance.popState, and that you invoked super.initialize(self) in the constructor.")
|
||||
|
||||
-- Invoke exitstate & poppedState on the state being popped out
|
||||
local prevState = self:getCurrentState()
|
||||
_invokeCallback(self, prevState, 'exitState')
|
||||
_invokeCallback(self, prevState, 'poppedState')
|
||||
|
||||
-- Do the pop
|
||||
local stack = _getStack(self)
|
||||
table.remove(stack, #stack)
|
||||
|
||||
-- Invoke continuedState on the new state
|
||||
local newState = self:getCurrentState()
|
||||
_invokeCallback(self, newState, 'continuedState')
|
||||
|
||||
return newState
|
||||
end
|
||||
|
||||
--[[ Empties the state stack
|
||||
This function will invoke all the popState, exitState callbacks on all the states as they pop out.
|
||||
]]
|
||||
function StatefulObject:popAllStates()
|
||||
local state = self:popState()
|
||||
while(state~=nil) do state = self:popState() end
|
||||
end
|
||||
|
||||
--[[ Returns the current state (top of the stack only)
|
||||
The current state's name can be obtained doing object:getCurrentState().name
|
||||
]]
|
||||
function StatefulObject:getCurrentState()
|
||||
local stack = _getStack(self)
|
||||
if #stack == 0 then return nil end
|
||||
return(stack[#stack])
|
||||
end
|
||||
|
||||
-- Invoke pausedState on the previous state
|
||||
_invokeCallback(self, self:getCurrentState(), 'pausedState')
|
||||
|
||||
-- Do the push
|
||||
table.insert(stack, nextState)
|
||||
|
||||
-- Invoke pushedState & enterState on the next state
|
||||
_invokeCallback(self, nextState, 'pushedState')
|
||||
_invokeCallback(self, nextState, 'enterState')
|
||||
|
||||
return nextState
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns true if the object is in the state named 'stateName'
|
||||
If second(optional) parameter is true, this method returns true if the state is on the stack instead
|
||||
]]
|
||||
function StatefulObject:isInState(stateName, testStateStack)
|
||||
local stack = _getStack(self)
|
||||
|
||||
if(testStateStack==true) then
|
||||
|
||||
--[[ Removes a state from the state stack
|
||||
If a state name is given, it will attempt to remove it from the stack. If not found on the stack it will do nothing.
|
||||
If no state name is give, this pops the top state from the stack, if any. Otherwise it does nothing.
|
||||
Callbacks will be called when needed.
|
||||
]]
|
||||
function StatefulObject:popState(stateName)
|
||||
assert(_private[self].states~=nil, "Attribute 'states' not detected. check that you called instance:popState and not instance.popState, and that you invoked super.initialize(self) in the constructor.")
|
||||
|
||||
-- Invoke exitstate & poppedState on the state being popped out
|
||||
local prevState = self:getCurrentState()
|
||||
_invokeCallback(self, prevState, 'exitState')
|
||||
_invokeCallback(self, prevState, 'poppedState')
|
||||
|
||||
-- Do the pop
|
||||
local stack = _getStack(self)
|
||||
table.remove(stack, #stack)
|
||||
|
||||
-- Invoke continuedState on the new state
|
||||
local newState = self:getCurrentState()
|
||||
_invokeCallback(self, newState, 'continuedState')
|
||||
|
||||
return newState
|
||||
end
|
||||
|
||||
--[[ Empties the state stack
|
||||
This function will invoke all the popState, exitState callbacks on all the states as they pop out.
|
||||
]]
|
||||
function StatefulObject:popAllStates()
|
||||
local state = self:popState()
|
||||
while(state~=nil) do state = self:popState() end
|
||||
end
|
||||
|
||||
--[[ Returns the current state (top of the stack only)
|
||||
The current state's name can be obtained doing object:getCurrentState().name
|
||||
]]
|
||||
function StatefulObject:getCurrentState()
|
||||
local stack = _getStack(self)
|
||||
if #stack == 0 then return nil end
|
||||
return(stack[#stack])
|
||||
end
|
||||
|
||||
--[[
|
||||
Returns true if the object is in the state named 'stateName'
|
||||
If second(optional) parameter is true, this method returns true if the state is on the stack instead
|
||||
]]
|
||||
function StatefulObject:isInState(stateName, testStateStack)
|
||||
local stack = _getStack(self)
|
||||
|
||||
if(testStateStack==true) then
|
||||
for _,state in ipairs(stack) do
|
||||
if(state.name == stateName) then return true end
|
||||
end
|
||||
else --testStateStack==false
|
||||
local state = stack[#stack]
|
||||
if(state~=nil and state.name == stateName) then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
else --testStateStack==false
|
||||
local state = stack[#stack]
|
||||
if(state~=nil and state.name == stateName) then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
------------------------------------
|
||||
-- CLASS METHODS
|
||||
------------------------------------
|
||||
|
||||
--[[ Adds a new state to the "states" class member.
|
||||
superState is optional. If nil, Object will be the parent class of the new state
|
||||
superState is optional. If nil, State will be the parent class of the new state
|
||||
returns the newly created state
|
||||
]]
|
||||
function StatefulObject.addState(theClass, stateName, superState)
|
||||
superState = superState or State
|
||||
--print(theClass.name, stateName, superState.name)
|
||||
assert(subclassOf(StatefulObject, theClass), "Use class:addState instead of class.addState")
|
||||
assert(theClass.states[stateName]==nil, "The class " .. theClass.name .. " already has a state called '" .. stateName)
|
||||
assert(type(stateName)=="string", "stateName must be a string")
|
||||
-- states are just regular classes. If superState is nil, this uses Object as superClass
|
||||
local state = class(stateName, superState)
|
||||
local state = superState:subclass(stateName, theClass)
|
||||
theClass.states[stateName] = state
|
||||
return state
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Redefinition of Object:subclass
|
||||
Subclasses inherit all the states of their superclases, in a special way:
|
||||
If class A has a state called Sleeping and B = A.subClass('B'), then B.states.Sleeping is a subclass of A.states.Sleeping
|
||||
returns the newly created stateful class
|
||||
]]
|
||||
function StatefulObject.subclass(theClass, name)
|
||||
function StatefulObject.subclass(theClass, name)
|
||||
assert(theClass==StatefulObject or subclassOf(StatefulObject, theClass), "Use class:subclass instead of class.subclass")
|
||||
|
||||
|
||||
local theSubClass = _prevSubclass(theClass, name) --for now, theClass is just a regular subclass
|
||||
|
||||
--the states of the subclass are subclasses of the superclass' states
|
||||
@ -234,15 +258,15 @@ function StatefulObject.subclass(theClass, name)
|
||||
|
||||
--look for instance methods on the state stack before looking them up on the class' dictionary
|
||||
local classDict = theSubClass.__classDict
|
||||
classDict.__index = function(instance, methodName)
|
||||
-- If the method isn't on the 'ignoredMethods' list, look through the stack to see if it is defined
|
||||
if(_ignoredMethods[methodName]~=1) then
|
||||
local stack = _private[instance].stateStack
|
||||
local method
|
||||
for i = #stack,1,-1 do -- reversal loop
|
||||
method = stack[i][methodName]
|
||||
if(method~=nil) then return method end
|
||||
end
|
||||
classDict.__index = function(instance, methodName)
|
||||
-- If the method isn't on the 'ignoredMethods' list, look through the stack to see if it is defined
|
||||
if(_ignoredMethods[methodName]~=1) then
|
||||
local stack = _private[instance].stateStack
|
||||
local method
|
||||
for i = #stack,1,-1 do -- reversal loop
|
||||
method = stack[i][methodName]
|
||||
if(method~=nil) then return method end
|
||||
end
|
||||
end
|
||||
--if ignored or not found, look on the class method
|
||||
return classDict[methodName]
|
||||
@ -272,4 +296,4 @@ function StatefulObject.includes(theClass, module, ...)
|
||||
state:includes(moduleState, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user