Engine ownership and callbacks#
A Solver owns its native engine. Its domain views retain that owner, so a
view cannot outlive the Python object that manages its native memory. Calling
close() or destroy() invalidates retained 2D and infiltration views;
structural edits also invalidate these views. Reacquire solver.surface2d
after reopening or editing a model. Invalidated views raise
StaleObjectError before entering the native library.
Use solver.surface2d or Surface2D(solver). Passing solver.handle to
Surface2D is deprecated. For compatibility it is accepted only when the
address belongs to a live registered Python owner. Arbitrary native addresses
are rejected with BadHandleError.
ModelBuilder.to_solver() transfers ownership once. Existing builder views
become stale, and a second transfer raises BadHandleError. The returned
solver has the same initialized Python state as a normally constructed solver.
Threads#
Different solvers may advance on different threads. Calls that release the
Python GIL pin their engine owner for the duration of the native operation.
Access from another thread to that same engine raises LifecycleError
promptly. This includes attempts to destroy the engine while it is advancing.
Coordinate access in the application and collect snapshots between steps.
Callbacks#
Step callbacks run on the advancing thread. They may inspect results and apply
forcing supported at that stage by the engine. They must not reenter lifecycle
operations such as step(), close() or destroy(), or replace callback
registrations while a callback is active; these operations raise
LifecycleError.
A Python exception cannot unwind through the native callback ABI. The binding
captures the first exception and re-raises that same exception when the native
operation returns. Later callbacks in that operation are suppressed. The
current native step may finish, so callback failure does not roll back the
simulation. This behavior applies to step(), stride(), steps() and
until(). Unregister or repair the failing callback before advancing again.
Testing a local build#
Use the project’s openswmm Conda environment for validation:
conda run --no-capture-output -n openswmm python -m pytest tests/engine
Run this command from python/ after building/installing the checkout into
that environment, or set PYTHONPATH to a staged build. Verify the loaded
extension path before testing:
conda run -n openswmm python -c "import openswmm.engine._solver as m; print(m.__file__)"
A successful test of an older installed package does not validate a changed
checkout. Rebuild all extensions after changing a shared .pxd declaration.