diff --git a/game/ato/flight.py b/game/ato/flight.py index 26fe0726..d5142d2b 100644 --- a/game/ato/flight.py +++ b/game/ato/flight.py @@ -11,6 +11,7 @@ from .flightstate import FlightState, Uninitialized if TYPE_CHECKING: from game.dcs.aircrafttype import AircraftType + from game.sim.gameupdateevents import GameUpdateEvents from game.squadrons import Squadron, Pilot from game.theater import ControlPoint, MissionTarget from game.transfers import TransferOrder @@ -147,8 +148,10 @@ class Flight: def set_state(self, state: FlightState) -> None: self.state = state - def on_game_tick(self, time: datetime, duration: timedelta) -> None: - self.state.on_game_tick(time, duration) + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: + self.state.on_game_tick(events, time, duration) def should_halt_sim(self) -> bool: return self.state.should_halt_sim() diff --git a/game/ato/flightstate/completed.py b/game/ato/flightstate/completed.py index 5fe74c28..8be426ca 100644 --- a/game/ato/flightstate/completed.py +++ b/game/ato/flightstate/completed.py @@ -1,11 +1,19 @@ +from __future__ import annotations + from datetime import datetime, timedelta +from typing import TYPE_CHECKING from .flightstate import FlightState from ..starttype import StartType +if TYPE_CHECKING: + from game.sim.gameupdateevents import GameUpdateEvents + class Completed(FlightState): - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: return @property diff --git a/game/ato/flightstate/flightstate.py b/game/ato/flightstate/flightstate.py index bcaf5da8..0a6040f2 100644 --- a/game/ato/flightstate/flightstate.py +++ b/game/ato/flightstate/flightstate.py @@ -9,6 +9,7 @@ from game.ato.starttype import StartType if TYPE_CHECKING: from game.ato.flight import Flight from game.settings import Settings + from game.sim.gameupdateevents import GameUpdateEvents from game.threatzones import ThreatPoly @@ -18,7 +19,9 @@ class FlightState(ABC): self.settings = settings @abstractmethod - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: ... @property diff --git a/game/ato/flightstate/incombat.py b/game/ato/flightstate/incombat.py index 5fa80635..7aa15186 100644 --- a/game/ato/flightstate/incombat.py +++ b/game/ato/flightstate/incombat.py @@ -11,6 +11,7 @@ from ..starttype import StartType if TYPE_CHECKING: from game.sim.combat import FrozenCombat + from game.sim.gameupdateevents import GameUpdateEvents class InCombat(InFlight): @@ -32,7 +33,9 @@ class InCombat(InFlight): def estimate_speed(self) -> Speed: return self.previous_state.estimate_speed() - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: raise RuntimeError("Cannot simulate combat") @property diff --git a/game/ato/flightstate/inflight.py b/game/ato/flightstate/inflight.py index 9b51398b..c3724e10 100644 --- a/game/ato/flightstate/inflight.py +++ b/game/ato/flightstate/inflight.py @@ -17,6 +17,7 @@ from gen.flights.flightplan import LoiterFlightPlan if TYPE_CHECKING: from game.ato.flight import Flight from game.settings import Settings + from game.sim.gameupdateevents import GameUpdateEvents class InFlight(FlightState, ABC): @@ -88,7 +89,9 @@ class InFlight(FlightState, ABC): def advance_to_next_waypoint(self) -> None: self.flight.set_state(self.next_waypoint_state()) - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: self.elapsed_time += duration if self.elapsed_time > self.total_time_to_next_waypoint: self.advance_to_next_waypoint() diff --git a/game/ato/flightstate/navigating.py b/game/ato/flightstate/navigating.py index 85cf9970..b9870dae 100644 --- a/game/ato/flightstate/navigating.py +++ b/game/ato/flightstate/navigating.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime, timedelta from typing import TYPE_CHECKING from dcs import Point @@ -9,7 +10,7 @@ from game.ato.starttype import StartType from game.utils import Distance, LBS_TO_KG, Speed, meters if TYPE_CHECKING: - pass + from game.sim.gameupdateevents import GameUpdateEvents def lerp(v0: float, v1: float, t: float) -> float: @@ -17,6 +18,12 @@ def lerp(v0: float, v1: float, t: float) -> float: class Navigating(InFlight): + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: + super().on_game_tick(events, time, duration) + events.update_flight(self.flight) + def progress(self) -> float: return ( self.elapsed_time.total_seconds() diff --git a/game/ato/flightstate/startup.py b/game/ato/flightstate/startup.py index 4cc5a39e..53a602ed 100644 --- a/game/ato/flightstate/startup.py +++ b/game/ato/flightstate/startup.py @@ -11,6 +11,7 @@ from ..starttype import StartType if TYPE_CHECKING: from game.ato.flight import Flight from game.settings import Settings + from game.sim.gameupdateevents import GameUpdateEvents class StartUp(FlightState): @@ -18,7 +19,9 @@ class StartUp(FlightState): super().__init__(flight, settings) self.completion_time = now + flight.flight_plan.estimate_startup() - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: if time < self.completion_time: return self.flight.set_state(Taxi(self.flight, self.settings, time)) diff --git a/game/ato/flightstate/takeoff.py b/game/ato/flightstate/takeoff.py index 1ef8d22e..336c9874 100644 --- a/game/ato/flightstate/takeoff.py +++ b/game/ato/flightstate/takeoff.py @@ -12,6 +12,7 @@ from ...utils import LBS_TO_KG if TYPE_CHECKING: from game.ato.flight import Flight from game.settings import Settings + from game.sim.gameupdateevents import GameUpdateEvents class Takeoff(FlightState): @@ -20,7 +21,9 @@ class Takeoff(FlightState): # TODO: Not accounted for in FlightPlan, can cause discrepancy without loiter. self.completion_time = now + timedelta(seconds=30) - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: if time < self.completion_time: return self.flight.set_state(Navigating(self.flight, self.settings, waypoint_index=0)) diff --git a/game/ato/flightstate/taxi.py b/game/ato/flightstate/taxi.py index 31c3da90..2b88135c 100644 --- a/game/ato/flightstate/taxi.py +++ b/game/ato/flightstate/taxi.py @@ -11,6 +11,7 @@ from ..starttype import StartType if TYPE_CHECKING: from game.ato.flight import Flight from game.settings import Settings + from game.sim.gameupdateevents import GameUpdateEvents class Taxi(FlightState): @@ -18,7 +19,9 @@ class Taxi(FlightState): super().__init__(flight, settings) self.completion_time = now + flight.flight_plan.estimate_ground_ops() - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: if time < self.completion_time: return self.flight.set_state(Takeoff(self.flight, self.settings, time)) diff --git a/game/ato/flightstate/uninitialized.py b/game/ato/flightstate/uninitialized.py index 51ecbcf1..0493c30d 100644 --- a/game/ato/flightstate/uninitialized.py +++ b/game/ato/flightstate/uninitialized.py @@ -1,12 +1,20 @@ +from __future__ import annotations + from datetime import datetime, timedelta +from typing import TYPE_CHECKING from gen.flights.traveltime import TotEstimator from .flightstate import FlightState from ..starttype import StartType +if TYPE_CHECKING: + from game.sim.gameupdateevents import GameUpdateEvents + class Uninitialized(FlightState): - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: raise RuntimeError("Attempted to simulate flight that is not fully initialized") @property diff --git a/game/ato/flightstate/waitingforstart.py b/game/ato/flightstate/waitingforstart.py index 14e58a64..ff0cf106 100644 --- a/game/ato/flightstate/waitingforstart.py +++ b/game/ato/flightstate/waitingforstart.py @@ -13,6 +13,7 @@ from .taxi import Taxi if TYPE_CHECKING: from game.ato.flight import Flight from game.settings import Settings + from game.sim.gameupdateevents import GameUpdateEvents class WaitingForStart(FlightState): @@ -29,7 +30,9 @@ class WaitingForStart(FlightState): def start_type(self) -> StartType: return self.flight.start_type - def on_game_tick(self, time: datetime, duration: timedelta) -> None: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: if time < self.start_time: return diff --git a/game/sim/aircraftsimulation.py b/game/sim/aircraftsimulation.py index fdd7e7ec..06918bfb 100644 --- a/game/sim/aircraftsimulation.py +++ b/game/sim/aircraftsimulation.py @@ -18,35 +18,36 @@ from game.ato.flightstate import ( from game.ato.starttype import StartType from gen.flights.traveltime import TotEstimator from .combat import CombatInitiator, FrozenCombat -from .gameupdatecallbacks import GameUpdateCallbacks if TYPE_CHECKING: from game import Game + from .gameupdateevents import GameUpdateEvents class AircraftSimulation: - def __init__(self, game: Game, callbacks: GameUpdateCallbacks) -> None: + def __init__(self, game: Game) -> None: self.game = game - self.callbacks = callbacks self.combats: list[FrozenCombat] = [] def begin_simulation(self) -> None: self.reset() self.set_initial_flight_states() - def on_game_tick(self, time: datetime, duration: timedelta) -> bool: + def on_game_tick( + self, events: GameUpdateEvents, time: datetime, duration: timedelta + ) -> None: for flight in self.iter_flights(): - flight.on_game_tick(time, duration) + flight.on_game_tick(events, time, duration) # Finish updating all flights before checking for combat so that the new # positions are used. - CombatInitiator(self.game, self.combats, self.callbacks).update_active_combats() + CombatInitiator(self.game, self.combats, events).update_active_combats() # After updating all combat states, check for halts. for flight in self.iter_flights(): if flight.should_halt_sim(): - return True - return False + events.complete_simulation() + return def set_initial_flight_states(self) -> None: now = self.game.conditions.start_time diff --git a/game/sim/combat/combatinitiator.py b/game/sim/combat/combatinitiator.py index aed628ce..a95c3126 100644 --- a/game/sim/combat/combatinitiator.py +++ b/game/sim/combat/combatinitiator.py @@ -12,7 +12,7 @@ from .atip import AtIp from .defendingsam import DefendingSam from .joinablecombat import JoinableCombat from .samengagementzones import SamEngagementZones -from ..gameupdatecallbacks import GameUpdateCallbacks +from ..gameupdateevents import GameUpdateEvents if TYPE_CHECKING: from game import Game @@ -22,11 +22,11 @@ if TYPE_CHECKING: class CombatInitiator: def __init__( - self, game: Game, combats: list[FrozenCombat], callbacks: GameUpdateCallbacks + self, game: Game, combats: list[FrozenCombat], events: GameUpdateEvents ) -> None: self.game = game self.combats = combats - self.callbacks = callbacks + self.events = events def update_active_combats(self) -> None: blue_a2a = AircraftEngagementZones.from_ato(self.game.blue.ato) @@ -64,7 +64,7 @@ class CombatInitiator: logging.info(f"{flight} is joining existing combat {joined}") joined.join(flight) own_a2a.remove_flight(flight) - self.callbacks.on_combat_changed(joined) + self.events.update_combat(joined) elif (combat := self.check_flight_for_new_combat(flight, a2a, sam)) is not None: logging.info(f"Interrupting simulation because {combat.because()}") combat.update_flight_states() @@ -75,7 +75,7 @@ class CombatInitiator: a2a.update_for_combat(combat) own_a2a.update_for_combat(combat) self.combats.append(combat) - self.callbacks.on_add_combat(combat) + self.events.new_combat(combat) def check_flight_for_joined_combat( self, flight: Flight diff --git a/game/sim/gameloop.py b/game/sim/gameloop.py index e97db6f1..207d481e 100644 --- a/game/sim/gameloop.py +++ b/game/sim/gameloop.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING from .gamelooptimer import GameLoopTimer from .gameupdatecallbacks import GameUpdateCallbacks +from .gameupdateevents import GameUpdateEvents from .missionsimulation import MissionSimulation, SimulationAlreadyCompletedError from .simspeedsetting import SimSpeedSetting @@ -20,7 +21,9 @@ class GameLoop: self.game = game self.callbacks = callbacks self.timer = GameLoopTimer(self.tick) - self.sim = MissionSimulation(self.game, self.callbacks) + self.sim = MissionSimulation(self.game) + self.events = GameUpdateEvents() + self.last_update_time = datetime.now() self.started = False self.completed = False @@ -53,7 +56,7 @@ class GameLoop: self.pause() logging.info("Running sim to first contact") while not self.completed: - self.tick() + self.tick(suppress_events=True) def pause_and_generate_miz(self, output: Path) -> None: self.pause() @@ -68,13 +71,25 @@ class GameLoop: self.sim.process_results(debriefing) self.completed = True - def tick(self) -> None: + def send_update(self, rate_limit: bool) -> None: + now = datetime.now() + time_since_update = now - self.last_update_time + if not rate_limit or time_since_update >= timedelta(seconds=1 / 60): + self.callbacks.on_update(self.events) + self.events = GameUpdateEvents() + self.last_update_time = now + + def tick(self, suppress_events: bool = False) -> None: if not self.started: raise RuntimeError("Attempted to tick game loop before initialization") try: - self.completed = self.sim.tick() + self.sim.tick(self.events) + self.completed = self.events.simulation_complete + if not suppress_events: + self.send_update(rate_limit=True) if self.completed: self.pause() + self.send_update(rate_limit=False) logging.info(f"Simulation completed at {self.sim.time}") self.callbacks.on_simulation_complete() except SimulationAlreadyCompletedError: diff --git a/game/sim/gameupdatecallbacks.py b/game/sim/gameupdatecallbacks.py index 0208b50d..76e63139 100644 --- a/game/sim/gameupdatecallbacks.py +++ b/game/sim/gameupdatecallbacks.py @@ -2,10 +2,8 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from game.sim.combat import FrozenCombat +from game.sim.gameupdateevents import GameUpdateEvents # Ought to be frozen but mypy can't handle that: @@ -13,5 +11,4 @@ if TYPE_CHECKING: @dataclass class GameUpdateCallbacks: on_simulation_complete: Callable[[], None] - on_add_combat: Callable[[FrozenCombat], None] - on_combat_changed: Callable[[FrozenCombat], None] + on_update: Callable[[GameUpdateEvents], None] diff --git a/game/sim/gameupdateevents.py b/game/sim/gameupdateevents.py new file mode 100644 index 00000000..48230385 --- /dev/null +++ b/game/sim/gameupdateevents.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from game.ato import Flight + from game.sim.combat import FrozenCombat + + +class GameUpdateEvents: + def __init__(self) -> None: + self.simulation_complete = False + self.new_combats: list[FrozenCombat] = [] + self.updated_combats: list[FrozenCombat] = [] + self.updated_flights: list[Flight] = [] + + def complete_simulation(self) -> None: + self.simulation_complete = True + + def new_combat(self, combat: FrozenCombat) -> None: + self.new_combats.append(combat) + + def update_combat(self, combat: FrozenCombat) -> None: + self.updated_combats.append(combat) + + def update_flight(self, flight: Flight) -> None: + self.updated_flights.append(flight) diff --git a/game/sim/missionsimulation.py b/game/sim/missionsimulation.py index 49676d79..393bf918 100644 --- a/game/sim/missionsimulation.py +++ b/game/sim/missionsimulation.py @@ -9,11 +9,11 @@ from game.debriefing import Debriefing from game.missiongenerator import MissionGenerator from game.unitmap import UnitMap from .aircraftsimulation import AircraftSimulation -from .gameupdatecallbacks import GameUpdateCallbacks from .missionresultsprocessor import MissionResultsProcessor if TYPE_CHECKING: from game import Game + from .gameupdateevents import GameUpdateEvents TICK = timedelta(seconds=1) @@ -25,10 +25,10 @@ class SimulationAlreadyCompletedError(RuntimeError): class MissionSimulation: - def __init__(self, game: Game, callbacks: GameUpdateCallbacks) -> None: + def __init__(self, game: Game) -> None: self.game = game self.unit_map: Optional[UnitMap] = None - self.aircraft_simulation = AircraftSimulation(self.game, callbacks) + self.aircraft_simulation = AircraftSimulation(self.game) self.completed = False self.time = self.game.conditions.start_time @@ -36,12 +36,13 @@ class MissionSimulation: self.time = self.game.conditions.start_time self.aircraft_simulation.begin_simulation() - def tick(self) -> bool: + def tick(self, events: GameUpdateEvents) -> GameUpdateEvents: self.time += TICK if self.completed: raise RuntimeError("Simulation already completed") - self.completed = self.aircraft_simulation.on_game_tick(self.time, TICK) - return self.completed + self.aircraft_simulation.on_game_tick(events, self.time, TICK) + self.completed = events.simulation_complete + return events def generate_miz(self, output: Path) -> None: self.unit_map = MissionGenerator(self.game, self.time).generate_miz(output) diff --git a/qt_ui/models.py b/qt_ui/models.py index 75e30409..40c1d31c 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -17,6 +17,7 @@ from game.ato.flight import Flight from game.ato.flighttype import FlightType from game.ato.package import Package from game.game import Game +from game.sim.gameupdateevents import GameUpdateEvents from game.squadrons.squadron import Pilot, Squadron from game.theater.missiontarget import MissionTarget from game.transfers import PendingTransfers, TransferOrder @@ -211,7 +212,7 @@ class PackageModel(QAbstractListModel): for flight in self.package.flights: yield flight - def on_sim_update(self) -> None: + def on_sim_update(self, _events: GameUpdateEvents) -> None: self.dataChanged.emit(self.index(0), self.index(self.rowCount())) @@ -311,7 +312,7 @@ class AtoModel(QAbstractListModel): for package in self.ato.packages: yield self.package_models.acquire(package) - def on_sim_update(self) -> None: + def on_sim_update(self, _events: GameUpdateEvents) -> None: self.dataChanged.emit(self.index(0), self.index(self.rowCount())) diff --git a/qt_ui/simcontroller.py b/qt_ui/simcontroller.py index 4c8b0afd..90ee7bda 100644 --- a/qt_ui/simcontroller.py +++ b/qt_ui/simcontroller.py @@ -8,11 +8,10 @@ from typing import Callable, Optional, TYPE_CHECKING from PySide2.QtCore import QObject, Signal from game.polldebriefingfilethread import PollDebriefingFileThread -from game.sim.combat import FrozenCombat from game.sim.gameloop import GameLoop from game.sim.gameupdatecallbacks import GameUpdateCallbacks +from game.sim.gameupdateevents import GameUpdateEvents from game.sim.simspeedsetting import SimSpeedSetting -from qt_ui.simupdatethread import SimUpdateThread if TYPE_CHECKING: from game import Game @@ -20,19 +19,15 @@ if TYPE_CHECKING: class SimController(QObject): - sim_update = Signal() + sim_update = Signal(GameUpdateEvents) sim_speed_reset = Signal(SimSpeedSetting) simulation_complete = Signal() - on_add_combat = Signal(FrozenCombat) - on_combat_changed = Signal(FrozenCombat) def __init__(self, game: Optional[Game]) -> None: super().__init__() self.game_loop: Optional[GameLoop] = None self.recreate_game_loop(game) self.started = False - self._sim_update_thread = SimUpdateThread(self.sim_update.emit) - self._sim_update_thread.start() @property def completed(self) -> bool: @@ -54,12 +49,8 @@ class SimController(QObject): self.recreate_game_loop(game) self.sim_speed_reset.emit(SimSpeedSetting.PAUSED) - def shut_down(self) -> None: - self._sim_update_thread.stop() - def recreate_game_loop(self, game: Optional[Game]) -> None: if self.game_loop is not None: - self._sim_update_thread.on_sim_pause() self.game_loop.pause() self.game_loop = None if game is not None: @@ -67,8 +58,7 @@ class SimController(QObject): game, GameUpdateCallbacks( self.on_simulation_complete, - self.on_add_combat.emit, - self.on_combat_changed.emit, + self.sim_update.emit, ), ) self.started = False @@ -81,17 +71,11 @@ class SimController(QObject): self.game_loop.start() self.started = True self.game_loop.set_simulation_speed(simulation_speed) - if simulation_speed is SimSpeedSetting.PAUSED: - self._sim_update_thread.on_sim_pause() - else: - self._sim_update_thread.on_sim_unpause() def run_to_first_contact(self) -> None: self.game_loop.run_to_first_contact() - self.sim_update.emit() def generate_miz(self, output: Path) -> None: - self._sim_update_thread.on_sim_pause() self.game_loop.pause_and_generate_miz(output) def wait_for_debriefing( @@ -104,14 +88,11 @@ class SimController(QObject): def debrief_current_state( self, state_path: Path, force_end: bool = False ) -> Debriefing: - self._sim_update_thread.on_sim_pause() return self.game_loop.pause_and_debrief(state_path, force_end) def process_results(self, debriefing: Debriefing) -> None: - self._sim_update_thread.on_sim_pause() return self.game_loop.complete_with_results(debriefing) def on_simulation_complete(self) -> None: logging.debug("Simulation complete") - self._sim_update_thread.on_sim_pause() self.simulation_complete.emit() diff --git a/qt_ui/simupdatethread.py b/qt_ui/simupdatethread.py deleted file mode 100644 index 8cae1306..00000000 --- a/qt_ui/simupdatethread.py +++ /dev/null @@ -1,45 +0,0 @@ -from threading import Event, Thread, Timer -from typing import Callable - - -class SimUpdateThread(Thread): - def __init__(self, update_callback: Callable[[], None]) -> None: - super().__init__() - self.update_callback = update_callback - self.running = False - self.should_shutdown = False - self._interrupt = Event() - self._timer = self._make_timer() - - def run(self) -> None: - while True: - self._interrupt.wait() - self._interrupt.clear() - if self.should_shutdown: - return - if self.running: - self.update_callback() - self._timer = self._make_timer() - self._timer.start() - - def on_sim_pause(self) -> None: - self._timer.cancel() - self._timer = self._make_timer() - self.running = False - - def on_sim_unpause(self) -> None: - if not self.running: - self.running = True - self._timer.start() - - def stop(self) -> None: - self.should_shutdown = True - self._interrupt.set() - - def on_timer_elapsed(self) -> None: - self._timer = self._make_timer() - self._timer.start() - self._interrupt.set() - - def _make_timer(self) -> Timer: - return Timer(1 / 60, lambda: self._interrupt.set()) diff --git a/qt_ui/widgets/QConditionsWidget.py b/qt_ui/widgets/QConditionsWidget.py index 355c076f..07ddee56 100644 --- a/qt_ui/widgets/QConditionsWidget.py +++ b/qt_ui/widgets/QConditionsWidget.py @@ -12,6 +12,7 @@ from PySide2.QtWidgets import ( from dcs.weather import CloudPreset, Weather as PydcsWeather import qt_ui.uiconstants as CONST +from game.sim.gameupdateevents import GameUpdateEvents from game.utils import mps from game.weather import Conditions, TimeOfDay from qt_ui.simcontroller import SimController @@ -55,7 +56,7 @@ class QTimeTurnWidget(QGroupBox): sim_controller.sim_update.connect(self.on_sim_update) - def on_sim_update(self) -> None: + def on_sim_update(self, _events: GameUpdateEvents) -> None: time = self.sim_controller.current_time_in_sim if time is None: self.date_display.setText("") diff --git a/qt_ui/widgets/map/model/mapmodel.py b/qt_ui/widgets/map/model/mapmodel.py index 7d144287..c046a204 100644 --- a/qt_ui/widgets/map/model/mapmodel.py +++ b/qt_ui/widgets/map/model/mapmodel.py @@ -13,6 +13,7 @@ from game.sim.combat import FrozenCombat from game.sim.combat.aircombat import AirCombat from game.sim.combat.atip import AtIp from game.sim.combat.defendingsam import DefendingSam +from game.sim.gameupdateevents import GameUpdateEvents from game.theater import ( ConflictTheater, ) @@ -106,8 +107,6 @@ class MapModel(QObject): self.set_flight_selection ) sim_controller.sim_update.connect(self.on_sim_update) - sim_controller.on_add_combat.connect(self.on_add_combat) - sim_controller.on_combat_changed.connect(self.on_combat_changed) self.reset() def clear(self) -> None: @@ -128,9 +127,17 @@ class MapModel(QObject): self._ip_combats = [] self.cleared.emit() - def on_sim_update(self) -> None: + def on_sim_update(self, events: GameUpdateEvents) -> None: + # TODO: Only update flights with changes. + # We have the signal of which flights have updates, but no fast lookup for + # Flight -> FlightJs since Flight isn't hashable. Faster to update every flight + # than do do the O(n^2) filtered update. for flight in self._flights.values(): flight.positionChanged.emit() + for combat in events.new_combats: + self.on_add_combat(combat) + for combat in events.updated_combats: + self.on_combat_changed(combat) def set_package_selection(self, index: int) -> None: self.deselect_current_flight() diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index c17b20e4..d62e2265 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -404,7 +404,6 @@ class QLiberationWindow(QMainWindow): QMessageBox.Yes | QMessageBox.No, ) if result == QMessageBox.Yes: - self.sim_controller.shut_down() super().closeEvent(event) self.dialog = None else: