feat(perception): preserve resident runtime across recoverable input gaps
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""Recoverable input epochs; resident model ownership is a separate lifetime.
|
||||
|
||||
Used only by the explicitly enabled resumable adapter. The controller supplies
|
||||
the source-clock mapping and the profile adapter supplies verified decoder and
|
||||
sensor evidence. No socket, GPU, vehicle command or unbounded history lives here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, replace
|
||||
from hashlib import sha256
|
||||
|
||||
from .realtime_contract import StreamStart, _integer
|
||||
|
||||
|
||||
class StreamSuspended(RuntimeError):
|
||||
"""Discard obsolete work, keep the resident model processes and lease."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ResumeEvidence:
|
||||
camera_ns: int
|
||||
pose_ns: int
|
||||
oldest_points_ns: int
|
||||
newest_points_ns: int
|
||||
decoded_keyframe: bool
|
||||
|
||||
def validate(self, *, cutoff_ns: int, source_now_ns: int) -> None:
|
||||
for name in ("camera_ns", "pose_ns", "oldest_points_ns", "newest_points_ns"):
|
||||
_integer(getattr(self, name), name)
|
||||
_integer(source_now_ns, "source clock")
|
||||
if (
|
||||
self.decoded_keyframe is not True
|
||||
or not cutoff_ns <= self.pose_ns <= self.camera_ns <= source_now_ns
|
||||
or not cutoff_ns <= self.oldest_points_ns <= self.newest_points_ns <= self.camera_ns
|
||||
or source_now_ns - self.camera_ns > 250_000_000
|
||||
or self.camera_ns - self.pose_ns > 100_000_000
|
||||
or self.camera_ns - self.newest_points_ns > 100_000_000
|
||||
or self.camera_ns - self.oldest_points_ns > 250_000_000
|
||||
or max(
|
||||
abs(self.pose_ns - self.oldest_points_ns), abs(self.pose_ns - self.newest_points_ns)
|
||||
)
|
||||
> 100_000_000
|
||||
):
|
||||
raise StreamSuspended("fresh keyframe and causal camera/pose/points required")
|
||||
|
||||
|
||||
class InputContinuity:
|
||||
"""Access is serialized by StreamingLifecycle's lock."""
|
||||
|
||||
def __init__(self, activation: StreamStart) -> None:
|
||||
self.activation = self.epoch = activation
|
||||
self.phase = "active"
|
||||
self.generation = self.pauses = 0
|
||||
self.reason: str | None = None
|
||||
self.cutoff_ns = 0
|
||||
|
||||
def check(self, epoch: StreamStart, *, synchronizing: bool = False) -> None:
|
||||
if epoch != self.epoch:
|
||||
raise StreamSuspended("obsolete input epoch")
|
||||
if self.phase != "active" and not (synchronizing and self.phase == "synchronizing"):
|
||||
raise StreamSuspended("input is waiting for resynchronization")
|
||||
|
||||
def pause(self, reason: str) -> None:
|
||||
if reason not in ("input-disconnected", "input-timeout", "source-gap", "worker-telemetry"):
|
||||
raise ValueError("unknown recoverable pause reason")
|
||||
if self.phase != "waiting":
|
||||
self.pauses += 1
|
||||
self.phase, self.reason = "waiting", reason
|
||||
|
||||
def begin(self, source_now_ns: int) -> StreamStart:
|
||||
_integer(source_now_ns, "source clock")
|
||||
if self.phase != "waiting" or source_now_ns < self.cutoff_ns:
|
||||
raise StreamSuspended("new input needs a pause and nonregressing source clock")
|
||||
self.generation += 1
|
||||
identity = sha256(f"{self.activation.epoch_id}:{self.generation}".encode()).hexdigest()
|
||||
# Wire v2 already binds every packet to the complete StreamStart. Change
|
||||
# the input epoch, NOT the resident lease generation/model activation.
|
||||
self.epoch = replace(self.activation, epoch_id="input-" + identity)
|
||||
self.cutoff_ns, self.phase = source_now_ns, "synchronizing"
|
||||
return self.epoch
|
||||
|
||||
def snapshot(self) -> dict[str, object]:
|
||||
return {
|
||||
"phase": self.phase,
|
||||
"reason": self.reason,
|
||||
"input_generation": self.generation,
|
||||
"pauses": self.pauses,
|
||||
"input_start": self.epoch.to_dict(),
|
||||
"minimum_source_ns": str(self.cutoff_ns),
|
||||
"actuation_allowed": False,
|
||||
}
|
||||
Reference in New Issue
Block a user