Files
vision-anti-pinch/orin/pipeline/latest_result.py
T

65 lines
2.1 KiB
Python

"""One owned, immutable latest result, mirroring the 0920 LatestResult contract."""
from __future__ import annotations
from collections.abc import Mapping
import threading
from types import MappingProxyType
def freeze(value):
if isinstance(value, Mapping):
return MappingProxyType({key: freeze(item) for key, item in value.items()})
if isinstance(value, (list, tuple)):
return tuple(freeze(item) for item in value)
return value
def result_to_dict(value):
"""Return a mutable JSON-compatible copy when a consumer needs serialization."""
if isinstance(value, Mapping):
return {key: result_to_dict(item) for key, item in value.items()}
if isinstance(value, tuple):
return [result_to_dict(item) for item in value]
return value
class LatestResult:
"""Readers share a stable snapshot; reading never removes or acknowledges it.
Only the latest result is retained here. Slow readers can skip frames, and
references they already hold remain valid after subsequent publications.
"""
def __init__(self):
self._condition = threading.Condition()
self._value = None
self._closed = False
def publish(self, result):
if result is None:
raise ValueError("Cannot publish a null result")
snapshot = freeze(result)
with self._condition:
if self._closed:
raise RuntimeError("Result publisher is closed")
self._value = snapshot
self._condition.notify_all()
def load(self):
with self._condition:
return self._value
def wait(self, after_frame_index=-1, timeout=None):
"""Wait for a newer result; return None on timeout/end without a new frame."""
with self._condition:
def has_new_frame():
return self._value is not None and self._value['frame_index'] > after_frame_index
self._condition.wait_for(lambda: has_new_frame() or self._closed, timeout)
return self._value if has_new_frame() else None
def close(self):
with self._condition:
self._closed = True
self._condition.notify_all()