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 class NopStateStrategy(object):
36 def initialize_state(self):
39 def increment_state(self):
44 return UpdateStateMachine.INFINITE_PERIOD
47 class ConstantStateStrategy(object):
49 def __init__(self, timeout):
50 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
51 self._timeout = timeout
53 def initialize_state(self):
56 def increment_state(self):
64 class GeometricStateStrategy(object):
66 def __init__(self, init, min, max):
67 assert 0 < init and init < max and init != UpdateStateMachine.INFINITE_PERIOD
68 assert 0 < min and min != UpdateStateMachine.INFINITE_PERIOD
69 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
75 def initialize_state(self):
76 self._current = self._min / 2
78 def increment_state(self):
79 if self._max == UpdateStateMachine.INFINITE_PERIOD:
82 self._current = min(2 * self._current, self._max - self._init)
86 return self._init + self._current
89 class StateMachine(object):
91 STATE_ACTIVE = 0, "active"
92 STATE_IDLE = 1, "idle"
96 raise NotImplementedError("Abstract")
99 raise NotImplementedError("Abstract")
102 raise NotImplementedError("Abstract")
104 def set_state(self, state):
105 raise NotImplementedError("Abstract")
109 raise NotImplementedError("Abstract")
112 class MasterStateMachine(StateMachine):
116 self._state = self.STATE_ACTIVE
118 def append_machine(self, machine):
119 self._machines.append(machine)
122 # Confirm we are all on the same page
123 for machine in self._machines:
124 machine.set_state(self._state)
125 for machine in self._machines:
129 for machine in self._machines:
133 for machine in self._machines:
136 def set_state(self, state):
138 for machine in self._machines:
139 machine.set_state(state)
146 class UpdateStateMachine(StateMachine):
147 # Making sure the it is initialized is finicky, be careful
153 def __init__(self, updateItems, name=""):
155 self._updateItems = updateItems
157 self._state = self.STATE_ACTIVE
158 self._timeoutId = None
160 self._strategies = {}
161 self._callback = coroutines.func_sink(
162 coroutines.expand_positional(
163 self._request_reset_timers
167 def set_state_strategy(self, state, strategy):
168 self._strategies[state] = strategy
171 assert self._timeoutId is None
172 for strategy in self._strategies.itervalues():
173 strategy.initialize_state()
174 if self._strategy.timeout != self.INFINITE_PERIOD:
175 self._timeoutId = gobject.idle_add(self._on_timeout)
176 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
179 _moduleLogger.info("%s Stopping 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):
200 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
204 def request_reset_timers(self):
205 return self._callback
209 return self._strategies[self._state]
211 @gtk_toolbox.log_exception(_moduleLogger)
212 def _request_reset_timers(self, *args):
215 def _set_initial_period(self):
216 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later
218 def _schedule_update(self):
219 assert self._timeoutId is None
220 self._strategy.increment_state()
221 nextTimeout = self._strategy.timeout
222 if nextTimeout != self.INFINITE_PERIOD:
223 self._timeoutId = gobject.timeout_add(nextTimeout, self._on_timeout)
224 _moduleLogger.info("%s Next update in %s ms" % (self._name, nextTimeout, ))
226 def _stop_update(self):
227 if self._timeoutId is None:
229 gobject.source_remove(self._timeoutId)
230 self._timeoutId = None
232 def _reset_timers(self):
233 if self._timeoutId is None:
234 return # not started yet
236 self._strategy.initialize_state()
237 self._schedule_update()
239 @gtk_toolbox.log_exception(_moduleLogger)
240 def _on_timeout(self):
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