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
|
COLS = 13
|
||||||
ROWS = 8
|
ROWS = 8
|
||||||
|
|
||||||
REFRESH_INTERVAL = 0.2 # 5 FPS background refresh (catches dropped frames)
|
REFRESH_INTERVAL = 0.2
|
||||||
|
|
||||||
STATS_FILE = "/tmp/viz-stats.json"
|
STATS_FILE = "/tmp/viz-stats.json"
|
||||||
_poll = select.poll()
|
_poll = select.poll()
|
||||||
POLLIN = select.POLLIN
|
POLLIN = select.POLLIN
|
||||||
POLLOUT = select.POLLOUT
|
|
||||||
|
|
||||||
SOCK_BUFFER = 4096
|
SOCK_BUFFER = 4096
|
||||||
ROUTER_RECONNECT_DELAY = 0.1
|
|
||||||
|
|
||||||
|
|
||||||
def pack_frame(pixels):
|
def pack_frame(pixels):
|
||||||
@@ -29,8 +27,11 @@ def pack_frame(pixels):
|
|||||||
for col in range(COLS):
|
for col in range(COLS):
|
||||||
if pixels[row][col]:
|
if pixels[row][col]:
|
||||||
bits |= 1 << (row * COLS + col)
|
bits |= 1 << (row * COLS + col)
|
||||||
return (bits & 0xFFFFFFFF, (bits >> 32) & 0xFFFFFFFF,
|
w0 = bits & 0xFFFFFFFF
|
||||||
(bits >> 64) & 0xFFFFFFFF, (bits >> 96) & 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):
|
def render(active_notes):
|
||||||
@@ -46,7 +47,7 @@ def render(active_notes):
|
|||||||
|
|
||||||
def connect_router():
|
def connect_router():
|
||||||
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
router.setblocking(False)
|
router.settimeout(0.3)
|
||||||
try:
|
try:
|
||||||
router.connect(ROUTER_SOCK)
|
router.connect(ROUTER_SOCK)
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -54,19 +55,6 @@ def connect_router():
|
|||||||
return 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():
|
def main():
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
@@ -77,18 +65,15 @@ def main():
|
|||||||
packer = msgpack.Packer()
|
packer = msgpack.Packer()
|
||||||
|
|
||||||
_poll.register(sock, POLLIN)
|
_poll.register(sock, POLLIN)
|
||||||
router_fd = router.fileno()
|
|
||||||
_poll.register(router, POLLIN | POLLOUT)
|
|
||||||
|
|
||||||
active_notes = set()
|
active_notes = set()
|
||||||
last_sent_frame = None
|
last_sent_params = None
|
||||||
pending_frame = None
|
pending_bytes = None
|
||||||
buf = b""
|
buf = b""
|
||||||
msg_id = 0
|
msg_id = 0
|
||||||
|
|
||||||
frames_sent = 0
|
frames_sent = 0
|
||||||
frames_dropped = 0
|
frames_dropped = 0
|
||||||
router_errors = 0
|
|
||||||
|
|
||||||
last_refresh = time.monotonic()
|
last_refresh = time.monotonic()
|
||||||
stats_last_write = time.monotonic()
|
stats_last_write = time.monotonic()
|
||||||
@@ -100,14 +85,11 @@ def main():
|
|||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
refresh_due = (now - last_refresh) >= REFRESH_INTERVAL
|
refresh_due = (now - last_refresh) >= REFRESH_INTERVAL
|
||||||
|
|
||||||
timeout = 5 # ms
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
events = _poll.poll(timeout)
|
events = _poll.poll(5)
|
||||||
except OSError:
|
except OSError:
|
||||||
events = []
|
events = []
|
||||||
|
|
||||||
# Process UDP
|
|
||||||
for fd, flags in events:
|
for fd, flags in events:
|
||||||
if fd == sock.fileno() and (flags & POLLIN):
|
if fd == sock.fileno() and (flags & POLLIN):
|
||||||
try:
|
try:
|
||||||
@@ -137,38 +119,34 @@ def main():
|
|||||||
changed = True
|
changed = True
|
||||||
if changed:
|
if changed:
|
||||||
events_processed += 1
|
events_processed += 1
|
||||||
frame = render(active_notes)
|
params = render(active_notes)
|
||||||
if frame != last_sent_frame:
|
if params != last_sent_params:
|
||||||
pending_frame = frame
|
msg_id = (msg_id + 1) % 65535
|
||||||
|
pending_bytes = packer.pack([0, msg_id, "draw_frame", params])
|
||||||
|
last_sent_params = params
|
||||||
|
|
||||||
# Process router readable
|
# Try to send pending frame — no POLLOUT dependency
|
||||||
if fd == router_fd and (flags & POLLIN):
|
if pending_bytes is not None:
|
||||||
try:
|
try:
|
||||||
while True:
|
router.sendall(pending_bytes)
|
||||||
d = router.recv(SOCK_BUFFER)
|
pending_bytes = None
|
||||||
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
|
|
||||||
frames_sent += 1
|
frames_sent += 1
|
||||||
else:
|
except (BlockingIOError, OSError):
|
||||||
frames_dropped += 1
|
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:
|
if refresh_due:
|
||||||
last_refresh = now
|
last_refresh = now
|
||||||
fps_sample_frames += 1
|
fps_sample_frames += 1
|
||||||
if last_sent_frame is not None:
|
if last_sent_params is not None and pending_bytes is None:
|
||||||
msg_id = send_frame(router, packer, msg_id, last_sent_frame)
|
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:
|
if now - stats_last_write >= 2.0:
|
||||||
fps = fps_sample_frames / (now - fps_sample_start) if (now - fps_sample_start) > 0 else 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)
|
_write_stats(frames_sent, frames_dropped, fps, len(active_notes), events_processed)
|
||||||
|
|||||||
Reference in New Issue
Block a user