from transitions.extensions import HierarchicalMachine as Machine
-from . import debug_print, error_print
+from .helpers import debug_print, error_print
from . import actions
class Action:
'failed',
{
'name': 'loaded',
- 'children': ['running']
- }
+ 'children': ['stopped', 'running']
+ },
+ 'destroyed'
]
TRANSITIONS = [
},
{
'trigger': 'fail',
- 'source': 'loading',
+ 'source': ['loading', 'loaded'],
'dest': 'failed',
- 'after': 'poll_loaded'
},
{
'trigger': 'success',
'source': 'loading',
- 'dest': 'loaded',
- 'after': 'poll_loaded'
+ 'dest': 'loaded_stopped',
},
{
- 'trigger': 'run',
+ 'trigger': 'reload',
'source': 'loaded',
+ 'dest': 'loading',
+ },
+ {
+ 'trigger': 'run',
+ 'source': 'loaded_stopped',
'dest': 'loaded_running',
'after': 'finish_action',
- # if a child has no transitions, then it is bubbled to the parent,
- # and we don't want that. Not useful in that machine precisely.
- 'conditions': ['is_loaded']
},
{
'trigger': 'finish_action',
'source': 'loaded_running',
- 'dest': 'loaded'
+ 'dest': 'loaded_stopped'
+ },
+ {
+ 'trigger': 'destroy',
+ 'source': '*',
+ 'dest': 'destroyed'
}
]
def __init__(self, action, key, **kwargs):
Machine(model=self, states=self.STATES,
transitions=self.TRANSITIONS, initial='initial',
- ignore_invalid_triggers=True, queued=True)
+ ignore_invalid_triggers=True, queued=True,
+ after_state_change=self.notify_state_change)
self.action = action
self.key = key
def is_loaded_or_failed(self):
return self.is_loaded(allow_substates=True) or self.is_failed()
- def callback_music_loaded(self, success):
- if success:
- self.success()
- else:
+ def callback_music_state(self, new_state):
+ # If a music gets unloaded while the action is loaded_running and
+ # depending on the music, it won't be able to do the finish_action.
+ # Can that happen?
+ # a: play 'mp3';
+ # z: wait 'mp3';
+ # e: pause 'mp3';
+ # r: stop 'mp3'; unload_music 'mp3'
+ if new_state == 'failed':
self.fail()
+ elif self.is_loaded(allow_substates=True) and\
+ new_state in ['initial', 'loading']:
+ self.reload(reloading=True)
+ elif self.is_loading() and new_state.startswith('loaded_'):
+ self.success()
# Machine states / events
- def on_enter_loading(self):
+ def on_enter_loading(self, reloading=False):
+ if reloading:
+ return
if hasattr(actions, self.action):
- if 'music' in self.arguments:
- self.arguments['music'].subscribe_loaded(
- self.callback_music_loaded)
+ if 'music' in self.arguments and\
+ self.action not in ['unload_music', 'load_music']:
+ self.arguments['music'].subscribe_state_change(
+ self.callback_music_state)
else:
self.success()
else:
getattr(actions, self.action).run(self,
key_start_time=key_start_time, **self.arguments)
- def poll_loaded(self):
- self.key.callback_action_ready(self,
- self.is_loaded(allow_substates=True))
+ def on_enter_destroyed(self):
+ if 'music' in self.arguments:
+ self.arguments['music'].unsubscribe_state_change(
+ self.callback_music_state)
+
+ def notify_state_change(self, *args, **kwargs):
+ self.key.callback_action_state_changed()
# This one cannot be in the Machine state since it would be queued to run
# *after* the wait is ended...
return getattr(getattr(actions, self.action), 'interrupt')(
self, **self.arguments)
+ def pause(self):
+ if getattr(actions, self.action, None) and\
+ hasattr(getattr(actions, self.action), 'pause'):
+ return getattr(getattr(actions, self.action), 'pause')(
+ self, **self.arguments)
+
+ def unpause(self):
+ if getattr(actions, self.action, None) and\
+ hasattr(getattr(actions, self.action), 'unpause'):
+ return getattr(getattr(actions, self.action), 'unpause')(
+ self, **self.arguments)
+
+ def reset(self):
+ if getattr(actions, self.action, None) and\
+ hasattr(getattr(actions, self.action), 'reset'):
+ return getattr(getattr(actions, self.action), 'reset')(
+ self, **self.arguments)
+
# Helpers
def music_list(self, music):
if music is not None:
return getattr(actions, self.action)\
.description(self, **self.arguments)
else:
- return "unknown action {}".format(self.action)
+ return _("unknown action {}").format(self.action)