MindState.lua 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. -----------------------------------------------------------------------------------
  2. -- MindState.lua
  3. -- Enrique García ( enrique.garcia.cota [AT] gmail [DOT] com ) - 19 Oct 2009
  4. -- Based on Unrealscript's stateful objects
  5. -----------------------------------------------------------------------------------
  6. --[[ StatefulObject declaration
  7. * Stateful classes have a list of states (accesible through class.states).
  8. * 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)
  9. * 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
  10. * It is possible to change states by doing class:gotoState(stateName)
  11. ]]
  12. StatefulObject = class('StatefulObject')
  13. StatefulObject.states = {} -- the root state list
  14. local private = setmetatable({}, {__mode = "k"}) -- weak table storing private references
  15. -- Instance methods
  16. --[[ constructor
  17. If your states need initialization, they can receive parameters via the initParameters parameter
  18. initParameters is a table with parameters used for initializing the states. These are needed mostly if
  19. your states have a custom superclass that needs parameters on their initialize() function.
  20. ]]
  21. function StatefulObject:initialize(initParameters)
  22. super.initialize(self)
  23. initParameters = initParameters or {} --initialize to empty table if nil
  24. self.states = {}
  25. private[self] = {
  26. stateStack = {}
  27. }
  28. for stateName,stateClass in pairs(self.class.states) do
  29. local state = stateClass:new(unpack(initParameters[stateName] or {}))
  30. state.name = stateName
  31. self.states[stateName] = state
  32. end
  33. end
  34. --[[ Changes the current state.
  35. If the current state has a method called onExitState, it will be called, with the instance as a parameter.
  36. If the "next" state exists and has a method called onExitState, it will be called, with the instance as a parameter.
  37. use gotoState(nil) for setting states to nothing
  38. This method invokes the exitState and enterState functions if they exist on the current state
  39. Second parameter is optional. If true, the stack will be conserved. Otherwise, it will be popped.
  40. ]]
  41. function StatefulObject:gotoState(newStateName, keepStack)
  42. 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.")
  43. local prevState = self:getCurrentState()
  44. -- If we're trying to go to a state in which we already are, return (do nothing)
  45. if(prevState~=nil and prevState.name == newStateName) then return end
  46. local nextState
  47. if(newStateName~=nil) then
  48. nextState = self.states[newStateName]
  49. assert(nextState~=nil, "State '" .. newStateName .. "' not found")
  50. end
  51. -- Invoke exitState on the previous state
  52. if(prevState~=nil and type(prevState.exitState) == "function") then prevState.exitState(self, newStateName) end
  53. -- Empty the stack unless keepStack is true.
  54. if(keepStack~=true) then self:popAllStates() end
  55. -- replace the top of the stack with the new state
  56. local stack = private[self].stateStack
  57. stack[math.max(#stack,1)] = nextState
  58. -- Invoke enterState on the new state. 2nd parameter is the name of the previous state, or nil
  59. if(nextState~=nil and type(nextState.enterState) == "function") then
  60. nextState.enterState(self, prevState~=nil and prevState.name or nil)
  61. end
  62. end
  63. function StatefulObject:pushState(newStateName)
  64. assert(type(newState)=='string', "newStateName must be a string.")
  65. 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.")
  66. local nextState = self.states[newStateName]
  67. assert(nextState~=nil, "State '" .. newStateName .. "' not found")
  68. -- If we attempt to push a state and the state is already on return (do nothing)
  69. local stack = private[self].stateStack
  70. for _,state in ipairs(stack) do
  71. if(state.name == newStateName) then return end
  72. end
  73. -- Invoke pausedState on the previous state
  74. local prevState = self:getCurrentState()
  75. if(prevState~=nil and type(prevState.pausedState) == "function") then prevState.pausedState(self) end
  76. -- Do the push
  77. table.insert(stack, nextState)
  78. -- Invoke pushState on the next state
  79. if(type(nextState.pushedState) == "function") then nextState.pushedState(self) end
  80. return nextState
  81. end
  82. -- 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.
  83. -- If no state name is give, this pops the top state from the stack, if any. Otherwise it does nothing.
  84. -- Callbacks will be called when needed.
  85. function StatefulObject:popState(stateName)
  86. 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.")
  87. -- Invoke poppedState on the previous state
  88. local prevState = self:getCurrentState()
  89. if(prevState~=nil and type(prevState.poppedState) == "function") then prevState.poppedState(self) end
  90. -- Do the pop
  91. local stack = private[self].stateStack
  92. table.remove(stack, #stack)
  93. -- Invoke continuedState on the new state
  94. local newState = self:getCurrentState()
  95. if(newState~=nil and type(newState.continuedState) == "function") then newState.continuedState(self) end
  96. return newState
  97. end
  98. function StatefulObject:popAllStates()
  99. local state = self:popState()
  100. while(state~=nil) do state = self:popState() end
  101. end
  102. function StatefulObject:getCurrentState()
  103. local stack = private[self].stateStack
  104. if #stack == 0 then return nil end
  105. return(stack[#stack])
  106. end
  107. --[[
  108. Returns true if the object is in the state named 'stateName'
  109. If second(optional) parameter is true, this method returns true if the state is on the stack instead
  110. ]]
  111. function StatefulObject:inState(stateName, testStateStack)
  112. local stack = private[self].stateStack
  113. if(testStateStack==true) then
  114. for _,state in ipairs(stack) do
  115. if(state.name == stateName) then return true end
  116. end
  117. else --testStateStack==false
  118. local state = stack[#stack]
  119. if(state~=nil and state.name == stateName) then return true end
  120. end
  121. return false
  122. end
  123. -- Class methods
  124. --[[ Adds a new state to the "states" class member.
  125. superState is optional. If nil, Object will be the parent class of the new state
  126. returns the newly created state
  127. ]]
  128. function StatefulObject:addState(stateName, superState)
  129. assert(subclassOf(StatefulObject, self), "Use class:addState instead of class.addState")
  130. assert(self.states[stateName]==nil, "The class " .. self.name .. " already has a state called '" .. stateName)
  131. assert(type(stateName)=="string", "stateName must be a string")
  132. -- states are just regular classes. If superState is nil, this uses Object as superClass
  133. local state = class(stateName, superState)
  134. self.states[stateName] = state
  135. return state
  136. end
  137. -- These methods will not be overriden by the states.
  138. local ignoredMethods = {
  139. states=1, initialize=1,
  140. gotoState=1, pushState=1, popState=1, popAllStates=1, getCurrentState=1, inState=1,
  141. enterState=1, exitState=1, pushedState=1, poppedState=1, pausedState=1, continuedState=1,
  142. addState=1, subclass=1, includes=1
  143. }
  144. local prevSubclass = StatefulObject.subclass
  145. --[[ creates a stateful subclass
  146. Subclasses inherit all the states of their superclases, in a special way:
  147. If class A has a state called Sleeping and B = A.subClass('B'), then B.states.Sleeping is a subclass of A.states.Sleeping
  148. returns the newly created stateful class
  149. ]]
  150. function StatefulObject:subclass(name)
  151. --assert(subclassOf(StatefulObject, self), "Use class:subclass instead of class.subclass")
  152. local theClass = prevSubclass(self, name) --for now, theClass is just a regular subclass
  153. --the states of the subclass are subclasses of the superclass' states
  154. theClass.states = {}
  155. for stateName,state in pairs(self.states) do
  156. theClass:addState(stateName, state)
  157. end
  158. --make sure that the currentState is used on the method lookup function before looking on the class dict
  159. local classDict = theClass.__classDict
  160. classDict.__index = function(instance, methodName)
  161. -- If the method isn't on the 'ignoredMethods' list, look through the stack to see if it is defined
  162. if(ignoredMethods[methodName]~=1) then
  163. local stack = private[instance].stateStack
  164. local method
  165. for i = #stack,1,-1 do -- reversal loop
  166. method = stack[i][methodName]
  167. if(method~=nil) then return method end
  168. end
  169. end
  170. --if ignored or not found, look on the class itself
  171. return classDict[methodName]
  172. end
  173. return theClass
  174. end
  175. --[[ Include override for stateful classes.
  176. This is exactly like MiddleClass' include function, except that it module has a property called "states"
  177. then each member of that module.states is included on the StatefulObject class.
  178. If module.states has a state that doesn't exist on StatefulObject, a new state will be created.
  179. ]]
  180. function StatefulObject:includes(module, ...)
  181. assert(subclassOf(StatefulObject, self), "Use class:includes instead of class.includes")
  182. for methodName,method in pairs(module) do
  183. if methodName ~="included" and methodName ~= "states" then
  184. self[methodName] = method
  185. end
  186. end
  187. if type(module.included)=="function" then module.included(self, ...) end
  188. if type(module.states)=="table" then
  189. for stateName,moduleState in pairs(module.states) do
  190. local state = self.states[stateName]
  191. if(state==nil) then state = theClass:addState(stateName) end
  192. state:includes(moduleState, ...)
  193. end
  194. end
  195. end