historical/toontown-classic.git/panda/direct/fsm/FourStateAI.py
2024-01-16 11:20:27 -06:00

273 lines
8.9 KiB
Python

"""Contains the FourStateAI class. See also :mod:`.FourState`."""
__all__ = ['FourStateAI']
from direct.directnotify import DirectNotifyGlobal
#import DistributedObjectAI
from . import ClassicFSM
from . import State
from direct.task import Task
class FourStateAI:
"""
Generic four state ClassicFSM base class.
This is a mix-in class that expects that your derived class
is a DistributedObjectAI.
Inherit from FourStateFSM and pass in your states. Two of
the states should be oposites of each other and the other
two should be the transition states between the first two.
E.g::
+--------+
-->| closed | --
| +--------+ |
| |
| v
+---------+ +---------+
| closing |<----->| opening |
+---------+ +---------+
^ |
| |
| +------+ |
----| open |<---
+------+
There is a fifth off state, but that is an implementation
detail (and that's why it's not called a five state ClassicFSM).
I found that this pattern repeated in several things I was
working on, so this base class was created.
"""
notify = DirectNotifyGlobal.directNotify.newCategory('FourStateAI')
def __init__(self, names, durations = [0, 1, None, 1, 1]):
"""
names is a list of state names
E.g.
['off', 'opening', 'open', 'closing', 'closed',]
e.g. 2:
['off', 'locking', 'locked', 'unlocking', 'unlocked',]
e.g. 3:
['off', 'deactivating', 'deactive', 'activating', 'activated',]
durations is a list of durations in seconds or None values.
The list of duration values should be the same length
as the list of state names and the lists correspond.
For each state, after n seconds, the ClassicFSM will move to
the next state. That does not happen for any duration
values of None.
More Details
Here is a diagram showing the where the names from the list
are used:
+---------+
| 0 (off) |----> (any other state and vice versa).
+---------+
+--------+
-->| 4 (on) |---
| +--------+ |
| |
| v
+---------+ +---------+
| 3 (off) |<----->| 1 (off) |
+---------+ +---------+
^ |
| |
| +---------+ |
--| 2 (off) |<--
+---------+
Each states also has an associated on or off value. The only
state that is 'on' is state 4. So, the transition states
between off and on (states 1 and 3) are also considered
off (and so is state 2 which is oposite of state 4 and therefore
oposite of 'on').
"""
self.stateIndex = 0
assert self.__debugPrint(
"FourStateAI(names=%s, durations=%s)"
%(names, durations))
self.doLaterTask = None
assert len(names) == 5
assert len(names) == len(durations)
self.names = names
self.durations = durations
self.states = {
0: State.State(names[0],
self.enterState0,
self.exitState0,
[names[1],
names[2],
names[3],
names[4]]),
1: State.State(names[1],
self.enterState1,
self.exitState1,
[names[2], names[3]]),
2: State.State(names[2],
self.enterState2,
self.exitState2,
[names[3]]),
3: State.State(names[3],
self.enterState3,
self.exitState3,
[names[4], names[1]]),
4: State.State(names[4],
self.enterState4,
self.exitState4,
[names[1]]),
}
self.fsm = ClassicFSM.ClassicFSM('FourState',
list(self.states.values()),
# Initial State
names[0],
# Final State
names[0],
)
self.fsm.enterInitialState()
def delete(self):
assert self.__debugPrint("delete()")
if self.doLaterTask is not None:
self.doLaterTask.remove()
del self.doLaterTask
del self.states
del self.fsm
def getState(self):
assert self.__debugPrint("getState() returning %s"%(self.stateIndex,))
return [self.stateIndex]
def sendState(self):
assert self.__debugPrint("sendState()")
self.sendUpdate('setState', self.getState())
def setIsOn(self, isOn):
assert self.__debugPrint("setIsOn(isOn=%s)"%(isOn,))
if isOn:
if self.stateIndex != 4:
# ...if it's not On; request turning on:
self.fsm.request(self.states[3])
else:
if self.stateIndex != 2:
# ...if it's not Off; request turning off:
self.fsm.request(self.states[1])
#if isOn:
# nextState = (4, 3, 3, 4, None)[self.stateIndex]
#else:
# nextState = (2, 2, None, 1, 1)[self.stateIndex]
#if nextState is not None:
# self.fsm.request(self.states[nextState])
def isOn(self):
assert self.__debugPrint("isOn() returning %s (stateIndex=%s)"%(self.stateIndex==4, self.stateIndex))
return self.stateIndex==4
def changedOnState(self, isOn):
"""
Allow derived classes to overide this.
The self.isOn value has toggled. Call getIsOn() to
get the current state.
"""
assert self.__debugPrint("changedOnState(isOn=%s)"%(isOn,))
##### states #####
def switchToNextStateTask(self, task):
assert self.__debugPrint("switchToNextStateTask()")
self.fsm.request(self.states[self.nextStateIndex])
return Task.done
def distributeStateChange(self):
"""
This function is intentionaly simple so that derived classes
may easily alter the network message.
"""
assert self.__debugPrint("distributeStateChange()")
self.sendState()
def enterStateN(self, stateIndex, nextStateIndex):
assert self.__debugPrint(
"enterStateN(stateIndex=%s, nextStateIndex=%s)"%
(stateIndex, nextStateIndex))
self.stateIndex = stateIndex
self.nextStateIndex = nextStateIndex
self.distributeStateChange()
if self.durations[stateIndex] is not None:
assert self.doLaterTask is None
self.doLaterTask=taskMgr.doMethodLater(
self.durations[stateIndex],
self.switchToNextStateTask,
"enterStateN-timer-%s"%id(self))
def exitStateN(self):
assert self.__debugPrint("exitStateN()")
if self.doLaterTask:
taskMgr.remove(self.doLaterTask)
self.doLaterTask=None
##### state 0 #####
def enterState0(self):
assert self.__debugPrint("enter0()")
self.enterStateN(0, 0)
def exitState0(self):
assert self.__debugPrint("exit0()")
##### state 1 #####
def enterState1(self):
#assert self.__debugPrint("enterState1()")
self.enterStateN(1, 2)
def exitState1(self):
assert self.__debugPrint("exitState1()")
self.exitStateN()
##### state 2 #####
def enterState2(self):
#assert self.__debugPrint("enterState2()")
self.enterStateN(2, 3)
def exitState2(self):
assert self.__debugPrint("exitState2()")
self.exitStateN()
##### state 3 #####
def enterState3(self):
#assert self.__debugPrint("enterState3()")
self.enterStateN(3, 4)
def exitState3(self):
assert self.__debugPrint("exitState3()")
self.exitStateN()
##### state 4 #####
def enterState4(self):
assert self.__debugPrint("enterState4()")
self.enterStateN(4, 1)
self.changedOnState(1)
def exitState4(self):
assert self.__debugPrint("exitState4()")
self.exitStateN()
self.changedOnState(0)
if __debug__:
def __debugPrint(self, message):
"""for debugging"""
return self.notify.debug("%d (%d) %s"%(
id(self), self.stateIndex==4, message))