summaryrefslogtreecommitdiff
path: root/render.py
diff options
context:
space:
mode:
Diffstat (limited to 'render.py')
-rw-r--r--render.py85
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 = {}