4 @todo Look into supporting more states
11 import util.go_utils as gobject_utils
12 import util.coroutines as coroutines
16 _moduleLogger = logging.getLogger("gvoice.state_machine")
19 def to_milliseconds(**kwd):
20 if "milliseconds" in kwd:
21 return kwd["milliseconds"]
22 elif "seconds" in kwd:
23 return kwd["seconds"] * 1000
24 elif "minutes" in kwd:
25 return kwd["minutes"] * 1000 * 60
27 return kwd["hours"] * 1000 * 60 * 60
28 raise KeyError("Unknown arg: %r" % kwd)
31 def to_seconds(**kwd):
32 if "milliseconds" in kwd:
33 return kwd["milliseconds"] / 1000
34 elif "seconds" in kwd:
36 elif "minutes" in kwd:
37 return kwd["minutes"] * 60
39 return kwd["hours"] * 60 * 60
40 raise KeyError("Unknown arg: %r" % kwd)
43 class NopStateStrategy(object):
48 def initialize_state(self):
51 def increment_state(self):
56 return UpdateStateMachine.INFINITE_PERIOD
59 class ConstantStateStrategy(object):
61 def __init__(self, timeout):
62 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
63 self._timeout = timeout
65 def initialize_state(self):
68 def increment_state(self):
76 class GeometricStateStrategy(object):
78 def __init__(self, init, min, max):
79 assert 0 < init and init < max and init != UpdateStateMachine.INFINITE_PERIOD
80 assert 0 < min and min != UpdateStateMachine.INFINITE_PERIOD
81 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
87 def initialize_state(self):
88 self._current = self._min / 2
90 def increment_state(self):
91 if self._max == UpdateStateMachine.INFINITE_PERIOD:
94 self._current = min(2 * self._current, self._max - self._init)
98 return self._init + self._current
101 class StateMachine(object):
103 STATE_ACTIVE = 0, "active"
104 STATE_IDLE = 1, "idle"
108 raise NotImplementedError("Abstract")
111 raise NotImplementedError("Abstract")
114 raise NotImplementedError("Abstract")
116 def set_state(self, state):
117 raise NotImplementedError("Abstract")
121 raise NotImplementedError("Abstract")
124 class MasterStateMachine(StateMachine):
128 self._state = self.STATE_ACTIVE
130 def append_machine(self, machine):
131 self._machines.append(machine)
134 # Confirm we are all on the same page
135 for machine in self._machines:
136 machine.set_state(self._state)
137 for machine in self._machines:
141 for machine in self._machines:
145 for machine in self._machines:
148 def set_state(self, state):
150 for machine in self._machines:
151 machine.set_state(state)
158 class UpdateStateMachine(StateMachine):
159 # Making sure the it is initialized is finicky, be careful
165 def __init__(self, updateItems, name=""):
167 self._updateItems = updateItems
169 self._state = self.STATE_ACTIVE
170 self._timeoutId = None
172 self._strategies = {}
173 self._callback = coroutines.func_sink(
174 coroutines.expand_positional(
175 self._request_reset_timers
179 def set_state_strategy(self, state, strategy):
180 self._strategies[state] = strategy
183 assert self._timeoutId is None
184 for strategy in self._strategies.itervalues():
185 strategy.initialize_state()
186 if self._strategy.timeout != self.INFINITE_PERIOD:
187 self._timeoutId = gobject.idle_add(self._on_timeout)
188 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
191 _moduleLogger.info("%s Stopping State Machine" % (self._name, ))
195 assert self._timeoutId is None
196 self._callback = None
198 def set_state(self, newState):
199 if self._state == newState:
201 oldState = self._state
202 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
204 self._state = newState
211 def reset_timers(self):
212 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
216 def request_reset_timers(self):
217 return self._callback
221 return self._strategies[self._state]
223 @gtk_toolbox.log_exception(_moduleLogger)
224 def _request_reset_timers(self, *args):
227 def _schedule_update(self):
228 assert self._timeoutId is None
229 self._strategy.increment_state()
230 nextTimeout = self._strategy.timeout
231 if nextTimeout != self.INFINITE_PERIOD:
232 self._timeoutId = gobject_utils.timeout_add_seconds(nextTimeout, self._on_timeout)
233 _moduleLogger.info("%s Next update in %s ms" % (self._name, nextTimeout, ))
235 def _stop_update(self):
236 if self._timeoutId is None:
238 gobject.source_remove(self._timeoutId)
239 self._timeoutId = None
241 def _reset_timers(self):
242 if self._timeoutId is None:
243 return # not started yet
245 self._strategy.initialize_state()
246 self._schedule_update()
248 @gtk_toolbox.log_exception(_moduleLogger)
249 def _on_timeout(self):
250 self._timeoutId = None
251 self._schedule_update()
252 for item in self._updateItems:
254 item.update(force=True)
256 _moduleLogger.exception("Update failed for %r" % item)
257 return False # do not continue