Live Streaming
Instead of saving a scene and viewing it afterwards, LiveViewer starts the server
immediately and pushes each state to an already-open browser tab as your simulation
produces it, over a WebSocket:
from simview import LiveViewer
# `scene` needs its complete model (terrain, bodies, ...) up front; states are
# streamed in afterwards.
with LiveViewer(scene, open_browser=True) as live:
for t in range(num_steps):
... # step the simulation
live.push_state(time=t * dt, body_states=[...], scalar_values=...)
# scene.states was appended to exactly like scene.add_state would, so it can
# still be saved once streaming is done:
scene.save("recording.json.gz", compress=True)
push_state has the same signature and validation as SimulationScene.add_state --
it delegates to it directly, then hands the new frame to a background sender thread that
broadcasts it to every connected viewer. A viewer opened after the stream has already
started still gets the full history so far, replayed as a catch-up before it starts
receiving new frames live. If no viewer is connected yet, pushed frames are simply
buffered for the next one to connect. Playback in the browser follows the live frames
automatically as long as you haven't scrubbed backward or started a loop; a small "LIVE"
badge shows while the socket is open.
Backpressure
push_state never blocks your simulation loop on the network. Frames go into a bounded
queue (queue_size, 256 by default) drained by the sender thread, so a slow or hung
browser tab costs the caller nothing. When that queue fills up, the oldest pending
frame is dropped to make room for the newest, keeping what the viewer shows close to
live instead of falling further behind:
with LiveViewer(scene, queue_size=64) as live:
...
print(live.dropped_frames) # frames skipped on the wire, if any
Dropping only affects the live stream. Every pushed frame still lands in scene.states
and in the catch-up buffer, so the saved recording is complete either way. stop() (and
therefore the with block's exit) flushes whatever is still queued before shutting the
server down.
See example_live.py for a runnable end-to-end example.
See the LiveViewer API reference for the full signature.