Files @ 54cf7034bee0
Branch filter:

Location: light9/light9/effect/effecteval2.py

drewp@bigasterisk.com
EE don't abort compile on partial data
import traceback
import inspect
import logging
from dataclasses import dataclass
from typing import Callable, List, Optional

from rdfdb.syncedgraph.syncedgraph import SyncedGraph
from rdflib import RDF
from rdflib.term import Node

from light9.effect.effect_function_library import EffectFunctionLibrary
from light9.effect.settings import DeviceSettings, EffectSettings
from light9.namespaces import L9
from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectFunction, EffectUri, VTUnion)
from light9.typedgraph import typedValue

log = logging.getLogger('effecteval')


@dataclass
class Config:
    effectFunction: EffectFunction
    esettings: EffectSettings
    devSettings: Optional[DeviceSettings]  # the EffectSettings :effectAttr :devSettings item, if there was one
    func: Callable
    funcArgs: List[inspect.Parameter]


@dataclass
class EffectEval2:
    """Runs one effect code to turn EffectSettings (e.g. strength) into DeviceSettings"""
    graph: SyncedGraph
    uri: EffectUri
    lib: EffectFunctionLibrary

    config: Optional[Config] = None

    def __post_init__(self):
        self.graph.addHandler(self._compile)

    def _compile(self):
        self.config = None
        if not self.graph.contains((self.uri, RDF.type, L9['Effect'])):
            return

        try:
            effectFunction = typedValue(EffectFunction, self.graph, self.uri, L9['effectFunction'])
            effSets = []
            devSettings = None
            for s in self.graph.objects(self.uri, L9['setting']):
                attr = typedValue(EffectAttr, self.graph, s, L9['effectAttr'])
                if attr == L9['deviceSettings']:
                    value = typedValue(Node, self.graph, s, L9['value'])

                    rows = []
                    for ds in self.graph.objects(value, L9['setting']):
                        d = typedValue(DeviceUri, self.graph, ds, L9['device'])
                        da = typedValue(DeviceAttr, self.graph, ds, L9['deviceAttr'])
                        v = typedValue(VTUnion, self.graph, ds, L9['value'])
                        rows.append((d, da, v))
                    devSettings = DeviceSettings(self.graph, rows)
                else:
                    value = typedValue(VTUnion, self.graph, s, L9['value'])
                    effSets.append((self.uri, attr, value))
            esettings = EffectSettings(self.graph, effSets)

            try:
                effectFunction = typedValue(EffectFunction, self.graph, self.uri, L9['effectFunction'])
            except ValueError:
                raise ValueError(f'{self.uri} has no :effectFunction')
            func = self.lib.getFunc(effectFunction)

            # This should be in EffectFunctionLibrary
            funcArgs = list(inspect.signature(func).parameters.values())

            self.config = Config(effectFunction, esettings, devSettings, func, funcArgs)
        except Exception:
            log.error(f"while compiling {self.uri}")
            traceback.print_exc()

    def compute(self, songTime: float, inputs: EffectSettings) -> DeviceSettings:
        """
        calls our function using inputs (publishedAttr attrs, e.g. :strength)
        and effect-level settings including a special attr called :deviceSettings
        with DeviceSettings as its value
        """
        if self.config is None:
            return DeviceSettings(self.graph, [])

        c = self.config
        kw = {}
        for arg in c.funcArgs:
            if arg.annotation == DeviceSettings:
                v = c.devSettings
            elif arg.name == 'songTime':
                v = songTime
            else:
                eaForName = EffectAttr(L9[arg.name])
                v = self._getEffectAttrValue(eaForName, inputs)

            kw[arg.name] = v
        log.debug('calling %s with %s', c.func, kw)
        return c.func(**kw)

    def _getEffectAttrValue(self, attr: EffectAttr, inputs: EffectSettings) -> VTUnion:
        c = self.config
        if c is None:
            raise
        try:
            return inputs.getValue(self.uri, attr, defaultToZero=False)
        except KeyError:
            pass
        try:
            return c.esettings.getValue(self.uri, attr, defaultToZero=False)
        except KeyError:
            pass
        return self.lib.getDefaultValue(c.effectFunction, attr)