4 @todo Look into switching from POLL_TIME = min(F * 2^n, MAX) to POLL_TIME = min(CONST + F * 2^n, MAX)
5 @todo Look into supporting more states that have a different F and MAX
12 import util.go_utils as gobject_utils
13 import util.coroutines as coroutines
17 _moduleLogger = logging.getLogger("gvoice.state_machine")
20 def to_milliseconds(**kwd):
21 if "milliseconds" in kwd:
22 return kwd["milliseconds"]
23 elif "seconds" in kwd:
24 return kwd["seconds"] * 1000
25 elif "minutes" in kwd:
26 return kwd["minutes"] * 1000 * 60
28 return kwd["hours"] * 1000 * 60 * 60
29 raise KeyError("Unknown arg: %r" % kwd)
32 class NopStateStrategy(object):
37 def initialize_state(self):
40 def increment_state(self):
45 return UpdateStateMachine.INFINITE_PERIOD
48 class ConstantStateStrategy(object):
50 def __init__(self, timeout):
51 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
52 self._timeout = timeout
54 def initialize_state(self):
57 def increment_state(self):
65 class GeometricStateStrategy(object):
67 def __init__(self, init, min, max):
68 assert 0 < init and init < max and init != UpdateStateMachine.INFINITE_PERIOD
69 assert 0 < min and min != UpdateStateMachine.INFINITE_PERIOD
70 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
76 def initialize_state(self):
77 self._current = self._min / 2
79 def increment_state(self):
80 if self._max == UpdateStateMachine.INFINITE_PERIOD:
83 self._current = min(2 * self._current, self._max - self._init)
87 return self._init + self._current
90 class StateMachine(object):
92 STATE_ACTIVE = 0, "active"
93 STATE_IDLE = 1, "idle"
97 raise NotImplementedError("Abstract")
100 raise NotImplementedError("Abstract")
103 raise NotImplementedError("Abstract")
105 def set_state(self, state):
106 raise NotImplementedError("Abstract")
110 raise NotImplementedError("Abstract")
113 class MasterStateMachine(StateMachine):
117 self._state = self.STATE_ACTIVE
119 def append_machine(self, machine):
120 self._machines.append(machine)
123 # Confirm we are all on the same page
124 for machine in self._machines:
125 machine.set_state(self._state)
126 for machine in self._machines:
130 for machine in self._machines:
134 for machine in self._machines:
137 def set_state(self, state):
139 for machine in self._machines:
140 machine.set_state(state)
147 class UpdateStateMachine(StateMachine):
148 # Making sure the it is initialized is finicky, be careful
154 def __init__(self, updateItems, name=""):
156 self._updateItems = updateItems
158 self._state = self.STATE_ACTIVE
159 self._timeoutId = None
161 self._strategies = {}
162 self._callback = coroutines.func_sink(
163 coroutines.expand_positional(
164 self._request_reset_timers
168 def set_state_strategy(self, state, strategy):
169 self._strategies[state] = strategy
172 assert self._timeoutId is None
173 for strategy in self._strategies.itervalues():
174 strategy.initialize_state()
175 if self._strategy.timeout != self.INFINITE_PERIOD:
176 self._timeoutId = gobject.idle_add(self._on_timeout)
177 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
183 assert self._timeoutId is None
184 self._callback = None
186 def set_state(self, newState):
187 if self._state == newState:
189 oldState = self._state
190 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
192 self._state = newState
199 def reset_timers(self):
203 def request_reset_timers(self):
204 return self._callback
208 return self._strategies[self._state]
210 @gtk_toolbox.log_exception(_moduleLogger)
211 def _request_reset_timers(self, *args):
214 def _set_initial_period(self):
215 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later
217 def _schedule_update(self):
218 assert self._timeoutId is None
219 self._strategy.increment_state()
220 nextTimeout = self._strategy.timeout
221 if nextTimeout != self.INFINITE_PERIOD:
222 self._timeoutId = gobject.timeout_add(nextTimeout, self._on_timeout)
223 _moduleLogger.debug("%s Next update in %s ms" % (self._name, nextTimeout, ))
225 def _stop_update(self):
226 if self._timeoutId is None:
228 gobject.source_remove(self._timeoutId)
229 self._timeoutId = None
231 def _reset_timers(self):
232 if self._timeoutId is None:
233 return # not started yet
235 self._strategy.initialize_state()
236 self._schedule_update()
238 @gtk_toolbox.log_exception(_moduleLogger)
239 def _on_timeout(self):
240 _moduleLogger.info("%s Update" % (self._name))
241 self._timeoutId = None
242 self._schedule_update()
243 for item in self._updateItems:
245 item.update(force=True)
247 _moduleLogger.exception("Update failed for %r" % item)
248 return False # do not continue