Files
hms-mediaengine/_entwicklung/apps/renderer/hms_renderer/state_mirror.py
T
HMS MediaEngine Agent 362e089be0 AUFGERAUMT: Root auf 10 sichtbare Elemente reduziert
Der Nutzer hat recht: Der Ordner war voller Entwicklungs-Muell.
Jetzt ist sauber getrennt:

ROOT (was der Nutzer sieht und braucht):
- run.py                     = das Programm
- hms_app/                   = der Anwendungscode
- HMS MediaEngine.app        = macOS Doppelklick-Starter
- HMS-Start.vbs              = Windows Doppelklick-Starter
- HMS-Install.vbs             = Windows Erst-Installation
- HMS-Mac-Install.command     = macOS Homebrew-Installation
- HMS-Portable-Install.command = macOS Portable-Installation (16GB-Fix)
- installer_gui.py           = grafischer Installer
- launcher.pyw + launcher_core.py = interne Start-Logik
- LIESMICH.txt               = 10-Zeilen-Kurzanleitung
- .gitignore

_entwicklung/ (alles andere, NICHT benoetigt):
- packages/ apps/ native/ plugins/ tools/ schemas/ tests/ docs/
  build/ fixture_profiles/
- PLAN.md STATUS.md ERRORS.md TEST_REPORT.md CHANGELOG.md README.md
- pyproject.toml uv.lock setup_*.sh/ps1 make_mac_app.py

Diese Trennung gilt ab sofort fuer alle Commits. Der Nutzer kann
_entwicklung/ loeschen wenn er Platz braucht - die App laeuft ohne.

Verifiziert: App startet nach Aufraeumen unveraendert (Health 200).
2026-09-11 23:44:06 +02:00

97 lines
3.5 KiB
Python

"""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