MindState: Untested attempt at adding stackable states

This commit is contained in:
kikito 2010-02-14 23:37:10 +00:00
parent 5b1a437780
commit e06b0c16d7

View File

@ -13,7 +13,9 @@ require 'MiddleClass.lua'
]] ]]
StatefulObject = class('StatefulObject') StatefulObject = class('StatefulObject')
StatefulObject.states = {} -- the root state list StatefulObject.states = {} -- the root state list
local private = setmetatable({}, {__mode = "k"}) -- weak table storing private references
-- Instance methods -- Instance methods
@ -25,7 +27,10 @@ StatefulObject.states = {} -- the root state list
function StatefulObject:initialize(initParameters) function StatefulObject:initialize(initParameters)
super.initialize(self) super.initialize(self)
initParameters = initParameters or {} --initialize to empty table if nil initParameters = initParameters or {} --initialize to empty table if nil
self.states = {} self.states = {}
private[self] = {
stateStack = {}
}
for stateName,stateClass in pairs(self.class.states) do for stateName,stateClass in pairs(self.class.states) do
local state = stateClass:new(unpack(initParameters[stateName] or {})) local state = stateClass:new(unpack(initParameters[stateName] or {}))
state.name = stateName state.name = stateName
@ -37,29 +42,116 @@ end
If the current state has a method called onExitState, it will be called, with the instance as a parameter. 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. 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 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(stateName) 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.") 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 nextState
if(stateName~=nil) then
nextState = self.states[stateName]
assert(nextState~=nil, "State '" .. stateName .. "' not found")
end
local prevState = self.currentState
if(prevState~=nil and type(prevState.exitState) == "function") then
prevState.exitState(self)
end
self.previousState = prevState local prevState = self:getCurrentState()
self.currentState = nextState
-- If we're trying to go to a state in which we already are, return (do nothing)
if(nextState~=nil and type(nextState.enterState) == "function") then if(prevState~=nil and prevState.name == newStateName) then return end
nextState.enterState(self)
local nextState
if(newStateName~=nil) then
nextState = self.states[newStateName]
assert(nextState~=nil, "State '" .. newStateName .. "' not found")
end 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 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 -- Class methods
@ -77,8 +169,13 @@ function StatefulObject:addState(stateName, superState)
return state return state
end end
-- These methods will not be overriden by the states.
local ignoredMethods = {states=1, initialize=1, gotoState=1, addState=1, subclass=1, includes=1, exitState=1, enterState=1} 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 local prevSubclass = StatefulObject.subclass
--[[ creates a stateful subclass --[[ creates a stateful subclass
Subclasses inherit all the states of their superclases, in a special way: Subclasses inherit all the states of their superclases, in a special way:
@ -97,17 +194,18 @@ function StatefulObject:subclass(name)
--make sure that the currentState is used on the method lookup function before looking on the class dict --make sure that the currentState is used on the method lookup function before looking on the class dict
local classDict = theClass.__classDict local classDict = theClass.__classDict
classDict.__index = function(instance, method) classDict.__index = function(instance, methodName)
--first look on the current state -- If the method isn't on the 'ignoredMethods' list, look through the stack to see if it is defined
local currentState = rawget(instance, 'currentState') if(ignoredMethods[methodName]~=1) then
if( currentState~=nil and local stack = private[instance].stateStack
currentState[method]~=nil and local method
ignoredMethods[method]==nil) then for i = #stack,1,-1 do -- reversal loop
return currentState[method] method = stack[i][methodName]
else if(method~=nil) then return method end
--if not found, look on the class itself end
return classDict[method]
end end
--if ignored or not found, look on the class itself
return classDict[methodName]
end end
return theClass return theClass
@ -119,19 +217,19 @@ end
then each member of that module.states is included on the StatefulObject class. 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. If module.states has a state that doesn't exist on StatefulObject, a new state will be created.
]] ]]
function StatefulObject:includes(module) function StatefulObject:includes(module, ...)
assert(subclassOf(StatefulObject, self), "Use class:includes instead of class.includes") assert(subclassOf(StatefulObject, self), "Use class:includes instead of class.includes")
for methodName,method in pairs(module) do for methodName,method in pairs(module) do
if methodName ~="included" and methodName ~= "states" then if methodName ~="included" and methodName ~= "states" then
self[methodName] = method self[methodName] = method
end end
end end
if type(module.included)=="function" then module:included(self) end if type(module.included)=="function" then module.included(self, ...) end
if type(module.states)=="table" then if type(module.states)=="table" then
for stateName,moduleState in pairs(module.states) do for stateName,moduleState in pairs(module.states) do
local state = self.states[stateName] local state = self.states[stateName]
if(state==nil) then state = theClass:addState(stateName) end if(state==nil) then state = theClass:addState(stateName) end
state:includes(moduleState) state:includes(moduleState, ...)
end end
end end
end end