diff --git a/openpilot/selfdrive/ui/onroad/cameraview.py b/openpilot/selfdrive/ui/onroad/cameraview.py index 729f16cc44..aa222e6ffb 100644 --- a/openpilot/selfdrive/ui/onroad/cameraview.py +++ b/openpilot/selfdrive/ui/onroad/cameraview.py @@ -88,8 +88,9 @@ class CameraView(Widget): def __init__(self, name: str, stream_type: VisionStreamType): super().__init__() self._name = name - # Primary stream - self.client = VisionIpcClient(name, stream_type, conflate=True) + # Reuse subscribers: destroying a VisionIpcClient doesn't reclaim its msgq reader slot. + self._clients = {stream: VisionIpcClient(name, stream, conflate=True) for stream in VisionStreamType} + self.client = self._clients[stream_type] self._stream_type: VisionStreamType = stream_type self.available_streams: list[VisionStreamType] = [] @@ -130,16 +131,14 @@ class CameraView(Widget): ui_state.add_offroad_transition_callback(self._offroad_transition) def _offroad_transition(self): - # Reconnect if not first time going onroad + # Discard queued frames from the previous drive without recreating subscribers. if ui_state.is_onroad() and self.frame is not None: - # Prevent old frames from showing when going onroad. Qt has a separate thread - # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough - # and only clears internal buffers, not the message queue. self.frame = None - self.available_streams.clear() - if self.client: - del self.client - self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) + for client in self._clients.values(): + client.recv(timeout_ms=0) + self._target_client = None + self._target_stream_type = None + self._switching = False def _set_placeholder_color(self, color: rl.Color): """Set a placeholder color to be drawn when no frame is available.""" @@ -154,11 +153,8 @@ class CameraView(Widget): cloudlog.debug(f'Preparing switch from {self._stream_type} to {stream_type}') - if self._target_client: - del self._target_client - self._target_stream_type = stream_type - self._target_client = VisionIpcClient(self._name, stream_type, conflate=True) + self._target_client = self._clients[stream_type] self._switching = True @property @@ -181,6 +177,8 @@ class CameraView(Widget): self.frame = None self.available_streams.clear() self.client = None + self._target_client = None + self._clients.clear() def __del__(self): self.close() @@ -350,10 +348,6 @@ class CameraView(Widget): def _complete_switch(self) -> None: """Instantly switch to target stream.""" cloudlog.debug(f"Switching to {self._target_stream_type}") - # Clean up current resources - if self.client: - del self.client - # Switch to target assert self._target_client is not None and self._target_stream_type is not None self.client = self._target_client