From fff5e34c9864532b5e38e70b658eccb0ff35d1d3 Mon Sep 17 00:00:00 2001 From: Ben Connors Date: Thu, 24 Jan 2019 16:35:21 -0500 Subject: A bunch of changes - Begin work on simple rendering backend - Define lighting output interface - Cache hash() value on functions - Add unique identifier for each audio cue --- render.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 render.py (limited to 'render.py') diff --git a/render.py b/render.py new file mode 100644 index 0000000..833b5f5 --- /dev/null +++ b/render.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +import queue +import time +import threading + +from .audio import DefaultAudioPlayer, AudioPlayer +from .output import LightingOutput +from .workspace import SHOW, CHASER, Advanceable + +class FunctionQueue: + def after(self, t: float, f: callable): + """Run the given function after t milliseconds.""" + self.queue.put((time.monotonic() + t/1000, f)) + + def start(self): + """Run until the queue is empty.""" + while not self.queue.empty(): + t, f = self.queue.get() + time.sleep(max(0, time.monotonic() - t)) + f() + + def __init__(self): + self.queue = queue.SimpleQueue() + +class Renderer: + """Basic renderer for functions. + + Supports live-rendering Chasers and Shows. + + Instances of this class are NOT thread-safe, with the exception of the advance() method, + which may be called from other threads. + """ + def start(self): + if self.start_time != -1: + raise ValueError("Already running") + self.f.after(0, self.render_step) + self.f.start() + self.nx = None + self.data = None + self.vals = {} + + def render_step(self): + """Output the current step and render the next one.""" + if self.nx not in (None, -1): + self.fq.after((max(self.minnx, self.nx), self.render_step)) + elif self.nx is None: + self.start_time = time.monotonic() + + self.lo.set_values(tuple(self.values.items())) + + with self.data_lock: + t = 1000*(int((time.monotonic() - self.start_time)/1000 + 1) + self.nx) + vals, acues, self.nx, self.data = self.f.render(t) + for c, v in vals: + self.values[c] = v + + def advance(self): + """Advance the function, if possible. + + It is not an error to call this function when dealing with non-Advanceable toplevel + functions; this will just do nothing. + """ + with self.data_lock: + if self.start_time == -1: + raise ValueError("Cannot advance a function that has not been started!") + if issubclass(type(self.f), Advanceable): + t = 1000*int(self.monotonic() - self.start_time) + self.data = self.f.advance(self.data, time.monotonic() - self.start_time) + *_, self.data = self.f.render(t) + + def __init__(self, f, lo:LightingOutput, ao: AudioPlayer=DefaultAudioPlayer, minnx=-1): + if f.type not in (SHOW, CHASER): + raise ValueError("Only Shows and Chasers may be used as toplevel functions") + self.start_time = -1 + self.f = f + self.fq = FunctionQueue() + self.minnx = minnx + self.nx = None + self.data = None + self.data_lock = threading.Lock() + self.values = {c: 0 for c in self.f.scope} + self.lo = lo + self.ao = ao + self.aplayers = {} -- cgit v1.2.3