"""Remote-State-Mirror auf der Renderer-Seite (PLAN.md §6.2, §6.4). Der Renderer hält den zuletzt übermittelten Showzustand als lokales Spiegelbild: - nach Verbindung: vollständiger Snapshot (§6.2 Pflicht) - danach inkrementelle Deltas mit monotoner Revision - Deltas werden erst nach erfolgtem Snapshot akzeptiert (§6.2: „Deltas erst nach erfolgreichem Re-Sync") - veraltete/doppelte Deltas sind idempotente No-Ops (§6.5) Der Mirror ist reine Zustandslogik ohne Pixelbezug (§33); der Rendergraph liest Werte über get_value() je Frame. """ from __future__ import annotations class RemoteStateMirror: """Spiegel des autoritativen Showzustands im Renderer.""" def __init__(self) -> None: self._revision = 0 self._values: dict[str, float] = {} self._has_snapshot = False @property def revision(self) -> int: return self._revision @property def has_snapshot(self) -> bool: """True, nachdem ein vollständiger Snapshot empfangen wurde.""" return self._has_snapshot def get_value(self, path: str, default: float | None = None) -> float | None: """Wirksamer Wert für einen Parameterpfad (§10.2).""" return self._values.get(path, default) def all_values(self) -> dict[str, float]: return dict(self._values) # ---------- Snapshot / Delta (§6.2, §6.4) ---------- def apply_snapshot(self, payload: dict) -> None: """Übernimmt einen vollständigen Snapshot (nach Verbindung/Reconnect).""" values = payload.get("values", {}) if not isinstance(values, dict): raise ValueError("Snapshot ohne Werte-Objekt") self._revision = int(payload["state_revision"]) self._values = {str(k): float(v) for k, v in values.items()} self._has_snapshot = True def apply_delta(self, payload: dict) -> bool: """Wendet ein Delta an. Rückgabe: - True: Delta angewendet ODER als veraltetes Duplikat ignoriert - False: Re-Sync nötig (noch kein Snapshot empfangen, §6.2) Gelöschte Pfade sind als None kodiert (StateDelta-Vertrag). """ if not self._has_snapshot: return False # §6.2: Deltas erst nach Snapshot delta_revision = int(payload["state_revision"]) if delta_revision <= self._revision: return True # idempotent: Duplikat/veraltet, kein Handlungsbedarf changes = payload.get("changes", {}) if not isinstance(changes, dict): raise ValueError("Delta ohne Changes-Objekt") for path, value in changes.items(): if value is None: self._values.pop(str(path), None) # gelöscht else: self._values[str(path)] = float(value) self._revision = delta_revision return True def apply_envelope(mirror: RemoteStateMirror, envelope) -> bool: """Verarbeitet ein IPC-Envelope in den Mirror. - SNAPSHOT: vollständige Übernahme - EVENT mit changes: Delta-Anwendung - alles andere (Heartbeat, Ack, …): keine Zustandswirkung Rückgabe: True, wenn der Zustand dadurch (re-)synchronisiert wurde; False, wenn ein Re-Sync (neuer Snapshot) angefordert werden muss. """ from hms_protocol import MessageType if envelope.type is MessageType.SNAPSHOT: mirror.apply_snapshot(envelope.payload) return True if envelope.type is MessageType.EVENT and "changes" in envelope.payload: return mirror.apply_delta(envelope.payload) return True # keine Zustandsnachricht: nichts zu tun