reactor-runtime 2.9.1__tar.gz → 2.9.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/PKG-INFO +1 -1
  2. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/pyproject.toml +1 -1
  3. reactor_runtime-2.9.2/src/reactor_runtime/realtime/__init__.py +20 -0
  4. reactor_runtime-2.9.2/src/reactor_runtime/realtime/pipeline.py +304 -0
  5. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
  6. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime.egg-info/SOURCES.txt +2 -0
  7. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/README.md +0 -0
  8. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/setup.cfg +0 -0
  9. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/api/__init__.py +0 -0
  10. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/__init__.py +0 -0
  11. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/config.py +0 -0
  12. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/experiment/__init__.py +0 -0
  13. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/experiment/session.py +0 -0
  14. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/__init__.py +0 -0
  15. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/client.py +0 -0
  16. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/defaults.py +0 -0
  17. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/driver/__init__.py +0 -0
  18. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/driver/pipeline_executor.py +0 -0
  19. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/driver/step_result.py +0 -0
  20. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/events/__init__.py +0 -0
  21. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/events/connected.py +0 -0
  22. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/events/event.py +0 -0
  23. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/events/messages.py +0 -0
  24. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/events/upload.py +0 -0
  25. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/internal/__init__.py +0 -0
  26. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
  27. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
  28. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
  29. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/model/__init__.py +0 -0
  30. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/model/decorators.py +0 -0
  31. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/model/handlers.py +0 -0
  32. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
  33. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
  34. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
  35. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
  36. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
  37. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
  38. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
  39. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/tracks/input.py +0 -0
  40. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/tracks/output.py +0 -0
  41. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/interface/upload.py +0 -0
  42. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/model_state.py +0 -0
  43. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/__init__.py +0 -0
  44. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
  45. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/backends/base.py +0 -0
  46. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/backends/file.py +0 -0
  47. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
  48. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/helpers.py +0 -0
  49. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/nvml_sampler.py +0 -0
  50. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
  51. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
  52. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/profiler.py +0 -0
  53. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/singleton.py +0 -0
  54. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/profiling/torch_chunk_profiler.py +0 -0
  55. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/__init__.py +0 -0
  56. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/chunk_encoder.py +0 -0
  57. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/chunk_uploader.py +0 -0
  58. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/config.py +0 -0
  59. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/markers.py +0 -0
  60. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/session_recorder.py +0 -0
  61. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/sinks.py +0 -0
  62. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/recording/track_resolver.py +0 -0
  63. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/runtime_api.py +0 -0
  64. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/runtimes/http/config.py +0 -0
  65. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/runtimes/http/http_runtime.py +0 -0
  66. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/runtimes/http/types.py +0 -0
  67. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/schema.py +0 -0
  68. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/schema_validator.py +0 -0
  69. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/__init__.py +0 -0
  70. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/__main__.py +0 -0
  71. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/commands/__init__.py +0 -0
  72. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/commands/run.py +0 -0
  73. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/commands/schema.py +0 -0
  74. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/main.py +0 -0
  75. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/utils/__init__.py +0 -0
  76. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/utils/config.py +0 -0
  77. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/serve/utils/runtime.py +0 -0
  78. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/__init__.py +0 -0
  79. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/config.py +0 -0
  80. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/events.py +0 -0
  81. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
  82. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
  83. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
  84. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
  85. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
  86. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
  87. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
  88. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
  89. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/opus.py +0 -0
  90. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
  91. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
  92. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
  93. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
  94. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
  95. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
  96. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
  97. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
  98. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
  99. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
  100. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
  101. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
  102. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
  103. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/probes/__init__.py +0 -0
  104. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/probes/fps_probe.py +0 -0
  105. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/quality.py +0 -0
  106. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
  107. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
  108. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
  109. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
  110. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
  111. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
  112. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
  113. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
  114. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
  115. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
  116. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
  117. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
  118. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
  119. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
  120. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
  121. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/ice_uris.py +0 -0
  122. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/interface.py +0 -0
  123. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/media.py +0 -0
  124. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/transports/types.py +0 -0
  125. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/launch.py +0 -0
  126. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/loader.py +0 -0
  127. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/log.py +0 -0
  128. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/messages.py +0 -0
  129. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/paths.py +0 -0
  130. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/ports.py +0 -0
  131. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime/utils/typing.py +0 -0
  132. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
  133. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime.egg-info/requires.txt +0 -0
  134. {reactor_runtime-2.9.1 → reactor_runtime-2.9.2}/src/reactor_runtime.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.9.1
3
+ Version: 2.9.2
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reactor_runtime"
7
- version = "2.9.1"
7
+ version = "2.9.2"
8
8
  description = "Reactor runtime with public model API"
9
9
  authors = [
10
10
  { name = "Reactor", email = "team@reactor.inc" }
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+ """
3
+ Runtime ↔ engine bridge.
4
+
5
+ The realtime inference engine lives in its own standalone package
6
+ (``realtime_engine``) with **zero** ``reactor_runtime`` dependency — it is
7
+ driven entirely through inbox/outbox queues. This module is the runtime-side
8
+ caller: :class:`RealtimePipeline` pumps the engine's queues from inside a
9
+ ``ReactorPipeline`` so a client connected over the real transport drives the
10
+ engine and receives its chunks.
11
+
12
+ Only the bridge lives here; the engine itself is never imported into the
13
+ runtime tree beyond this thin seam.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from reactor_runtime.realtime.pipeline import RealtimePipeline
19
+
20
+ __all__ = ["RealtimePipeline"]
@@ -0,0 +1,304 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+ """
3
+ :class:`RealtimePipeline` — the thin runtime bridge to a standalone engine.
4
+
5
+ The engine (the ``realtime_engine`` package) owns the scheduling loop and all
6
+ ML. It is driven only through inbox/outbox queues and has **zero**
7
+ ``reactor_runtime`` dependency. This bridge is the runtime-side caller: it
8
+ inherits everything from :class:`ReactorPipeline` — typed :class:`InputState`,
9
+ auto ``set_<field>`` events, :class:`Output` wrapping, ``emit`` / FPS pacing,
10
+ and the connection lifecycle — and adds only the queue plumbing::
11
+
12
+ connect -> Attach(session_id, attach_context)
13
+ set_<field> -> Action(session_id, conditioning) (on change)
14
+ Chunk -> _wrap_output -> emit
15
+
16
+ The bridge owns **no** scheduling, **no** ML, and **no** state machine; if it
17
+ grows logic, that logic belongs in the engine (its ``serve`` loop) or in a
18
+ hook, not here.
19
+
20
+ Subclasses declare a typed ``state: MyState`` and an :class:`Output` subclass,
21
+ then override the four small hooks: :meth:`build_engine`,
22
+ :meth:`_attach_context`, :meth:`state_to_conditioning`, :meth:`_wrap_output`.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import asyncio
28
+ import copy
29
+ import itertools
30
+ from collections.abc import Mapping
31
+ from typing import Any, Callable, Optional
32
+
33
+ import numpy as np
34
+
35
+ from reactor_runtime.interface.pipeline.reactor_pipeline import ReactorPipeline
36
+ from reactor_runtime.interface.tracks.output import Output
37
+ from reactor_runtime.utils.log import get_logger
38
+
39
+ # Optional dependency: the runtime does NOT depend on the engine/contract. This
40
+ # guarded import lets a plain ``reactor_runtime`` install (and non-engine models)
41
+ # work without it — importing this module never fails. Only engine-backed models
42
+ # need the contract, and ``RealtimePipeline.__init__`` fails clearly (below) if
43
+ # it's absent, instead of a bare ``ModuleNotFoundError``.
44
+ try:
45
+ from realtime_engine import (
46
+ Action,
47
+ Attach,
48
+ Detach,
49
+ Inbox,
50
+ Outbox,
51
+ RealtimeInterface,
52
+ run_engine,
53
+ )
54
+
55
+ HAS_REALTIME_ENGINE = True
56
+ except ImportError:
57
+ HAS_REALTIME_ENGINE = False
58
+
59
+ logger = get_logger(__name__)
60
+
61
+
62
+ class RealtimePipeline(ReactorPipeline):
63
+ """Drive a queue/``serve`` :class:`RealtimeInterface` engine from the runtime.
64
+
65
+ Override :meth:`run` is **not** needed — this class provides the queue
66
+ loop. Subclasses provide the model surface (state + output track) and the
67
+ four hooks. ``inference()`` is intentionally unused: the engine, not a
68
+ generator, produces frames.
69
+ """
70
+
71
+ # Session-liveness poll interval. This only governs how quickly the bridge
72
+ # notices connect/disconnect transitions to issue Attach/Detach — it is NOT
73
+ # on the frame path (chunks flow through an independent drain task + the
74
+ # engine's own loop), so a coarse interval is fine.
75
+ _lifecycle_poll_s: float = 0.02
76
+
77
+ def __init__(self) -> None:
78
+ if not HAS_REALTIME_ENGINE:
79
+ raise ImportError(
80
+ "RealtimePipeline (engine-backed models) requires the realtime "
81
+ "contract package. Install it with "
82
+ "`pip install reactor-realtime-engine`."
83
+ )
84
+ super().__init__()
85
+ self._engine: Optional[RealtimeInterface] = None
86
+ self._inbox: Optional[Inbox] = None
87
+ self._sid: Optional[str] = None
88
+ self._last_cond: Optional[dict] = None
89
+ self._sid_counter = itertools.count(1)
90
+
91
+ # ------------------------------------------------------------------
92
+ # load() — build the engine (no event-loop primitives here)
93
+ # ------------------------------------------------------------------
94
+
95
+ def load(self, config: dict[str, Any]) -> None:
96
+ """Build the engine once, before any client connects.
97
+
98
+ Queues / events are created in :meth:`run` instead — ``load()`` runs
99
+ before the event loop exists.
100
+ """
101
+ self._engine = self.build_engine(config)
102
+
103
+ # ------------------------------------------------------------------
104
+ # run() — the queue-plumbing loop (replaces the generator loop)
105
+ # ------------------------------------------------------------------
106
+
107
+ async def run(self) -> None:
108
+ engine = self._engine
109
+ if engine is None:
110
+ raise RuntimeError(
111
+ f"{type(self).__name__}.load() must build an engine before run()"
112
+ )
113
+
114
+ inbox, outbox, stop = Inbox(), Outbox(), asyncio.Event()
115
+ self._inbox = inbox
116
+
117
+ # Pace emission to the engine's declared chunk cadence — NOT to its
118
+ # per-step compute time. The engine's serve loop already ticks at
119
+ # rate_hz; deriving fps from step_ms (tiny on a fast engine) would tell
120
+ # the output buffer to drain far faster than chunks actually arrive.
121
+ # frames_per_chunk is declared by the manifest (not inferred from
122
+ # chunk_shape, which is an opaque output shape).
123
+ manifest = engine.manifest()
124
+ self.output_buffer.set_fps(
125
+ max(1.0, manifest.rate_hz * manifest.frames_per_chunk)
126
+ )
127
+
128
+ lock = self._gen_lock
129
+ if lock is None:
130
+ raise RuntimeError(
131
+ f"{type(self).__name__}.run() called before _init_async()"
132
+ )
133
+
134
+ engine_task = asyncio.create_task(run_engine(engine, inbox, outbox, stop=stop))
135
+ try:
136
+ while True:
137
+ # Fresh state per connection (mirrors ReactorPipeline.run()).
138
+ self.state = self._state_cls()
139
+ self._sid = None
140
+ self._last_cond = None
141
+ await self.connected.wait()
142
+
143
+ # Attach only after the @connected handler has initialized state.
144
+ # That handler runs under _gen_lock (ReactorPipeline._invoke), and
145
+ # the dispatcher acquires the lock before it yields, so taking the
146
+ # lock here orders us strictly after it — otherwise a model that
147
+ # sets seed/prompt/image in @connected would attach with defaults.
148
+ async with lock:
149
+ sid = f"s{next(self._sid_counter)}"
150
+ self._sid = sid
151
+ # deepcopy: the inbox payload must be a self-contained
152
+ # snapshot — a nested ndarray/list in the context stays a
153
+ # live alias of self.state under a shallow dict() copy, so a
154
+ # later in-place mutation could change an already-enqueued msg.
155
+ inbox.put_nowait(
156
+ Attach(sid, copy.deepcopy(dict(self._attach_context())))
157
+ )
158
+ self._push_action_if_changed() # seed the initial conditioning
159
+
160
+ # One loop both drains chunks -> emit and notices disconnect.
161
+ # wait_for bounds get() so a paused engine (no chunks) still
162
+ # detaches promptly instead of blocking here forever; a timed-out
163
+ # get() leaves any in-flight chunk on the queue (get is
164
+ # cancellation-safe), so the next iteration picks it up.
165
+ try:
166
+ while self.connected.is_set():
167
+ try:
168
+ chunk = await asyncio.wait_for(
169
+ outbox.get(), self._lifecycle_poll_s
170
+ )
171
+ except asyncio.TimeoutError:
172
+ continue # no chunk this interval — re-check liveness
173
+ if chunk.session_id != sid:
174
+ continue # a chunk for a previous/other session — drop it
175
+ out = self._wrap_output(chunk.output)
176
+ if not isinstance(out, Output):
177
+ out = self._resolve_output(out)
178
+ await self.emit(out)
179
+ finally:
180
+ # Clear identity BEFORE enqueuing Detach: a set_<field> event
181
+ # racing teardown then sees no session and won't enqueue an
182
+ # Action after the Detach (the local sid stays valid here).
183
+ self._sid = None
184
+ self.state = None
185
+ inbox.put_nowait(Detach(sid))
186
+ for buf in self._input_buffers.values():
187
+ buf.reset()
188
+ self.output_buffer.flush()
189
+ finally:
190
+ stop.set()
191
+ self._inbox = None
192
+ await _await_quietly(engine_task)
193
+
194
+ # ------------------------------------------------------------------
195
+ # Conditioning — client→action, event-driven
196
+ # ------------------------------------------------------------------
197
+
198
+ async def _invoke(
199
+ self,
200
+ handler: Callable,
201
+ is_async: bool,
202
+ *,
203
+ _event: Any = None,
204
+ reserved: tuple = (),
205
+ ctx: Any = None,
206
+ **kw: Any,
207
+ ) -> None:
208
+ """Run the handler (under ``_gen_lock``), then refresh conditioning.
209
+
210
+ Any ``set_<field>`` auto-setter or custom ``@event`` handler may have
211
+ mutated ``self.state``; afterwards we recompute conditioning and push a
212
+ single :class:`Action` only when it actually changed. ``default_serve``
213
+ keeps the last conditioning between ticks, so unchanged state needs no
214
+ message.
215
+ """
216
+ await super()._invoke(
217
+ handler, is_async, _event=_event, reserved=reserved, ctx=ctx, **kw
218
+ )
219
+ self._push_action_if_changed()
220
+
221
+ def _push_action_if_changed(self) -> None:
222
+ if self._inbox is None or self._sid is None or self.state is None:
223
+ return
224
+ # deepcopy so the enqueued Action is a self-contained snapshot: a nested
225
+ # ndarray/list would otherwise alias self.state and could be mutated in
226
+ # place after enqueue (also keeps _last_cond a stable comparison base).
227
+ cond = copy.deepcopy(dict(self.state_to_conditioning()))
228
+ if _conds_equal(cond, self._last_cond):
229
+ return
230
+ self._last_cond = cond
231
+ self._inbox.put_nowait(Action(self._sid, cond))
232
+
233
+ # ------------------------------------------------------------------
234
+ # Hooks — subclasses override these
235
+ # ------------------------------------------------------------------
236
+
237
+ def build_engine(self, config: dict[str, Any]) -> RealtimeInterface:
238
+ """Construct the engine. Required override."""
239
+ raise NotImplementedError(
240
+ f"{type(self).__name__} must implement build_engine()"
241
+ )
242
+
243
+ def _state_snapshot(self) -> dict[str, Any]:
244
+ """Public state fields as a plain dict (the default hook payload)."""
245
+ if self.state is None:
246
+ return {}
247
+ return {
248
+ name: getattr(self.state, name) for name in type(self.state)._public_fields
249
+ }
250
+
251
+ def _attach_context(self) -> Mapping[str, Any]:
252
+ """Seed context for :class:`Attach` (image / scene / prompt / seed).
253
+
254
+ Defaults to a snapshot of public state at connect time; override to
255
+ narrow it (e.g. only the seed).
256
+ """
257
+ return self._state_snapshot()
258
+
259
+ def state_to_conditioning(self) -> Mapping[str, Any]:
260
+ """Adapter from ``self.state`` to the engine's action-class conditioning.
261
+
262
+ This is the State -> ``dict[str, Any]`` seam for :class:`Action`.
263
+ Override it to decouple your state shape from the engine's conditioning
264
+ vocabulary — rename fields, drop internal-only fields, or derive values
265
+ the engine wants but you don't expose in state. Defaults to a snapshot
266
+ of public state.
267
+
268
+ Note: change-detection between ticks compares the returned dict; array
269
+ values are handled with :func:`numpy.array_equal`, but prefer
270
+ scalars/strings for high-frequency ``Action`` conditioning.
271
+ """
272
+ return self._state_snapshot()
273
+
274
+ def _wrap_output(self, output: Any) -> Any:
275
+ """Turn one engine chunk into an :class:`Output` (or a raw frame).
276
+
277
+ Returning a raw ``np.ndarray`` falls back to ``ReactorPipeline``'s
278
+ single-track shorthand. Default: pass through.
279
+ """
280
+ return output
281
+
282
+
283
+ def _conds_equal(a: Optional[dict], b: Optional[dict]) -> bool:
284
+ """Dict equality that tolerates ndarray values (plain ``==`` on arrays
285
+ yields an array, and ``bool()`` of that raises ``ValueError``)."""
286
+ if a is None or b is None:
287
+ return a is b
288
+ if a.keys() != b.keys():
289
+ return False
290
+ for key in a:
291
+ av, bv = a[key], b[key]
292
+ if isinstance(av, np.ndarray) or isinstance(bv, np.ndarray):
293
+ if not np.array_equal(av, bv):
294
+ return False
295
+ elif av != bv:
296
+ return False
297
+ return True
298
+
299
+
300
+ async def _await_quietly(task: asyncio.Task) -> None:
301
+ try:
302
+ await task
303
+ except asyncio.CancelledError:
304
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.9.1
3
+ Version: 2.9.2
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -54,6 +54,8 @@ src/reactor_runtime/profiling/backends/file.py
54
54
  src/reactor_runtime/profiling/backends/otlp.py
55
55
  src/reactor_runtime/profiling/plotting/__init__.py
56
56
  src/reactor_runtime/profiling/plotting/plot_profiling.py
57
+ src/reactor_runtime/realtime/__init__.py
58
+ src/reactor_runtime/realtime/pipeline.py
57
59
  src/reactor_runtime/recording/__init__.py
58
60
  src/reactor_runtime/recording/chunk_encoder.py
59
61
  src/reactor_runtime/recording/chunk_uploader.py