Source code for track_gen._src.runtime
"""Shared Warp runtime helpers for the utility family.
One home for the pieces every runtime utility (collision, collision_discs,
props, checkpoints, progress, course) duplicated: the ``_BIG`` sentinel, the
lazy ``wp.init()`` guard, the capture-aware host sync, the single process-wide
capture flag, and the array validator. Consolidating them means ONE flag
governs the whole family and ONE validator defines the error-message contract.
The generator-owned capture flags in ``warp_pipeline`` / ``warp_gate`` are
deliberately separate (pipeline-internal) and are NOT routed through here.
``_CAPTURE_LOCK`` below IS shared with the generators, by design: there is one
CUDA device stream, so at most one graph capture/replay may be in flight
process-wide regardless of which family started it.
"""
from __future__ import annotations
import threading
import warp as wp
_BIG = 1.0e30
_INITED = False
_CAPTURING = False
# Process-wide mutex serializing every CUDA graph capture AND replay (TrackGenerator,
# GateGenerator, Course). Captures record the device's current stream; a concurrent
# capture, replay, or plain kernel launch from another thread lands on that same stream
# and corrupts the recording (CUDA errors 401/900, or an async illegal-memory-access 700
# that poisons the context). The _CAPTURING flags above are also only mutated while this
# lock is held, which makes their save/restore idiom thread-safe. CPU paths have nothing
# to protect (eager execution); Course.generate() still takes the lock uniformly there —
# uncontended, harmless.
_CAPTURE_LOCK = threading.Lock()
def _init() -> None:
"""Initialize Warp once (idempotent). Must run before any wp.launch."""
global _INITED
if not _INITED:
wp.init()
_INITED = True
def _sync(device) -> None:
"""Host-block on a cuda device unless a capture is in progress.
Reads the module-global ``_CAPTURING`` so a single flag suppresses every
utility's post-launch synchronize during CUDA-graph capture.
"""
if _CAPTURING:
return
if "cuda" in str(device):
wp.synchronize()
[docs]
def set_capturing(flag: bool) -> None:
"""Toggle the family-wide capture flag.
While ``True``, no utility (collision, discs, props, checkpoints,
progress, course) performs a host synchronize, so ``query()`` /
``update()`` / ``sample()`` / ``bake()`` are safe to record into a
CUDA graph. The flag is PROCESS-WIDE: exactly one capturing sim loop at
a time is supported by design. Restore it (``set_capturing(False)`` or to
the saved previous value) after the capture region.
"""
global _CAPTURING
_CAPTURING = bool(flag)
def _check_arr(name: str, arr, shape: tuple, dtype, device) -> None:
"""Validate a wp.array's type, shape, dtype, and device.
Raises ``ValueError`` naming ``name`` on the first mismatch. Shared body
behind the family's ``_validate_inputs`` / ``_validate_position`` /
``_check_arr`` wrappers; ``shape`` is the expected shape tuple and
``device`` the expected (already canonicalized) device string.
"""
if not isinstance(arr, wp.array):
raise ValueError(f"{name} must be a wp.array, got {type(arr).__name__}")
if tuple(arr.shape) != tuple(shape):
raise ValueError(
f"{name} must have shape {tuple(shape)}, got {tuple(arr.shape)}")
if arr.dtype is not dtype:
want = getattr(dtype, "__name__", str(dtype))
got = getattr(arr.dtype, "__name__", str(arr.dtype))
raise ValueError(f"{name} must have dtype {want}, got {got}")
if str(arr.device) != str(device):
raise ValueError(
f"{name} must be on device {str(device)!r}, got "
f"{str(arr.device)!r}")