153 lines
5.7 KiB
Python
153 lines
5.7 KiB
Python
# FlightMonitor/controller/playback_data_processor.py
|
|
"""
|
|
Manages the processing of playback data from the PlaybackAdapter's output queue.
|
|
It updates the GUI's virtual clock, timeline, and dispatches flight states to the map.
|
|
"""
|
|
from queue import Queue, Empty as QueueEmpty
|
|
from typing import Optional, TYPE_CHECKING
|
|
import time
|
|
|
|
from flightmonitor.utils.logger import get_logger
|
|
from flightmonitor.data.data_constants import (
|
|
MSG_TYPE_FLIGHT_DATA,
|
|
MSG_TYPE_ADAPTER_STATUS,
|
|
STATUS_STOPPED,
|
|
)
|
|
from flightmonitor.data.common_models import CanonicalFlightState
|
|
|
|
if TYPE_CHECKING:
|
|
from flightmonitor.controller.app_controller import AppController
|
|
|
|
module_logger = get_logger(__name__)
|
|
|
|
GUI_QUEUE_CHECK_INTERVAL_MS = 100
|
|
|
|
|
|
class PlaybackDataProcessor:
|
|
"""
|
|
Processes flight data snapshots from a queue for playback, updating the GUI
|
|
(virtual clock, map, timeline slider) for each snapshot.
|
|
"""
|
|
|
|
def __init__(
|
|
self, app_controller: "AppController", data_queue: Queue
|
|
):
|
|
"""
|
|
Initializes the PlaybackDataProcessor.
|
|
|
|
Args:
|
|
app_controller: The main AppController instance.
|
|
data_queue: The queue from which to read playback data snapshots.
|
|
"""
|
|
self.app_controller = app_controller
|
|
self.data_queue = data_queue
|
|
self._gui_after_id: Optional[str] = None
|
|
module_logger.debug("PlaybackDataProcessor initialized.")
|
|
|
|
def start_processing(self):
|
|
"""Starts the periodic processing of the playback data queue."""
|
|
main_window = self.app_controller.main_window
|
|
if not (main_window and main_window.root and main_window.root.winfo_exists()):
|
|
module_logger.error(
|
|
"PlaybackDataProcessor: Cannot start queue processing, MainWindow or root is missing."
|
|
)
|
|
return
|
|
|
|
if self._gui_after_id:
|
|
try:
|
|
main_window.root.after_cancel(self._gui_after_id)
|
|
except Exception:
|
|
pass
|
|
|
|
module_logger.info(
|
|
"PlaybackDataProcessor: Starting playback data queue processing loop."
|
|
)
|
|
self._gui_after_id = main_window.root.after(
|
|
GUI_QUEUE_CHECK_INTERVAL_MS, self._process_queue_cycle
|
|
)
|
|
|
|
def stop_processing(self):
|
|
"""Stops the periodic processing loop."""
|
|
main_window = self.app_controller.main_window
|
|
if self._gui_after_id and main_window and main_window.root.winfo_exists():
|
|
try:
|
|
main_window.root.after_cancel(self._gui_after_id)
|
|
module_logger.info(
|
|
"PlaybackDataProcessor: Stopped queue processing loop."
|
|
)
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
self._gui_after_id = None
|
|
|
|
if self.data_queue:
|
|
while not self.data_queue.empty():
|
|
try:
|
|
self.data_queue.get_nowait()
|
|
except QueueEmpty:
|
|
break
|
|
|
|
def _process_queue_cycle(self):
|
|
"""
|
|
The core processing cycle that runs on the GUI thread.
|
|
It retrieves a data snapshot, updates the clock, and updates the map.
|
|
"""
|
|
main_window = self.app_controller.main_window
|
|
|
|
if not (main_window and main_window.root and main_window.root.winfo_exists()):
|
|
self._gui_after_id = None
|
|
return
|
|
|
|
try:
|
|
message = self.data_queue.get_nowait()
|
|
message_type = message.get("type")
|
|
|
|
if message_type == MSG_TYPE_FLIGHT_DATA:
|
|
snapshot_timestamp = message.get("timestamp")
|
|
payload_dict = message.get("payload", {})
|
|
flight_states: List[CanonicalFlightState] = payload_dict.get(
|
|
"canonical", []
|
|
)
|
|
|
|
playback_panel = (
|
|
main_window.function_notebook_panel.playback_panel
|
|
if hasattr(main_window.function_notebook_panel, "playback_panel")
|
|
else None
|
|
)
|
|
|
|
if snapshot_timestamp and playback_panel:
|
|
playback_panel.update_virtual_clock(snapshot_timestamp)
|
|
if self.app_controller.playback_adapter_thread:
|
|
start_ts = self.app_controller.playback_adapter_thread.start_ts
|
|
end_ts = self.app_controller.playback_adapter_thread.end_ts
|
|
playback_panel.update_timeline(snapshot_timestamp, start_ts, end_ts)
|
|
|
|
if main_window.map_manager_instance:
|
|
main_window.map_manager_instance.update_playback_frame(
|
|
flight_states, snapshot_timestamp
|
|
)
|
|
|
|
elif message_type == MSG_TYPE_ADAPTER_STATUS:
|
|
if message.get("status_code") == STATUS_STOPPED:
|
|
module_logger.info(
|
|
"Playback adapter finished. Stopping playback from processor."
|
|
)
|
|
self.app_controller.stop_playback(finished_normally=True)
|
|
|
|
self.data_queue.task_done()
|
|
|
|
except QueueEmpty:
|
|
pass
|
|
except Exception as e:
|
|
module_logger.error(f"Error in PlaybackDataProcessor cycle: {e}", exc_info=True)
|
|
self.app_controller.stop_playback(from_error=True)
|
|
|
|
if self.app_controller.is_playback_active:
|
|
if main_window and main_window.root and main_window.root.winfo_exists():
|
|
self._gui_after_id = main_window.root.after(
|
|
GUI_QUEUE_CHECK_INTERVAL_MS, self._process_queue_cycle
|
|
)
|
|
else:
|
|
self._gui_after_id = None
|
|
else:
|
|
self._gui_after_id = None |