Remove direct visualizer blank prints#5627
Conversation
Greptile SummaryThis PR removes noisy blank-line prints from the Rerun and Viser visualizer startup by routing the browser URL through a new deferred
Confidence Score: 4/5Safe to merge; changes are limited to startup logging and default browser-open behavior with no impact on simulation correctness. The core simulation and training paths are untouched. The three findings are style/behavior edge cases: verbose=False hardcoded for Viser changes what the verbose flag means, the signature-inspection suppression in the Rerun browser-suppression wrapper has a silent fallback, and the module-attribute monkey-patches are not protected against concurrent initialization. None of these affect normal single-process runs. rerun_visualizer.py and viser_visualizer.py — the monkey-patch initialization blocks and the verbose=False hardcoding are worth a second look. Important Files Changed
Sequence DiagramsequenceDiagram
participant Cfg as RerunVisualizerCfg / ViserVisualizerCfg
participant Viz as RerunVisualizer / ViserVisualizer
participant Base as BaseVisualizer
participant SC as SimulationContext
Cfg->>Viz: initialize(scene_data_provider)
Viz->>Viz: create viewer (NewtonViewerRerun / NewtonViewerViser)
Note over Viz: open_browser defaults to False
Viz->>Base: _log_viewer_url(name, url)
Base->>Base: append box to _deferred_startup_messages
Viz-->>SC: (initialization complete)
SC->>SC: update_visualizers(dt)
loop first call per visualizer
SC->>Base: flush_startup_messages()
Base->>Base: print URL box to stdout
Base->>Base: _deferred_startup_messages.clear()
end
SC->>Viz: viz.step(dt)
SC->>SC: update_visualizers(dt) [subsequent calls]
SC->>Base: flush_startup_messages()
Note over Base: list is empty — no-op
SC->>Viz: viz.step(dt)
Reviews (1): Last reviewed commit: "Remove direct visualizer blank prints" | Re-trigger Greptile |
| self._viewer = NewtonViewerViser( | ||
| port=self.cfg.port, | ||
| bind_address=self.cfg.bind_address, | ||
| label=self.cfg.label, | ||
| verbose=False, | ||
| share=self.cfg.share, | ||
| record_to_viser=record_to_viser, | ||
| metadata=metadata or {}, | ||
| ) |
There was a problem hiding this comment.
verbose=False is hardcoded here, so cfg.verbose=True no longer exposes viser's internal startup logs — it only controls whether the URL box is printed. Previously _suppress_viser_startup_logs(enabled=not self.cfg.verbose) honoured the config for both. Any user who sets verbose=True to get raw websocket/server startup output for debugging will see only the URL box and nothing else.
| self._viewer = NewtonViewerViser( | |
| port=self.cfg.port, | |
| bind_address=self.cfg.bind_address, | |
| label=self.cfg.label, | |
| verbose=False, | |
| share=self.cfg.share, | |
| record_to_viser=record_to_viser, | |
| metadata=metadata or {}, | |
| ) | |
| self._viewer = NewtonViewerViser( | |
| port=self.cfg.port, | |
| bind_address=self.cfg.bind_address, | |
| label=self.cfg.label, | |
| verbose=self.cfg.verbose, | |
| share=self.cfg.share, | |
| record_to_viser=record_to_viser, | |
| metadata=metadata or {}, | |
| ) |
| def _serve_web_viewer_without_browser(*serve_args, **serve_kwargs): | ||
| with contextlib.suppress(TypeError, ValueError): | ||
| supports_open_browser = "open_browser" in inspect.signature(original_serve_web_viewer).parameters | ||
| if supports_open_browser: | ||
| serve_kwargs.setdefault("open_browser", False) | ||
| return original_serve_web_viewer(*serve_args, **serve_kwargs) |
There was a problem hiding this comment.
Silent inspection failure may still open browser
If inspect.signature(original_serve_web_viewer) raises TypeError or ValueError (e.g., a C-extension binding in a future rerun release), the entire with contextlib.suppress(...) block exits silently without setting open_browser=False in serve_kwargs. The original rr.serve_web_viewer is then called with unmodified kwargs, potentially opening the browser even when open_browser=False was configured.
| with contextlib.ExitStack() as stack: | ||
| rr.serve_web_viewer = _serve_web_viewer_without_browser | ||
| stack.callback(setattr, rr, "serve_web_viewer", original_serve_web_viewer) | ||
| super().__init__(*args, **kwargs) |
There was a problem hiding this comment.
Module-level attribute swap is not thread-safe
rr.serve_web_viewer is replaced on the rr module for the duration of super().__init__. If two NewtonViewerRerun instances are created concurrently (e.g., in a multi-process or multi-threaded test harness), thread B may capture the already-patched wrapper as its original_serve_web_viewer, and after thread A restores the real function, thread B would write the patched wrapper back. The same pattern applies to the viser.ViserServer swap in NewtonViewerViser.__init__. A threading lock around the swap-and-restore block would eliminate the race.
There was a problem hiding this comment.
Code Review: PR #5627 — Remove direct visualizer blank prints
Thank you for this cleanup PR! The changes improve user experience by deferring visualizer URL display until the simulation loop starts, preventing noise during initialization. The implementation is solid overall, with good test coverage for the new functionality.
🔬 Design Assessment
Architecture fit: The deferred startup message pattern is well-designed. Queuing messages in _log_viewer_url() and flushing via flush_startup_messages() in update_visualizers() ensures URLs appear at the right time (just before the simulation loop) rather than during potentially noisy initialization.
Cross-module impact: Self-contained. The changes affect only the visualizer subsystem and don't break any existing interfaces.
🔵 Suggestions
1. flush_startup_messages() called on every frame — source/isaaclab/isaaclab/sim/simulation_context.py:764
The flush_startup_messages() method is called on every update_visualizers() invocation. While the implementation clears the list after printing (making subsequent calls no-ops), iterating over all visualizers and calling this method every frame is unnecessary overhead for a one-time operation.
Consider tracking whether startup messages have been flushed to avoid repeated calls:
# In SimulationContext
if not self._visualizers:
return
if not getattr(self, '_startup_messages_flushed', False):
for viz in self._visualizers:
viz.flush_startup_messages()
self._startup_messages_flushed = TrueAlternatively, flush only once per visualizer by adding a flag in BaseVisualizer.
2. verbose parameter disconnect in NewtonViewerViser — source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py:272
NewtonViewerViser.__init__ accepts a verbose parameter, but ViserVisualizer._create_viewer() always passes verbose=False. This means the verbose parameter in NewtonViewerViser has no effect at runtime. Consider either:
- Removing the
verboseparameter fromNewtonViewerVisersince it's always False - Or passing
self.cfg.verboseto allow users to control internal viser logging
This is minor since the current behavior (suppress internal logs, use cfg.verbose for Isaac Lab URL logging) appears intentional.
🧪 Test Coverage
The PR includes good test coverage:
- ✅
test_rendering_without_visualizers_initializes_kit_backend— verifies rendering mode still creates Kit backend - ✅
test_non_rendering_without_visualizers_defers_backend_initialization— verifies no backend when not needed - ✅
test_kit_visualizer_initializes_kit_backend_when_rendering_flag_is_false— verifies visualizer overrides rendering flag - ✅
test_rendering_context_authors_visible_usd_point_instancer— verifies USD markers work with rendering - ✅
test_viser_visualizer_open_browser_is_opt_in— verifies open_browser=False default - ✅
test_rerun_visualizer_open_browser_is_opt_in— verifies open_browser=False default - ✅
test_web_visualizer_cfgs_do_not_open_browser_by_default— verifies config defaults
The deferred message flushing is exercised through integration tests. Consider adding a unit test specifically for _log_viewer_url() URL formatting with edge cases (e.g., URLs without ports).
✅ Summary
Verdict: Ship it
The changes are well-implemented with appropriate test coverage. The suggestions above are minor improvements that could be addressed in follow-up work. The fix for visualization marker backend initialization (sim.is_rendering check) is a good catch that ensures USD markers work during camera rendering even without explicit visualizers.
Summary
Validation
python3 -m py_compile source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py