1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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 = {}
|