changeset 1108:4b542d321c8f

effectnode runs multiple lines of code in dependency order Ignore-this: 45e7a4f332c6b1e15eefb7bd8a3ed657
author Drew Perttula <drewp@bigasterisk.com>
date Tue, 10 Jun 2014 09:29:05 +0000
parents 512381de45bd
children 53528b34097f
files bin/effecteval light9/effecteval/effect.py light9/effecteval/effectloop.py light9/effecteval/test_effect.py pydeps
diffstat 5 files changed, 60 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/bin/effecteval	Tue Jun 10 08:48:34 2014 +0000
+++ b/bin/effecteval	Tue Jun 10 09:29:05 2014 +0000
@@ -104,8 +104,12 @@
     def updateClient(self):
         # todo: if client has dropped, abort and don't get any more
         # graph updates
-        self.sendMessage({'codeLines':
-                          list(self.graph.objects(self.uri, L9['code']))})
+
+        # EffectNode knows how to put them in order. Somehow this is
+        # not triggering an update when the order changes.
+        en = EffectNode(self.graph, self.uri)
+        codeLines = [c.code for c in en.codes]
+        self.sendMessage({'codeLines': codeLines})
         
     def connectionLost(self, reason):
         log.info("websocket closed")
--- a/light9/effecteval/effect.py	Tue Jun 10 08:48:34 2014 +0000
+++ b/light9/effecteval/effect.py	Tue Jun 10 09:29:05 2014 +0000
@@ -1,4 +1,5 @@
 import re, logging
+import toposort
 from rdflib import URIRef
 from light9.namespaces import L9, RDF
 from light9.curvecalc.curve import CurveResource
@@ -22,8 +23,9 @@
     def __init__(self, graph, code):
         self.graph, self.code = graph, code
 
-        self.outName, self.expr, self.resources = self._asPython()
+        self.outName, self.inExpr, self.expr, self.resources = self._asPython()
         self.pyResources = self._resourcesAsPython(self.resources)
+        self.possibleVars = self.findVars(self.inExpr)
 
     def _asPython(self):
         """
@@ -50,8 +52,15 @@
                     return 'curve(%s, t)' % v
             return v
         outExpr = re.sub(r'<(http\S*?)>', repl, expr)
-        return lname, outExpr, resources
+        return lname, expr, outExpr, resources
 
+    def findVars(self, expr):
+        """may return some more strings than just the vars"""
+        withoutUris = re.sub(r'<(http\S*?)>', 'None', expr)
+        tokens = set(re.findall(r'\b([a-zA-Z_]\w*)\b', withoutUris))
+        tokens.discard('None')
+        return tokens
+        
     def _uriIsCurve(self, uri):
         # this result could vary with graph changes (rare)
         return self.graph.contains((uri, RDF.type, L9['Curve']))
@@ -92,8 +101,21 @@
 
         self.codes = [CodeLine(self.graph, s) for s in codeStrs]
 
+        self.sortCodes()
+        
         self.otherFuncs = Effects.configExprGlobals()
-                
+
+    def sortCodes(self):
+        """put self.codes in a working evaluation order"""
+        codeFromOutput = dict((c.outName, c) for c in self.codes)
+        deps = {}
+        for c in self.codes:
+            outName = c.outName
+            inNames = c.possibleVars.intersection(codeFromOutput.keys())
+            inNames.discard(outName)
+            deps[outName] = inNames
+        self.codes = [codeFromOutput[n] for n in toposort.toposort_flatten(deps)]
+        
     def eval(self, songTime):
         ns = {'t': songTime}
         ns.update(self.otherFuncs)
@@ -101,13 +123,13 @@
         ns.update(dict(
             curve=lambda c, t: c.eval(t),
             ))
-        # loop over lines in order, merging in outputs 
-        # merge in named outputs from previous lines
 
         for c in self.codes:
             codeNs = ns.copy()
             codeNs.update(c.pyResources)
-            if c.outName == 'out':
-                out = eval(c.expr, codeNs)
-        return out
+            lineOut = eval(c.expr, codeNs)
+            ns[c.outName] = lineOut
+        if 'out' not in ns:
+            log.error("ran code for %s, didn't make an 'out' value", self.uri)
+        return ns['out']
 
--- a/light9/effecteval/effectloop.py	Tue Jun 10 08:48:34 2014 +0000
+++ b/light9/effecteval/effectloop.py	Tue Jun 10 09:29:05 2014 +0000
@@ -78,9 +78,6 @@
                         now = time.time()
                         if now > self.lastErrorLog + 5:
                             log.error("effect %s: %s" % (e.uri, exc))
-                            log.error("  expr: %s", e.code.expr)
-                            log.error("  resources: %r",
-                                      getattr(e, 'resourceMap', '?'))
                             self.lastErrorLog = now
                 out = Submaster.sub_maxes(*outSubs)
 
--- a/light9/effecteval/test_effect.py	Tue Jun 10 08:48:34 2014 +0000
+++ b/light9/effecteval/test_effect.py	Tue Jun 10 09:29:05 2014 +0000
@@ -10,43 +10,56 @@
     return 'curve' in uri
 
 @mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
+@mock.patch('light9.effecteval.effect.CodeLine._resourcesAsPython',
+            new=lambda self, r: self.expr)
 class TestAsPython(unittest.TestCase):
     def test_gets_lname(self):
         ec = CodeLine(graph=None, code='x = y+1')
-        self.assertEqual('x', ec._asPython()[0])
+        self.assertEqual('x', ec.outName)
 
     def test_gets_simple_code(self):
         ec = CodeLine(graph=None, code='x = y+1')
-        self.assertEqual('y+1', ec._asPython()[1])
-        self.assertEqual({}, ec._asPython()[2])
+        self.assertEqual('y+1', ec._asPython()[2])
+        self.assertEqual({}, ec._asPython()[3])
         
     def test_converts_uri_to_var(self):
         ec = CodeLine(graph=None, code='x = <http://example.com/>')
-        _, expr, uris = ec._asPython()
+        _, inExpr, expr, uris = ec._asPython()
         self.assertEqual('_res0', expr)
         self.assertEqual({'_res0': URIRef('http://example.com/')}, uris)
 
     def test_converts_multiple_uris(self):
         ec = CodeLine(graph=None, code='x = <http://example.com/> + <http://other>')
-        _, expr, uris = ec._asPython()
+        _, inExpr, expr, uris = ec._asPython()
         self.assertEqual('_res0 + _res1', expr)
         self.assertEqual({'_res0': URIRef('http://example.com/'),
                           '_res1': URIRef('http://other')}, uris)
         
     def test_doesnt_fall_for_brackets(self):
         ec = CodeLine(graph=None, code='x = 1<2>3< h')
-        _, expr, uris = ec._asPython()
+        _, inExpr, expr, uris = ec._asPython()
         self.assertEqual('1<2>3< h', expr)
         self.assertEqual({}, uris)
         
     def test_curve_uri_expands_to_curve_eval_func(self):
         ec = CodeLine(graph=None, code='x = <http://example/curve1>')
-        _, expr, uris = ec._asPython()
+        _, inExpr, expr, uris = ec._asPython()
         self.assertEqual('curve(_res0, t)', expr)
         self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
 
     def test_curve_doesnt_double_wrap(self):
         ec = CodeLine(graph=None, code='x = curve(<http://example/curve1>, t+.01)')
-        _, expr, uris = ec._asPython()
+        _, inExpr, expr, uris = ec._asPython()
         self.assertEqual('curve(_res0, t+.01)', expr)
         self.assertEqual({'_res0': URIRef('http://example/curve1')}, uris)
+
+@mock.patch('light9.effecteval.effect.CodeLine._uriIsCurve', new=isCurve)
+@mock.patch('light9.effecteval.effect.CodeLine._resourcesAsPython',
+            new=lambda self, r: self.expr)
+class TestPossibleVars(unittest.TestCase):
+    def test1(self):
+        self.assertEqual(set([]), CodeLine(None, 'a1 = 1').possibleVars)
+    def test2(self):
+        self.assertEqual(set(['a2']), CodeLine(None, 'a1 = a2').possibleVars)
+    def test3(self):
+        self.assertEqual(set(['a2', 'a3']), CodeLine(None, 'a1 = a2 + a3').possibleVars)
--- a/pydeps	Tue Jun 10 08:48:34 2014 +0000
+++ b/pydeps	Tue Jun 10 09:29:05 2014 +0000
@@ -20,4 +20,6 @@
 Pillow==2.4.0
 faulthandler==2.3
 treq==0.2.1
-mock==1.0.1
\ No newline at end of file
+mock==1.0.1
+toposort==1.0
+