Skip to content

Proposal: BDLiveSim — a third runner for UI-driven interactive simulation #33

Description

@PhotonicVelocity

Hello — first, thank you for bdsim and the recent realtime work. I've been reading through feat/realtime, the design
docs (REALTIME_IO_DESIGN.md, TELEMETRY_CONTROL_DESIGN.md, docs/realtime-refactor-plan.md), and the CHANGELOG for
1.2.0 / 1.3.0, and I'd like to discuss adding a third runner that fits the "Option B (separate runners)" pattern
established in the realtime refactor plan.

Following CONTRIBUTING.md, I'm filing this for design discussion before any code. I'm not asking you to build anything —
I'd do the work and submit a PR if there's alignment. I just want to make sure it lands as something you'd welcome, in a
shape consistent with where you're already taking bdsim.

The use case

I'm working on a motor-driven hardware project that needs simulation during development for decision-making and
controller design. To support that, I'm building an interactive simulation app — a fixed-frame UI loop running at ~60
Hz, with the user driving the sim live:

  • Sliders for tuning controller gains while the sim runs
  • 2D pad widgets for vector inputs (force injection to perturb the system and observe response)
  • Checkbox / dropdown for boolean / enum inputs
  • Live plotting of selected signals over time for analysis
  • Pause / playback-rate control (0.5×, 1×, 2×)
  • Scrub-back through history via snapshot / restore
  • All while the underlying physics + control graph evaluates

The idea is to use the bdsim graph as the design-and-validation environment for both control logic and hardware
choices
before any firmware writing or procurement happens. Sliders to swap between candidate motors / drivers /
sensors and see how the closed loop behaves; tweaking geometry parameters (mounting offsets, arm lengths, CG locations)
interactively to find what's actually feasible; comparing alternatives side-by-side without rebuilding the world each
time. The control-logic-tuning use case is one slice of a broader hands-on design loop.

Why bdsim

I did a survey of Python node-graph / dataflow libraries (ryvencore, simupy, python-control, RxPy, streamz, OpenMDAO,
etc.). bdsim is the closest fit — the Block ABC, the Clock primitive, hybrid continuous + discrete scheduling, the
algebraic-loop check, and scipy.integrate.solve_ivp integration are all exactly what I want. The math layer is
excellent.

What's missing for the interactive-app use case is a runner that allows external pump (advance(dt) style), live
parameter mutation between advances, and snapshot / restore for scrub-back. BDSim.run() is batch (owns the time loop
end-to-end); BDRealTime.run() is hardware-realtime (blocks until tf at wall-clock rate). Neither directly supports a
UI driving the sim at 60 Hz with playback_rate control.

The proposal: BDLiveSim as a third runner

A separate runner alongside BDSim and BDRealTime, fitting the Option B pattern from
docs/realtime-refactor-plan.md:

Runner Use case Time model
BDSim.run(bd, T=10) Offline batch / validation As-fast-as-possible
BDRealTime.run(bd, tf=20) Hardware control on RPi etc. Wall-clock realtime
BDLiveSim UI-driven interactive sim Wall-locked × playback_rate

Sketch of the API (just enough to ground discussion):

sim = BDLiveSim(bd)
sim.compile()
sim.start()  # records t=0 snapshot

# in UI's 60 Hz frame loop:
def on_frame(wall_dt):
    sim.advance(wall_dt * sim.playback_rate)
    snap = sim.snapshot()  # capture for plot / scrub buffer
    render(snap)

# user dragging a slider:
def on_slider_change(block, param, value):
    setattr(block, param, value)  # takes effect next advance

# user scrubbing back:
def on_scrub_to(t):
    sim.restore(snapshot_buffer[t])

Internally, advance(dt) would loop similarly to BDSim.run()'s while t0 < tf body — pulling the next event from the
eventq, running _interval_hybrid to that boundary, etc. — but exit when sim_t reaches the requested target rather
than running to a fixed tf. Reuses Clock, the algebraic-loop check, solve_ivp, the Block ABC — everything that lives
below the runner level.

BDLiveSim is the runner; UI is separate

To be explicit: this proposal is for the runtime layer onlyBDLiveSim is UI-framework-agnostic and ships without
one, the same way BDSim ships without a GUI. The advance(dt) / snapshot() / setattr(block, param, value) surface
is a programmatic API any UI can drive.

Concrete UIs that could be built on top:

  • bdedit-style desktop GUI (Qt) — bdedit could grow a "live mode" that creates a BDLiveSim per loaded diagram,
    drives it from a Qt timer, and routes property-panel edits through setattr.
  • bdweb's web frontend — its FastAPI backend maps HTTP requests directly onto BDLiveSim methods. Run button calls
    sim.advance(dt); slider input calls setattr; scrub bar calls sim.restore(snap).
  • Custom UIs in the host's own framework — I've currently built a first attempt at an interactive app in DearPyGui,
    but the same backend would work behind any UI framework (PySide, web, Tkinter, terminal, headless test driver).

This mirrors the bdsim core / bdedit / bdweb separation that's already established — the runner doesn't depend on any
specific UI; specific UIs are built to use it.

Alignment with in-flight work

A few things you've recently built or planned that this proposal would benefit from / contribute to:

  • 1.2.0's protected Block attributes ("This allows for discovery of parameters for possible run-time changing") —
    this is the foundation for live parameter mutation in BDLiveSim.
  • TELEMETRY_CONTROL_DESIGN.md Phases C and D (parameter registry with type metadata, bounds, mutability flags,
    "apply at next clock tick" semantics) — the same infrastructure that would power remote parameter control over network
    would power local UI-driven mutation. Could be designed once, used by both.
  • bdweb's runner gap — from the commit history (the Apr/May 2026 bdweb work on the cleanup branch), the frontend has
    a Run button wired up but it isn't yet hooked to a working backend runner (per the commit message on 392ae7f).
    BDLiveSim is exactly that backend runner: bdweb's FastAPI server would receive HTTP requests from the frontend and
    translate them into sim.advance(dt) / sim.snapshot() / setattr(block, param, value) calls. If you'd been
    intending to build that runner as part of bringing bdweb to life, this proposal could be it.

Questions for you

  1. Is this a direction you'd welcome? If not, no problem — I'll explore other approaches.
  2. If yes, is there a design constraint I should know about upfront — e.g., a different shape you'd prefer (single
    runner with mode flags? subclass of BDRealTime? something else)?
  3. Is there alignment work I should do with the in-flight 1.3.0 changes before starting? I don't want to design against
    an API surface that's actively shifting.
  4. Would you prefer I prototype small first and discuss the shape, or write up a more detailed design doc before any
    code?

I'm in no rush — happy to wait for a thoughtful response and align with your direction. Thanks again for the work you've
put into bdsim.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions