changeset 1959:5633155c0300

sequencer doesn't hang so much, and it logs when you go from error->working Ignore-this: 865392a34c76f0efaf18cb7f3d2c0c26
author Drew Perttula <drewp@bigasterisk.com>
date Thu, 06 Jun 2019 11:10:55 +0000
parents e0dd7fa987ea
children 8e2d456b3612
files light9/effect/sequencer.html light9/effect/sequencer.py
diffstat 2 files changed, 73 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/light9/effect/sequencer.html	Thu Jun 06 10:02:19 2019 +0000
+++ b/light9/effect/sequencer.html	Thu Jun 06 11:10:55 2019 +0000
@@ -28,7 +28,10 @@
          }
          td {
              white-space: nowrap;
-             padding: 0 20px;
+             padding: 0 10px;
+             vertical-align: top;
+             vertical-align: top;
+             text-align: start;
          }
          tr.active { background: #151515; }
          .inactive > * { opacity: .5; }
@@ -56,16 +59,6 @@
         <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 
         <h1>Sequencer <a href="stats/">[stats]</a></h1>
-
-        <p>fps={{report.recentFps}}</p>
-        
-        <p>Recent frame times (best to worst): 
-        <div class="chart">
-         <template is="dom-repeat" items="{{report.recentDeltasStyle}}">
-           <div style$="{{item}}"></div>
-         </template>
-        </div>
-        </p>
         
         <h2>Song</h2>
 
@@ -92,10 +85,12 @@
               </td>  
               <td>
                 <template is="dom-repeat" items="{{item.effectSettingsPairs}}">
+                  <div>
                   <span class="effectSetting">
                     <resource-display graph="{{graph}}" uri="{{item.effectAttr}}"></resource-display>:
                     <span class="number">{{item.value}}</span>
                   </span>
+                  </div>
                 </template>
               </td>
               <td>
--- a/light9/effect/sequencer.py	Thu Jun 06 10:02:19 2019 +0000
+++ b/light9/effect/sequencer.py	Thu Jun 06 11:10:55 2019 +0000
@@ -6,6 +6,7 @@
 from rdflib import URIRef
 from twisted.internet import reactor
 from twisted.internet import defer
+from twisted.internet.defer import Deferred, inlineCallbacks
 from twisted.internet.inotify import INotify
 from twisted.python.filepath import FilePath
 import cyclone.sse
@@ -32,9 +33,8 @@
     '/update/',
     scales.PmfStat('s0_getMusic', recalcPeriod=1),
     scales.PmfStat('s1_eval', recalcPeriod=1),
-    scales.PmfStat('s2_sendToWeb', recalcPeriod=1),
+    #scales.PmfStat('s3_send_client', recalcPeriod=1),
     scales.PmfStat('s3_send', recalcPeriod=1),
-    scales.PmfStat('sendPhase', recalcPeriod=1),
     scales.PmfStat('updateLoopLatency', recalcPeriod=1),
     scales.DoubleStat('updateLoopLatencyGoal'),
     scales.RecentFpsStat('updateFps'),
@@ -160,7 +160,7 @@
 
     def __init__(self,
                  graph: SyncedGraph,
-                 sendToCollector: Callable[[DeviceSettings], defer.Deferred],
+                 sendToCollector: Callable[[DeviceSettings], Deferred],
                  fps=40):
         self.graph = graph
         self.fps = fps
@@ -175,11 +175,16 @@
         self.notes: Dict[Song, List[Note]] = {}  # song: [notes]
         self.simpleOutputs = SimpleOutputs(self.graph)
         self.graph.addHandler(self.compileGraph)
+        self.lastLoopSucceeded = False
+
+        self.codeWatcher = CodeWatcher(onChange=self.onCodeChange)
         self.updateLoop()
 
-        self.codeWatcher = CodeWatcher(
-            onChange=lambda: self.graph.addHandler(self.compileGraph))
-
+    def onCodeChange(self):
+        log.debug('seq.onCodeChange')
+        self.graph.addHandler(self.compileGraph)
+        #self.updateLoop()
+        
     @compileStats.graph.time()
     def compileGraph(self) -> None:
         """rebuild our data from the graph"""
@@ -192,74 +197,75 @@
 
     @compileStats.song.time()
     def compileSong(self, song: Song) -> None:
+        anyErrors = False
         self.notes[song] = []
         for note in self.graph.objects(song, L9['note']):
-            self.notes[song].append(
-                Note(self.graph, NoteUri(note), effecteval, self.simpleOutputs))
+            try:
+                n = Note(self.graph, NoteUri(note), effecteval, self.simpleOutputs)
+            except Exception:
+                log.warn(f"failed to build Note {note} - skipping")
+                anyErrors = True
+                continue
+            self.notes[song].append(n)
+        if not anyErrors:
+            log.info('built all notes')
 
+    @inlineCallbacks
     def updateLoop(self) -> None:
         frameStart = time.time()
-
-        d = self.update()
-        sendStarted = time.time()
-
-        def done(sec: float):
+        try:
+            sec = yield self.update()
+        except Exception as e:
+            self.lastLoopSucceeded = False
+            traceback.print_exc()
+            log.warn('updateLoop: %r', e)
+            reactor.callLater(1, self.updateLoop)
+        else:
             took = time.time() - frameStart
-            delay = max(0, 1 / self.fps - took)
             updateStats.updateLoopLatency = took
 
-            # time to send to collector, reported by collector_client
-            if isinstance(
-                    sec,
-                    float):  # sometimes None, not sure why, and neither is mypy
-                updateStats.s3_send = sec
-
-            # time to send to collector, measured in this function,
-            # from after sendToCollector returned its deferred until
-            # when the deferred was called.
-            updateStats.sendPhase = time.time() - sendStarted
+            if not self.lastLoopSucceeded:
+                log.info('Sequencer.update is working')
+                self.lastLoopSucceeded = True
+        
+            delay = max(0, 1 / self.fps - took)
             reactor.callLater(delay, self.updateLoop)
 
-        def err(e):
-            log.warn('updateLoop: %r', e)
-            reactor.callLater(2, self.updateLoop)
-
-        d.addCallbacks(done, err)
+    @updateStats.updateFps.rate()
+    @inlineCallbacks
+    def update(self) -> Deferred:
+        with updateStats.s0_getMusic.time():
+            musicState = self.music.getLatest()
+            if not musicState.get('song') or not isinstance(
+                    musicState.get('t'), float):
+                return defer.succeed(0.0)
+            song = Song(URIRef(musicState['song']))
+            dispatcher.send('state',
+                            update={
+                                'song': str(song),
+                                't': musicState['t']
+                            })
 
-    @updateStats.updateFps.rate()
-    def update(self) -> defer.Deferred:
-        try:
-            with updateStats.s0_getMusic.time():
-                musicState = self.music.getLatest()
-                if not musicState.get('song') or not isinstance(
-                        musicState.get('t'), float):
-                    return defer.succeed(0.0)
-                song = Song(URIRef(musicState['song']))
-                dispatcher.send('state',
-                                update={
-                                    'song': str(song),
-                                    't': musicState['t']
-                                })
+        with updateStats.s1_eval.time():
+            settings = []
+            songNotes = sorted(self.notes.get(song, []),
+                               key=lambda n: n.uri)
+            noteReports = []
+            for note in songNotes:
+                s, report = note.outputSettings(musicState['t'])
+                noteReports.append(report)
+                settings.append(s)
+            devSettings = DeviceSettings.fromList(self.graph, settings)
 
-            with updateStats.s1_eval.time():
-                settings = []
-                songNotes = sorted(self.notes.get(song, []),
-                                   key=lambda n: n.uri)
-                noteReports = []
-                for note in songNotes:
-                    s, report = note.outputSettings(musicState['t'])
-                    noteReports.append(report)
-                    settings.append(s)
-                devSettings = DeviceSettings.fromList(self.graph, settings)
+        dispatcher.send('state', update={'songNotes': noteReports})
+
+        with updateStats.s3_send.time(): # our measurement
+            sendSecs = yield self.sendToCollector(devSettings)
 
-            with updateStats.s2_sendToWeb.time():
-                dispatcher.send('state', update={'songNotes': noteReports})
-
-            return self.sendToCollector(devSettings)
-        except Exception:
-            traceback.print_exc()
-            raise
-
+        # sendToCollector's own measurement.
+        # (sometimes it's None, not sure why, and neither is mypy)
+        #if isinstance(sendSecs, float):
+        #    updateStats.s3_send_client = sendSecs
 
 class Updates(cyclone.sse.SSEHandler):