#!/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 = {}