fix: pack frame at render time, send without POLLOUT dependency
Validate and Test / validate-patches (push) Failing after 13s
Validate and Test / validate-patches (push) Failing after 13s
- pending_bytes stores already-packed msgpack, sendable immediately - Try sendall() directly every iteration — no POLLOUT gate - Refresh path resends last_sent_params (not raw frame tuple) - Router socket uses settimeout instead of setblocking for simpler I/O
This commit is contained in:
+30
-52
@@ -12,15 +12,13 @@ ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock")
|
||||
COLS = 13
|
||||
ROWS = 8
|
||||
|
||||
REFRESH_INTERVAL = 0.2 # 5 FPS background refresh (catches dropped frames)
|
||||
REFRESH_INTERVAL = 0.2
|
||||
|
||||
STATS_FILE = "/tmp/viz-stats.json"
|
||||
_poll = select.poll()
|
||||
POLLIN = select.POLLIN
|
||||
POLLOUT = select.POLLOUT
|
||||
|
||||
SOCK_BUFFER = 4096
|
||||
ROUTER_RECONNECT_DELAY = 0.1
|
||||
|
||||
|
||||
def pack_frame(pixels):
|
||||
@@ -29,8 +27,11 @@ def pack_frame(pixels):
|
||||
for col in range(COLS):
|
||||
if pixels[row][col]:
|
||||
bits |= 1 << (row * COLS + col)
|
||||
return (bits & 0xFFFFFFFF, (bits >> 32) & 0xFFFFFFFF,
|
||||
(bits >> 64) & 0xFFFFFFFF, (bits >> 96) & 0xFFFFFFFF)
|
||||
w0 = bits & 0xFFFFFFFF
|
||||
w1 = (bits >> 32) & 0xFFFFFFFF
|
||||
w2 = (bits >> 64) & 0xFFFFFFFF
|
||||
w3 = (bits >> 96) & 0xFFFFFFFF
|
||||
return [str(w0), str(w1), str(w2), str(w3)]
|
||||
|
||||
|
||||
def render(active_notes):
|
||||
@@ -46,7 +47,7 @@ def render(active_notes):
|
||||
|
||||
def connect_router():
|
||||
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
router.setblocking(False)
|
||||
router.settimeout(0.3)
|
||||
try:
|
||||
router.connect(ROUTER_SOCK)
|
||||
except OSError:
|
||||
@@ -54,19 +55,6 @@ def connect_router():
|
||||
return router
|
||||
|
||||
|
||||
def send_frame(router, packer, msg_id, frame):
|
||||
if frame is None:
|
||||
return msg_id
|
||||
msg_id = (msg_id + 1) % 65535
|
||||
params = [str(v) for v in frame]
|
||||
req = packer.pack([0, msg_id, "draw_frame", params])
|
||||
try:
|
||||
router.sendall(req)
|
||||
except (BlockingIOError, OSError):
|
||||
pass
|
||||
return msg_id
|
||||
|
||||
|
||||
def main():
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
@@ -77,18 +65,15 @@ def main():
|
||||
packer = msgpack.Packer()
|
||||
|
||||
_poll.register(sock, POLLIN)
|
||||
router_fd = router.fileno()
|
||||
_poll.register(router, POLLIN | POLLOUT)
|
||||
|
||||
active_notes = set()
|
||||
last_sent_frame = None
|
||||
pending_frame = None
|
||||
last_sent_params = None
|
||||
pending_bytes = None
|
||||
buf = b""
|
||||
msg_id = 0
|
||||
|
||||
frames_sent = 0
|
||||
frames_dropped = 0
|
||||
router_errors = 0
|
||||
|
||||
last_refresh = time.monotonic()
|
||||
stats_last_write = time.monotonic()
|
||||
@@ -100,14 +85,11 @@ def main():
|
||||
now = time.monotonic()
|
||||
refresh_due = (now - last_refresh) >= REFRESH_INTERVAL
|
||||
|
||||
timeout = 5 # ms
|
||||
|
||||
try:
|
||||
events = _poll.poll(timeout)
|
||||
events = _poll.poll(5)
|
||||
except OSError:
|
||||
events = []
|
||||
|
||||
# Process UDP
|
||||
for fd, flags in events:
|
||||
if fd == sock.fileno() and (flags & POLLIN):
|
||||
try:
|
||||
@@ -137,38 +119,34 @@ def main():
|
||||
changed = True
|
||||
if changed:
|
||||
events_processed += 1
|
||||
frame = render(active_notes)
|
||||
if frame != last_sent_frame:
|
||||
pending_frame = frame
|
||||
params = render(active_notes)
|
||||
if params != last_sent_params:
|
||||
msg_id = (msg_id + 1) % 65535
|
||||
pending_bytes = packer.pack([0, msg_id, "draw_frame", params])
|
||||
last_sent_params = params
|
||||
|
||||
# Process router readable
|
||||
if fd == router_fd and (flags & POLLIN):
|
||||
try:
|
||||
while True:
|
||||
d = router.recv(SOCK_BUFFER)
|
||||
if not d:
|
||||
break
|
||||
except BlockingIOError:
|
||||
pass
|
||||
|
||||
# Router writable: send pending frame immediately
|
||||
if pending_frame is not None:
|
||||
if any(fd == router_fd and (flags & POLLOUT) for fd, flags in events):
|
||||
msg_id = send_frame(router, packer, msg_id, pending_frame)
|
||||
last_sent_frame = pending_frame
|
||||
pending_frame = None
|
||||
# Try to send pending frame — no POLLOUT dependency
|
||||
if pending_bytes is not None:
|
||||
try:
|
||||
router.sendall(pending_bytes)
|
||||
pending_bytes = None
|
||||
frames_sent += 1
|
||||
else:
|
||||
except (BlockingIOError, OSError):
|
||||
frames_dropped += 1
|
||||
|
||||
# Background refresh: resend last frame to recover from router drops
|
||||
# Background refresh: resend last params to recover from router drops
|
||||
if refresh_due:
|
||||
last_refresh = now
|
||||
fps_sample_frames += 1
|
||||
if last_sent_frame is not None:
|
||||
msg_id = send_frame(router, packer, msg_id, last_sent_frame)
|
||||
if last_sent_params is not None and pending_bytes is None:
|
||||
msg_id = (msg_id + 1) % 65535
|
||||
refresh_bytes = packer.pack([0, msg_id, "draw_frame", last_sent_params])
|
||||
try:
|
||||
router.sendall(refresh_bytes)
|
||||
frames_sent += 1
|
||||
except (BlockingIOError, OSError):
|
||||
pass
|
||||
|
||||
# Write stats every 2s
|
||||
if now - stats_last_write >= 2.0:
|
||||
fps = fps_sample_frames / (now - fps_sample_start) if (now - fps_sample_start) > 0 else 0
|
||||
_write_stats(frames_sent, frames_dropped, fps, len(active_notes), events_processed)
|
||||
|
||||
Reference in New Issue
Block a user