]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/commitdiff
Cleanup actions and subscribe to music events for loading
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Mon, 25 Jul 2016 15:43:47 +0000 (17:43 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Mon, 25 Jul 2016 17:40:48 +0000 (19:40 +0200)
helpers/action.py
helpers/mapping.py
helpers/music_file.py

index ec8fcb6f0b43748873eb2e8568589c62cfd36f58..a6c48e98d5140ca3460fe7103472f8cffbd9968a 100644 (file)
@@ -1,10 +1,11 @@
 import threading
 import time
 
-from . import debug_print
+from transitions.extensions import HierarchicalMachine as Machine
+from . import debug_print, error_print
 
 class Action:
-    action_types = [
+    ACTION_TYPES = [
         'command',
         'interrupt_wait',
         'pause',
@@ -17,40 +18,106 @@ class Action:
         'wait',
     ]
 
+    STATES = [
+        'initial',
+        'loading',
+        'failed',
+        {
+            'name': 'loaded',
+            'children': ['running']
+        }
+    ]
+
+    TRANSITIONS = [
+        {
+            'trigger': 'load',
+            'source': 'initial',
+            'dest': 'loading'
+        },
+        {
+            'trigger': 'fail',
+            'source': 'loading',
+            'dest': 'failed'
+        },
+        {
+            'trigger': 'success',
+            'source': 'loading',
+            'dest': 'loaded'
+        },
+        {
+            'trigger': 'run',
+            'source': 'loaded',
+            'dest': 'loaded_running',
+            'after': 'finish_action'
+        },
+        {
+            'trigger': 'interrupt',
+            'source': 'loaded_running',
+            'dest': 'loaded',
+            'before': 'trigger_interrupt'
+        },
+        {
+            'trigger': 'finish_action',
+            'source': 'loaded_running',
+            'dest': 'loaded'
+        }
+    ]
+
     def __init__(self, action, key, **kwargs):
-        if action in self.action_types:
-            self.action = action
-        else:
-            raise Exception("Unknown action {}".format(action))
+        Machine(model=self, states=self.STATES,
+                transitions=self.TRANSITIONS, initial='initial',
+                ignore_invalid_triggers=True, queued=True)
 
+        self.action = action
         self.key = key
         self.mapping = key.parent
         self.arguments = kwargs
         self.sleep_event = None
+        self.waiting_music = None
+        self.load()
 
     def ready(self):
-        if 'music' in self.arguments:
-            return self.arguments['music'].is_loaded(allow_substates=True)
+        return self.is_loaded(allow_substates=True)
+
+    def callback_loaded(self, success):
+        if success:
+            self.success()
+        else:
+            self.fail()
+
+    # Machine states / events
+    def on_enter_loading(self):
+        if self.action in self.ACTION_TYPES:
+            if 'music' in self.arguments:
+                self.arguments['music'].subscribe_loaded(self.callback_loaded)
+            else:
+                self.success()
         else:
-            return True
+            error_print("Unknown action {}".format(self.action))
+            self.fail()
+
 
-    def run(self):
+    def on_enter_loaded_running(self):
         debug_print(self.description())
         getattr(self, self.action)(**self.arguments)
 
-    def description(self):
-        return getattr(self, self.action + "_print")(**self.arguments)
-
-    def interrupt(self):
+    def trigger_interrupt(self):
         if getattr(self, self.action + "_interrupt", None):
             return getattr(self, self.action + "_interrupt")(**self.arguments)
 
+    # Helpers
     def music_list(self, music):
         if music is not None:
             return [music]
         else:
             return self.mapping.open_files.values()
 
+    def description(self):
+        if getattr(self, self.action + "_print", None):
+            return getattr(self, self.action + "_print")(**self.arguments)
+        else:
+            return "unknown action {}".format(self.action)
+
     # Actions
     def command(self, command="", **kwargs):
         # FIXME: todo
@@ -104,6 +171,7 @@ class Action:
                 music.stop(fade_out=fade_out)
 
         if previous is not None:
+            self.waiting_music = previous
             previous.stop(
                     fade_out=fade_out,
                     wait=wait,
@@ -254,10 +322,14 @@ class Action:
 
         return message
 
-    # Interruptions
+    # Interruptions (only for non-"atomic" actions)
     def wait_interrupt(self, duration=0, music=None, **kwargs):
         if self.sleep_event is not None:
             self.sleep_event.set()
         if music is not None:
             music.wait_event.set()
 
+    def stop_interrupt(self, music=None, fade_out=0, wait=False,
+            set_wait_id=None, **kwargs):
+        if self.waiting_music is not None:
+            self.waiting_music.wait_event.set()
index c2a94e67baa52f3bfe4b06eccb78d88741805789..b71f3fe5869cf550d8df5fc12d9e3b455465cc20 100644 (file)
@@ -178,8 +178,8 @@ class Mapping(RelativeLayout):
                                         **config['music_properties'][filename])
                             else:
                                 seen_files[filename] = MusicFile(
-                                        self,
-                                        filename)
+                                        filename,
+                                        self)
 
                         if filename not in key_properties[mapped_key]['files']:
                             key_properties[mapped_key]['files'] \
index ccf60ce5eb8874f5b68dc81d48a89b9e86fd8626..aeba1b9912fd9d2aa74da3fddd8683fc9e21fb12 100644 (file)
@@ -32,7 +32,8 @@ class MusicFile:
         {
             'trigger': 'load',
             'source': 'initial',
-            'dest': 'loading'
+            'dest': 'loading',
+            'after': 'poll_loaded'
         },
         {
             'trigger': 'fail',
@@ -68,7 +69,8 @@ class MusicFile:
             'trigger': 'stopped',
             'source': '*',
             'dest': 'loaded',
-            'before': 'trigger_stopped_events'
+            'before': 'trigger_stopped_events',
+            'conditions': ['is_in_use']
         }
     ]
 
@@ -77,6 +79,7 @@ class MusicFile:
                 transitions=self.TRANSITIONS, initial='initial',
                 ignore_invalid_triggers=True)
 
+        self.loaded_callbacks = []
         self.mapping = mapping
         self.filename = filename
         self.name = name or filename
@@ -230,6 +233,21 @@ class MusicFile:
         self.wait_event.clear()
         self.wait_event.wait()
 
+    # Let other subscribe for an event when they are ready
+    def subscribe_loaded(self, callback):
+        with file_lock:
+            if self.is_loaded(allow_substates=True):
+                callback(True)
+            elif self.is_failed():
+                callback(False)
+            else:
+                self.loaded_callbacks.append(callback)
+
+    def poll_loaded(self):
+        for callback in self.loaded_callbacks:
+            callback(self.is_loaded())
+        self.loaded_callbacks = []
+
     # Callbacks
     def finished_callback(self):
         self.stopped()