diff options
author | Ben Connors <benconnors@outlook.com> | 2019-01-24 16:35:21 -0500 |
---|---|---|
committer | Ben Connors <benconnors@outlook.com> | 2019-01-24 19:52:33 -0500 |
commit | fff5e34c9864532b5e38e70b658eccb0ff35d1d3 (patch) | |
tree | 42913e78d056cda7d4a77d9d276ace9c649c0b5f /render.py | |
parent | bb9e61aaf7c86d27ef24cfc1c3d4b7f0baadbf89 (diff) |
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
Diffstat (limited to 'render.py')
-rw-r--r-- | render.py | 85 |
1 files changed, 85 insertions, 0 deletions
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 = {} |