123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- -----------------------------------------------------------------------------------
- -- MindState.lua
- -- Enrique García ( enrique.garcia.cota [AT] gmail [DOT] com ) - 19 Oct 2009
- -- Based on Unrealscript's stateful objects
- -----------------------------------------------------------------------------------
- --[[ 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)
- * If a method is not found on the current state, or if current state is nil, the method is looked up on the class itself
- * It is possible to change states by doing class:gotoState(stateName)
- ]]
- StatefulObject = class('StatefulObject')
- StatefulObject.states = {} -- the root state list
- local private = setmetatable({}, {__mode = "k"}) -- weak table storing private references
- -- Instance methods
- --[[ constructor
- If your states need initialization, they can receive parameters via the initParameters parameter
- initParameters is a table with parameters used for initializing the states. These are needed mostly if
- your states have a custom superclass that needs parameters on their initialize() function.
- ]]
- function StatefulObject:initialize(initParameters)
- super.initialize(self)
- initParameters = initParameters or {} --initialize to empty table if nil
- self.states = {}
- private[self] = {
- stateStack = {}
- }
- for stateName,stateClass in pairs(self.class.states) do
- local state = stateClass:new(unpack(initParameters[stateName] or {}))
- state.name = stateName
- self.states[stateName] = state
- end
- end
- --[[ Changes the current state.
- 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
- Second parameter is optional. If true, the stack will be conserved. Otherwise, it will be popped.
- ]]
- function StatefulObject:gotoState(newStateName, keepStack)
- assert(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 = self.states[newStateName]
- assert(nextState~=nil, "State '" .. newStateName .. "' not found")
- end
- -- Invoke exitState on the previous state
- if(prevState~=nil and type(prevState.exitState) == "function") then prevState.exitState(self, newStateName) end
- -- Empty the stack unless keepStack is true.
- if(keepStack~=true) then self:popAllStates() end
- -- replace the top of the stack with the new state
- local stack = private[self].stateStack
- stack[math.max(#stack,1)] = nextState
- -- Invoke enterState on the new state. 2nd parameter is the name of the previous state, or nil
- if(nextState~=nil and type(nextState.enterState) == "function") then
- nextState.enterState(self, prevState~=nil and prevState.name or nil)
- end
- end
- function StatefulObject:pushState(newStateName)
- assert(type(newState)=='string', "newStateName must be a string.")
- assert(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 = 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 = private[self].stateStack
- for _,state in ipairs(stack) do
- if(state.name == newStateName) then return end
- end
- -- Invoke pausedState on the previous state
- local prevState = self:getCurrentState()
- if(prevState~=nil and type(prevState.pausedState) == "function") then prevState.pausedState(self) end
- -- Do the push
- table.insert(stack, nextState)
- -- Invoke pushState on the next state
- if(type(nextState.pushedState) == "function") then nextState.pushedState(self) end
-
- return nextState
- end
- -- 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(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 poppedState on the previous state
- local prevState = self:getCurrentState()
- if(prevState~=nil and type(prevState.poppedState) == "function") then prevState.poppedState(self) end
- -- Do the pop
- local stack = private[self].stateStack
- table.remove(stack, #stack)
- -- Invoke continuedState on the new state
- local newState = self:getCurrentState()
- if(newState~=nil and type(newState.continuedState) == "function") then newState.continuedState(self) end
-
- return newState
- end
- function StatefulObject:popAllStates()
- local state = self:popState()
- while(state~=nil) do state = self:popState() end
- end
- function StatefulObject:getCurrentState()
- local stack = private[self].stateStack
- 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:inState(stateName, testStateStack)
- local stack = private[self].stateStack
- 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
- -- 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
- returns the newly created state
- ]]
- function StatefulObject:addState(stateName, superState)
- assert(subclassOf(StatefulObject, self), "Use class:addState instead of class.addState")
- assert(self.states[stateName]==nil, "The class " .. self.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)
- self.states[stateName] = state
- return state
- 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, inState=1,
- enterState=1, exitState=1, pushedState=1, poppedState=1, pausedState=1, continuedState=1,
- addState=1, subclass=1, includes=1
- }
- local prevSubclass = StatefulObject.subclass
- --[[ creates a stateful 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(name)
- --assert(subclassOf(StatefulObject, self), "Use class:subclass instead of class.subclass")
- local theClass = prevSubclass(self, name) --for now, theClass is just a regular subclass
-
- --the states of the subclass are subclasses of the superclass' states
- theClass.states = {}
- for stateName,state in pairs(self.states) do
- theClass:addState(stateName, state)
- end
- --make sure that the currentState is used on the method lookup function before looking on the class dict
- local classDict = theClass.__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
- end
- --if ignored or not found, look on the class itself
- return classDict[methodName]
- end
- return theClass
- end
- --[[ Include override for stateful classes.
- This is exactly like MiddleClass' include function, except that it module has a property called "states"
- then each member of that module.states is included on the StatefulObject class.
- If module.states has a state that doesn't exist on StatefulObject, a new state will be created.
- ]]
- function StatefulObject:includes(module, ...)
- assert(subclassOf(StatefulObject, self), "Use class:includes instead of class.includes")
- for methodName,method in pairs(module) do
- if methodName ~="included" and methodName ~= "states" then
- self[methodName] = method
- end
- end
- if type(module.included)=="function" then module.included(self, ...) end
- if type(module.states)=="table" then
- for stateName,moduleState in pairs(module.states) do
- local state = self.states[stateName]
- if(state==nil) then state = theClass:addState(stateName) end
- state:includes(moduleState, ...)
- end
- end
- end
|