babylon.engine.runner

Async simulation runner for non-blocking UI integration.

This module provides AsyncSimulationRunner, which decouples the UI from the simulation engine by running simulation steps in a background thread and pushing WorldState snapshots to an async queue.

The runner enables: - Non-blocking UI updates during simulation steps - Continuous play mode without freezing the GUI - Queue-based state consumption for flexible UI update strategies

Usage Example:
>>> from babylon.engine.runner import AsyncSimulationRunner
>>> from babylon.engine.simulation import Simulation
>>>
>>> runner = AsyncSimulationRunner(simulation, tick_interval=1.0)
>>> await runner.start()  # Begin continuous simulation
>>>
>>> # UI can poll for states without blocking
>>> state = await runner.get_state()
>>> if state:
...     update_ui(state)
>>>
>>> await runner.stop()

See also

  • ai-docs/asyncio-patterns.yaml for async best practices

  • src/babylon/ui/main.py for UI integration patterns

Classes

AsyncSimulationRunner(simulation[, ...])

Async runner that decouples UI from simulation engine.

class babylon.engine.runner.AsyncSimulationRunner(simulation, tick_interval=1.0)[source]

Bases: object

Async runner that decouples UI from simulation engine.

The runner wraps a Simulation instance and provides:

  1. Non-blocking steps: Uses asyncio.to_thread() to run simulation.step() without blocking the event loop.

  2. State queue: Pushes WorldState snapshots to an async queue for the UI to consume at its own pace.

  3. Continuous play: start()/stop() manage a background loop that steps the simulation at configurable intervals.

  4. Queue overflow handling: Drops oldest states when queue is full (MAX_QUEUE_SIZE=10) to prevent memory issues.

Parameters:
MAX_QUEUE_SIZE

Maximum states to buffer before dropping oldest.

Example

>>> runner = AsyncSimulationRunner(sim, tick_interval=0.5)
>>> state = await runner.step_once()  # Single step
>>> print(f"Now at tick {state.tick}")
Thread Safety:

The runner uses an asyncio.Lock to serialize step_once() calls, ensuring simulation state consistency even with concurrent access.

MAX_QUEUE_SIZE: int = 10
__init__(simulation, tick_interval=1.0)[source]

Initialize the async runner with a simulation.

Parameters:
  • simulation (Simulation) – The Simulation facade to run.

  • tick_interval (float) – Seconds between steps in continuous mode. Must be positive.

Raises:

ValueError – If tick_interval is not positive.

Return type:

None

property simulation: Simulation

Return the wrapped Simulation instance.

property tick_interval: float

Return the interval between steps in continuous mode.

property is_running: bool

Return True if continuous mode is active.

property queue: Queue[WorldState]

Return the state queue for direct access.

Returns:

The asyncio.Queue containing WorldState snapshots.

async start()[source]

Start continuous simulation mode.

Creates a background task that steps the simulation at tick_interval intervals. Idempotent - calling start() when already running is a no-op.

The task reference is stored to prevent garbage collection.

Return type:

None

async stop()[source]

Stop continuous simulation mode.

Cancels the background task and waits for it to finish. Idempotent - calling stop() when not running is a no-op.

Return type:

None

async step_once()[source]

Execute a single simulation step without blocking.

Uses asyncio.to_thread() to run simulation.step() in a thread pool, keeping the event loop responsive.

The new state is pushed to the queue. If the queue is full, the oldest state is dropped to make room.

Return type:

WorldState

Returns:

The new WorldState after the step.

Note

Uses an asyncio.Lock to serialize concurrent calls, ensuring simulation state consistency.

async get_state()[source]

Get a state from the queue without blocking.

Return type:

WorldState | None

Returns:

The next WorldState if available, None otherwise.

async get_state_blocking(timeout=None)[source]

Get a state from the queue, waiting if necessary.

Parameters:

timeout (float | None) – Maximum seconds to wait. None means wait forever.

Return type:

WorldState

Returns:

The next WorldState from the queue.

Raises:

asyncio.TimeoutError – If timeout expires before a state is available.

async drain_queue()[source]

Remove and return all states from the queue.

Return type:

list[WorldState]

Returns:

List of all WorldState objects that were in the queue, in order from oldest to newest. Empty list if queue was empty.

async reset(new_simulation)[source]

Reset the runner with a new simulation.

Stops the runner if running, drains the queue, and assigns the new simulation for future steps.

Parameters:

new_simulation (Simulation) – The new Simulation instance to use.

Return type:

None