Source code for genesis.recorders.recorder_manager
from typing import TYPE_CHECKING, Any, Callable
import genesis as gs
if TYPE_CHECKING:
from .base_recorder import Recorder, RecorderOptions
[docs]class RecorderManager:
"""
Manage the creation, processing, and cleanup of all data recorders.
Parameters
----------
step_dt: float
The simulation time step.
"""
RECORDER_TYPES_MAP = {}
def __init__(self, step_dt: float):
self._step_dt = step_dt
self._recorders: list["Recorder"] = []
self._is_recording = False
self._is_built = False
[docs] @gs.assert_unbuilt
def add_recorder(self, data_func: Callable[[], Any], rec_options: "RecorderOptions") -> "Recorder":
"""
Automatically read and process data. See RecorderOptions for more details.
Parameters
----------
data_func: Callable[[], Any]
A callable with no arguments that returns the data a step records. A recorder that overrides
'Recorder._get_frame' receives from it the object it reads the frame from (the trajectory recorder receives
a TrajectorySource).
rec_options: RecorderOptions
The options for the recorder which determines how the data is recorded and processed.
Returns
-------
recorder : Recorder
The created recorder object.
"""
recorder_cls = RecorderManager.RECORDER_TYPES_MAP[type(rec_options)]
recorder = recorder_cls(self, rec_options, data_func)
self._recorders.append(recorder)
return recorder
[docs] @gs.assert_unbuilt
def build(self):
"""Start data recording."""
# A recorder that fails to build leaves none of the recorders started before it running
started = []
try:
for recorder in self._recorders:
recorder.build()
recorder.start()
started.append(recorder)
except Exception:
# Every started recorder is stopped before a failure is raised: the first stop that raised, with the build
# failure as its context, or the build failure itself
stop_error = None
for recorder in started:
try:
recorder.stop()
except Exception as error:
if stop_error is None:
stop_error = error
if stop_error is not None:
raise stop_error
raise
self._is_recording = True
self._is_built = True
[docs] @gs.assert_built
def stop(self):
"""Stop and complete data recording."""
if not self._is_recording:
gs.logger.warning("[DataRecorder] Ignoring stop(): data recording is not active.")
else:
self._is_recording = False
# Every recorder is stopped and flushed before the first failure is raised
error = None
for recorder in self._recorders:
try:
recorder.stop()
except Exception as recorder_error:
if error is None:
error = recorder_error
self._recorders.clear()
if error is not None:
raise error
[docs] @gs.assert_built
def reset(self, envs_idx=None):
for recorder in self._recorders:
if recorder.is_alive:
recorder.reset(envs_idx)
recorder.start()
[docs] @gs.assert_built
def step(self, global_step: int):
"""
Increment the step count and process data from each recording configuration.
In threaded mode, data is put in queues. In non-threaded mode, data is processed synchronously.
"""
if not self._is_recording:
return
for recorder in self._recorders:
recorder.step(global_step)
[docs] @gs.assert_built
def record(self, global_step: int):
"""Record the current state through every recorder, whatever its sampling rate."""
if not self._is_recording:
return
# Every recorder gets the state before the first failure is raised: it is the state each log ends with
error = None
for recorder in self._recorders:
try:
recorder.record(global_step)
except Exception as recorder_error:
if error is None:
error = recorder_error
if error is not None:
raise error
@property
def recorders(self) -> "list[Recorder]":
return gs.List(self._recorders)
@property
def is_recording(self) -> bool:
return self._is_recording
@property
def is_built(self) -> bool:
return self._is_built
def register_recording(options_cls: type["RecorderOptions"]):
def _impl(recorder_cls: type["Recorder"]):
RecorderManager.RECORDER_TYPES_MAP[options_cls] = recorder_cls
return recorder_cls
return _impl