fix: reduce viz decay to 3 frames, add max-age safety cap, add FPS stats
Validate and Test / validate-patches (push) Failing after 12s
Validate and Test / validate-patches (push) Failing after 12s
- DECAY_FRAMES default 8→3 (100ms visual trail) - MAX_NOTE_AGE safety cap: force-expire notes after 15 frames - Track note age separately from decay to prevent stale notes - Add FPS and active_notes to viz stats for diagnostics - Fix router reconnect: unregister old fd, not new one
This commit is contained in:
+22
-14
@@ -7,7 +7,8 @@ import socket, msgpack, time, os, select, collections
|
|||||||
|
|
||||||
VIZ_PORT = int(os.environ.get("VIZ_PORT", "8082"))
|
VIZ_PORT = int(os.environ.get("VIZ_PORT", "8082"))
|
||||||
ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock")
|
ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock")
|
||||||
DECAY_FRAMES = int(os.environ.get("DECAY_FRAMES", "8"))
|
DECAY_FRAMES = int(os.environ.get("DECAY_FRAMES", "3"))
|
||||||
|
MAX_NOTE_AGE = int(os.environ.get("MAX_NOTE_AGE", "15"))
|
||||||
|
|
||||||
COLS = 13
|
COLS = 13
|
||||||
ROWS = 8
|
ROWS = 8
|
||||||
@@ -23,8 +24,6 @@ POLLOUT = select.POLLOUT
|
|||||||
SOCK_BUFFER = 4096
|
SOCK_BUFFER = 4096
|
||||||
ROUTER_RECONNECT_DELAY = 0.1
|
ROUTER_RECONNECT_DELAY = 0.1
|
||||||
|
|
||||||
_frame_hooks = [] # for metrics
|
|
||||||
|
|
||||||
|
|
||||||
def pack_frame(pixels):
|
def pack_frame(pixels):
|
||||||
bits = 0
|
bits = 0
|
||||||
@@ -87,7 +86,6 @@ def main():
|
|||||||
next_frame_time = time.monotonic()
|
next_frame_time = time.monotonic()
|
||||||
|
|
||||||
pending_frames = FrameQueue(maxsize=5)
|
pending_frames = FrameQueue(maxsize=5)
|
||||||
router_registered = False
|
|
||||||
router_fd = router.fileno()
|
router_fd = router.fileno()
|
||||||
_poll.register(router, POLLIN | POLLOUT)
|
_poll.register(router, POLLIN | POLLOUT)
|
||||||
|
|
||||||
@@ -95,6 +93,8 @@ def main():
|
|||||||
frames_dropped = 0
|
frames_dropped = 0
|
||||||
router_errors = 0
|
router_errors = 0
|
||||||
stats_last_write = time.monotonic()
|
stats_last_write = time.monotonic()
|
||||||
|
fps_sample_start = time.monotonic()
|
||||||
|
fps_sample_frames = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
@@ -127,7 +127,7 @@ def main():
|
|||||||
vel = int(parts[3]) if len(parts) >= 4 else 0
|
vel = int(parts[3]) if len(parts) >= 4 else 0
|
||||||
msg_type = status & 0xF0
|
msg_type = status & 0xF0
|
||||||
if msg_type == 0x90 and vel > 0:
|
if msg_type == 0x90 and vel > 0:
|
||||||
active_notes[note] = DECAY_FRAMES
|
active_notes[note] = {"decay": DECAY_FRAMES, "age": 0}
|
||||||
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
|
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
|
||||||
active_notes.pop(note, None)
|
active_notes.pop(note, None)
|
||||||
|
|
||||||
@@ -153,26 +153,32 @@ def main():
|
|||||||
router_errors += 1
|
router_errors += 1
|
||||||
pending_frames.push(frame)
|
pending_frames.push(frame)
|
||||||
time.sleep(ROUTER_RECONNECT_DELAY)
|
time.sleep(ROUTER_RECONNECT_DELAY)
|
||||||
|
old_fd = router_fd
|
||||||
try:
|
try:
|
||||||
router.close()
|
router.close()
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
router = connect_router()
|
router = connect_router()
|
||||||
router_fd = router.fileno()
|
router_fd = router.fileno()
|
||||||
_poll.unregister(router)
|
try:
|
||||||
|
_poll.unregister(old_fd)
|
||||||
|
except (KeyError, OSError):
|
||||||
|
pass
|
||||||
_poll.register(router, POLLIN | POLLOUT)
|
_poll.register(router, POLLIN | POLLOUT)
|
||||||
|
|
||||||
# Render frame when due
|
# Render frame when due
|
||||||
if now >= next_frame_time:
|
if now >= next_frame_time:
|
||||||
next_frame_time = now + frame_interval
|
next_frame_time = now + frame_interval
|
||||||
|
fps_sample_frames += 1
|
||||||
|
|
||||||
pixels = [[0] * COLS for _ in range(ROWS)]
|
pixels = [[0] * COLS for _ in range(ROWS)]
|
||||||
expired = []
|
expired = []
|
||||||
for note, decay in list(active_notes.items()):
|
for note, state in list(active_notes.items()):
|
||||||
if decay <= 0:
|
state["age"] += 1
|
||||||
|
state["decay"] -= 1
|
||||||
|
if state["decay"] <= 0 or state["age"] > MAX_NOTE_AGE:
|
||||||
expired.append(note)
|
expired.append(note)
|
||||||
continue
|
continue
|
||||||
active_notes[note] = decay - 1
|
|
||||||
note_min, note_max = 36, 96
|
note_min, note_max = 36, 96
|
||||||
row = 7 - int((note - note_min) / (note_max - note_min) * 7)
|
row = 7 - int((note - note_min) / (note_max - note_min) * 7)
|
||||||
row = max(0, min(7, row))
|
row = max(0, min(7, row))
|
||||||
@@ -195,14 +201,14 @@ def main():
|
|||||||
|
|
||||||
# Write stats periodically (every 2s)
|
# Write stats periodically (every 2s)
|
||||||
if now - stats_last_write >= 2.0:
|
if now - stats_last_write >= 2.0:
|
||||||
_write_stats(frames_sent, frames_dropped, pending_frames)
|
fps = fps_sample_frames / (now - fps_sample_start) if (now - fps_sample_start) > 0 else 0
|
||||||
|
_write_stats(frames_sent, frames_dropped, pending_frames, fps, len(active_notes))
|
||||||
stats_last_write = now
|
stats_last_write = now
|
||||||
|
fps_sample_start = now
|
||||||
|
fps_sample_frames = 0
|
||||||
|
|
||||||
|
|
||||||
_stats_last_write = 0.0
|
def _write_stats(sent, dropped, pending, fps, active_count):
|
||||||
|
|
||||||
|
|
||||||
def _write_stats(sent, dropped, pending):
|
|
||||||
import json
|
import json
|
||||||
try:
|
try:
|
||||||
with open(STATS_FILE, "w") as f:
|
with open(STATS_FILE, "w") as f:
|
||||||
@@ -211,6 +217,8 @@ def _write_stats(sent, dropped, pending):
|
|||||||
"frames_dropped": dropped,
|
"frames_dropped": dropped,
|
||||||
"pending_queue": len(pending),
|
"pending_queue": len(pending),
|
||||||
"queue_full": pending.full(),
|
"queue_full": pending.full(),
|
||||||
|
"fps": round(fps, 1),
|
||||||
|
"active_notes": active_count,
|
||||||
"ts": time.time()
|
"ts": time.time()
|
||||||
}, f)
|
}, f)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|||||||
Reference in New Issue
Block a user