Mercurial > code > home > repos > light9
view light9/effecteval/effect.py @ 1252:fccb6ca6516d
currentSubLevel lets subs drive effects
Ignore-this: 13c5c1743c456386b8aca36a189d905b
author | drewp@bigasterisk.com |
---|---|
date | Sat, 13 Jun 2015 06:09:24 +0000 |
parents | cc94b5fc9205 |
children | 7817e1ef0ff0 |
line wrap: on
line source
import re, logging import toposort from rdflib import URIRef from light9.namespaces import L9, RDF from light9.curvecalc.curve import CurveResource from light9 import prof from light9 import Submaster from light9 import Effects # gets reload() later log = logging.getLogger('effect') # consider http://waxeye.org/ for a parser that can be used in py and js class CouldNotConvert(TypeError): pass class CodeLine(object): """code string is immutable""" def __init__(self, graph, code): self.graph, self.code = graph, code self.outName, self.inExpr, self.expr, self.resources = self._asPython() self.pyResources = self._resourcesAsPython(self.resources) self.possibleVars = self.findVars(self.inExpr) @prof.logTime def _asPython(self): """ out = sub(<uri1>, intensity=<curveuri2>) becomes 'out', 'sub(_u1, intensity=curve(_u2, t))', {'_u1': URIRef('uri1'), '_u2': URIRef('uri2')} """ lname, expr = [s.strip() for s in self.code.split('=', 1)] self.uriCounter = 0 resources = {} def alreadyInFunc(prefix, s, i): return i >= len(prefix) and s[i-len(prefix):i] == prefix def repl(m): v = '_res%s' % self.uriCounter self.uriCounter += 1 r = resources[v] = URIRef(m.group(1)) for uriTypeMatches, wrapFuncName, addlArgs in [ (self._uriIsCurve(r), 'curve', ', t'), (self._uriIsSub(r), 'currentSubLevel', ''), ]: if uriTypeMatches: if not alreadyInFunc(wrapFuncName + '(', m.string, m.start()): return '%s(%s%s)' % (wrapFuncName, v, addlArgs) return v outExpr = re.sub(r'<(http\S*?)>', repl, expr) 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'])) def _uriIsSub(self, uri): return self.graph.contains((uri, RDF.type, L9['Submaster'])) @prof.logTime def _resourcesAsPython(self, resources): """ mapping of the local names for uris in the code to high-level objects (Submaster, Curve) """ out = {} subs = prof.logTime(Submaster.get_global_submasters)(self.graph) for localVar, uri in resources.items(): for rdfClass in self.graph.objects(uri, RDF.type): if rdfClass == L9['Curve']: cr = CurveResource(self.graph, uri) # this is slow- pool these curves somewhere, maybe just with curveset prof.logTime(cr.loadCurve)() out[localVar] = cr.curve break elif rdfClass == L9['Submaster']: out[localVar] = subs.get_sub_by_uri(uri) break else: out[localVar] = CouldNotConvert(uri) break else: out[localVar] = CouldNotConvert(uri) return out class EffectNode(object): def __init__(self, graph, uri): self.graph, self.uri = graph, uri # this is not expiring at the right time, when an effect goes away self.graph.addHandler(self.prepare) @prof.logTime def prepare(self): log.info("prepare effect %s", self.uri) # maybe there can be multiple lines of code as multiple # objects here, and we sort them by dependencies codeStrs = list(self.graph.objects(self.uri, L9['code'])) if not codeStrs: raise ValueError("effect %s has no code" % self.uri) self.codes = [CodeLine(self.graph, s) for s in codeStrs] self.sortCodes() #reload(Effects) self.otherFuncs = prof.logTime(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 _currentSubSettingValues(self, sub): """what KC subSettings are setting levels right now?""" cs = self.graph.currentState with cs(tripleFilter=(None, L9['sub'], sub)) as g1: for subj in g1.subjects(L9['sub'], sub): with cs(tripleFilter=(subj, None, None)) as g2: if (subj, RDF.type, L9['SubSetting']) in g2: v = g2.value(subj, L9['level']).toPython() yield v def currentSubLevel(self, uri): """what's the max level anyone (probably KC) is holding this sub to right now?""" if isinstance(uri, Submaster.Submaster): # likely the uri was spotted and replaced uri = uri.uri if not isinstance(uri, URIRef): raise TypeError("got %r" % uri) foundLevels = list(self._currentSubSettingValues(uri)) if not foundLevels: return 0 return max(foundLevels) def eval(self, songTime): ns = {'t': songTime} ns.update(self.otherFuncs) ns.update(dict( curve=lambda c, t: c.eval(t), currentSubLevel=self.currentSubLevel, )) for c in self.codes: codeNs = ns.copy() codeNs.update(c.pyResources) try: lineOut = eval(c.expr, codeNs) except Exception as e: e.expr = c.expr raise e 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']