From 87bf3110c84371506f06fe3939205c37678b450e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 31 Oct 2021 18:07:42 -0700 Subject: [PATCH] Add play/pause features to the sim. There's no UI feedback for this yet other than the log messages. https://github.com/dcs-liberation/dcs_liberation/issues/1704 --- game/debriefing.py | 62 +-------------- game/polldebriefingfilethread.py | 56 ++++++++++++++ game/sim/aircraftsimulation.py | 21 +----- game/sim/gameloop.py | 73 ++++++++++++++++++ game/sim/gamelooptimer.py | 40 ++++++++++ game/sim/missionsimulation.py | 30 ++++++-- game/sim/simspeedsetting.py | 28 +++++++ qt_ui/__init__.py | 0 qt_ui/simcontroller.py | 75 +++++++++++++++++++ qt_ui/widgets/QTopPanel.py | 32 ++++---- qt_ui/widgets/simspeedcontrols.py | 35 +++++++++ qt_ui/windows/QLiberationWindow.py | 9 ++- .../windows/QWaitingForMissionResultWindow.py | 20 ++--- 13 files changed, 369 insertions(+), 112 deletions(-) create mode 100644 game/polldebriefingfilethread.py create mode 100644 game/sim/gameloop.py create mode 100644 game/sim/gamelooptimer.py create mode 100644 game/sim/simspeedsetting.py create mode 100644 qt_ui/__init__.py create mode 100644 qt_ui/simcontroller.py create mode 100644 qt_ui/widgets/simspeedcontrols.py diff --git a/game/debriefing.py b/game/debriefing.py index 0a57132d..27cff023 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -1,17 +1,11 @@ from __future__ import annotations import itertools -import json import logging -import os -import threading -import time from collections import defaultdict from dataclasses import dataclass, field -from pathlib import Path from typing import ( Any, - Callable, Dict, Iterator, List, @@ -19,6 +13,7 @@ from typing import ( Union, ) +from game.ato.flight import Flight from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType from game.theater import Airfield, ControlPoint @@ -27,16 +22,14 @@ from game.unitmap import ( AirliftUnits, Building, ConvoyUnit, + FlyingUnit, FrontLineUnit, GroundObjectUnit, UnitMap, - FlyingUnit, ) -from game.ato.flight import Flight if TYPE_CHECKING: from game import Game - from game.sim import MissionSimulation DEBRIEFING_LOG_EXTENSION = "log" @@ -356,54 +349,3 @@ class Debriefing: captures.append(BaseCaptureEvent(control_point, captured_by_player)) return captures - - -class PollDebriefingFileThread(threading.Thread): - """Thread class with a stop() method. The thread itself has to check - regularly for the stopped() condition.""" - - def __init__( - self, - callback: Callable[[Debriefing], None], - mission_simulation: MissionSimulation, - ) -> None: - super().__init__() - self._stop_event = threading.Event() - self.callback = callback - self.mission_sim = mission_simulation - - def stop(self) -> None: - self._stop_event.set() - - def stopped(self) -> bool: - return self._stop_event.is_set() - - def run(self) -> None: - if os.path.isfile("state.json"): - last_modified = os.path.getmtime("state.json") - else: - last_modified = 0 - while not self.stopped(): - try: - if ( - os.path.isfile("state.json") - and os.path.getmtime("state.json") > last_modified - ): - self.callback( - self.mission_sim.debrief_current_state(Path("state.json")) - ) - break - except json.JSONDecodeError: - logging.exception( - "Failed to decode state.json. Probably attempted read while DCS " - "was still writing the file. Will retry in 5 seconds." - ) - time.sleep(5) - - -def wait_for_debriefing( - callback: Callable[[Debriefing], None], mission_simulation: MissionSimulation -) -> PollDebriefingFileThread: - thread = PollDebriefingFileThread(callback, mission_simulation) - thread.start() - return thread diff --git a/game/polldebriefingfilethread.py b/game/polldebriefingfilethread.py new file mode 100644 index 00000000..f637168a --- /dev/null +++ b/game/polldebriefingfilethread.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import json +import logging +import os +import time +from pathlib import Path +from threading import Event, Thread +from typing import Callable, TYPE_CHECKING + +if TYPE_CHECKING: + from game.debriefing import Debriefing + from game.sim import MissionSimulation + + +class PollDebriefingFileThread(Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" + + def __init__( + self, + callback: Callable[[Debriefing], None], + mission_sim: MissionSimulation, + ) -> None: + super().__init__() + self._stop_event = Event() + self.callback = callback + self.mission_sim = mission_sim + + def stop(self) -> None: + self._stop_event.set() + + def stopped(self) -> bool: + return self._stop_event.is_set() + + def run(self) -> None: + if os.path.isfile("state.json"): + last_modified = os.path.getmtime("state.json") + else: + last_modified = 0 + while not self.stopped(): + try: + if ( + os.path.isfile("state.json") + and os.path.getmtime("state.json") > last_modified + ): + self.callback( + self.mission_sim.debrief_current_state(Path("state.json")) + ) + break + except json.JSONDecodeError: + logging.exception( + "Failed to decode state.json. Probably attempted read while DCS " + "was still writing the file. Will retry in 5 seconds." + ) + time.sleep(5) diff --git a/game/sim/aircraftsimulation.py b/game/sim/aircraftsimulation.py index 3f844b99..7dde4601 100644 --- a/game/sim/aircraftsimulation.py +++ b/game/sim/aircraftsimulation.py @@ -1,7 +1,6 @@ from __future__ import annotations import itertools -import logging from collections import Iterator from datetime import datetime, timedelta @@ -24,30 +23,17 @@ if TYPE_CHECKING: from game import Game -TICK = timedelta(seconds=1) - - class AircraftSimulation: def __init__(self, game: Game) -> None: self.game = game - self.time = self.game.conditions.start_time - def run(self) -> None: + def begin_simulation(self) -> None: self.reset() self.set_initial_flight_states() - if self.game.settings.fast_forward_to_first_contact: - self.simulate_until_first_contact() - logging.info(f"Mission simulation completed at {self.time}") - def simulate_until_first_contact(self) -> None: - while True: - self.time += TICK - if self.tick(): - return - - def tick(self) -> bool: + def on_game_tick(self, time: datetime, duration: timedelta) -> bool: for flight in self.iter_flights(): - flight.on_game_tick(self.time, TICK) + flight.on_game_tick(time, duration) # Finish updating all flights before computing engagement zones so that the new # positions are used. @@ -83,7 +69,6 @@ class AircraftSimulation: raise ValueError(f"Unknown start type {flight.start_type} for {flight}") def reset(self) -> None: - self.time = self.game.conditions.start_time for flight in self.iter_flights(): flight.set_state(Uninitialized(flight, self.game.settings)) diff --git a/game/sim/gameloop.py b/game/sim/gameloop.py new file mode 100644 index 00000000..a2cb3ff5 --- /dev/null +++ b/game/sim/gameloop.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Callable, TYPE_CHECKING + +from .gamelooptimer import GameLoopTimer +from .missionsimulation import MissionSimulation, SimulationAlreadyCompletedError +from .simspeedsetting import SimSpeedSetting + +if TYPE_CHECKING: + from game import Game + from game.debriefing import Debriefing + + +class GameLoop: + def __init__(self, game: Game, on_complete: Callable[[], None]) -> None: + self.game = game + self.on_complete = on_complete + self.timer = GameLoopTimer(self.tick) + self.sim = MissionSimulation(self.game) + self.started = False + self.completed = False + + def start(self) -> None: + if self.started: + raise RuntimeError("Cannot start game loop because it has already started") + self.started = True + self.sim.begin_simulation() + + def pause(self) -> None: + self.set_simulation_speed(SimSpeedSetting.PAUSED) + + def set_simulation_speed(self, simulation_speed: SimSpeedSetting) -> None: + self.timer.stop() + if simulation_speed != self.timer.simulation_speed: + logging.info(f"Speed changed to {simulation_speed}") + if not self.started: + self.start() + self.timer.set_speed(simulation_speed) + + def run_to_first_contact(self) -> None: + self.pause() + logging.info("Running sim to first contact") + while not self.completed: + self.tick() + + def pause_and_generate_miz(self, output: Path) -> None: + self.pause() + self.sim.generate_miz(output) + + def pause_and_debrief(self, state_path: Path, force_end: bool) -> Debriefing: + self.pause() + return self.sim.debrief_current_state(state_path, force_end) + + def complete_with_results(self, debriefing: Debriefing) -> None: + self.pause() + self.sim.process_results(debriefing) + self.completed = True + + def tick(self) -> None: + if not self.started: + raise RuntimeError("Attempted to tick game loop before initialization") + try: + self.completed = self.sim.tick() + if self.completed: + self.pause() + logging.info(f"Simulation completed at {self.sim.time}") + self.on_complete() + else: + logging.info(f"Simulation continued at {self.sim.time}") + except SimulationAlreadyCompletedError: + logging.exception("Attempted to tick already completed sim") diff --git a/game/sim/gamelooptimer.py b/game/sim/gamelooptimer.py new file mode 100644 index 00000000..b089f5e7 --- /dev/null +++ b/game/sim/gamelooptimer.py @@ -0,0 +1,40 @@ +from threading import Lock, Timer +from typing import Callable, Optional + +from .simspeedsetting import SimSpeedSetting + + +class GameLoopTimer: + def __init__(self, callback: Callable[[], None]) -> None: + self.callback = callback + self.simulation_speed = SimSpeedSetting.PAUSED + self._timer: Optional[Timer] = None + self._timer_lock = Lock() + + def set_speed(self, simulation_speed: SimSpeedSetting) -> None: + with self._timer_lock: + self._stop() + self.simulation_speed = simulation_speed + self._recreate_timer() + + def stop(self) -> None: + with self._timer_lock: + self._stop() + + def _stop(self) -> None: + if self._timer is not None: + self._timer.cancel() + + def _recreate_timer(self) -> None: + self._stop() + factor = self.simulation_speed.speed_factor + if not factor: + self._timer = None + return None + self._timer = Timer(1 / factor, self._tick) + self._timer.start() + + def _tick(self) -> None: + self.callback() + with self._timer_lock: + self._recreate_timer() diff --git a/game/sim/missionsimulation.py b/game/sim/missionsimulation.py index 0469845b..86b4e95f 100644 --- a/game/sim/missionsimulation.py +++ b/game/sim/missionsimulation.py @@ -1,29 +1,45 @@ from __future__ import annotations import json +from datetime import timedelta from pathlib import Path from typing import Optional, TYPE_CHECKING from game.debriefing import Debriefing from game.missiongenerator import MissionGenerator -from game.sim.aircraftsimulation import AircraftSimulation -from game.sim.missionresultsprocessor import MissionResultsProcessor from game.unitmap import UnitMap +from .aircraftsimulation import AircraftSimulation +from .missionresultsprocessor import MissionResultsProcessor if TYPE_CHECKING: from game import Game +TICK = timedelta(seconds=1) + + +class SimulationAlreadyCompletedError(RuntimeError): + def __init__(self) -> None: + super().__init__("Simulation already completed") + + class MissionSimulation: def __init__(self, game: Game) -> None: self.game = game self.unit_map: Optional[UnitMap] = None - self.time = game.conditions.start_time + self.aircraft_simulation = AircraftSimulation(self.game) + self.completed = False + self.time = self.game.conditions.start_time - def run(self) -> None: - sim = AircraftSimulation(self.game) - sim.run() - self.time = sim.time + def begin_simulation(self) -> None: + self.aircraft_simulation.begin_simulation() + + def tick(self) -> bool: + 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 def generate_miz(self, output: Path) -> None: self.unit_map = MissionGenerator(self.game, self.time).generate_miz(output) diff --git a/game/sim/simspeedsetting.py b/game/sim/simspeedsetting.py new file mode 100644 index 00000000..95427046 --- /dev/null +++ b/game/sim/simspeedsetting.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from enum import Enum, unique + + +@unique +class SimSpeedSetting(Enum): + PAUSED = (0, "Paused") + X1 = (1, "1x") + X2 = (2, "2x") + X5 = (5, "5x") + X10 = (10, "10x") + X100 = (100, "100x") + X1000 = (1000, "1000x") + + def __init__(self, speed_factor: int, text: str) -> None: + self.speed_factor = speed_factor + self.text = text + + def __str__(self) -> str: + return self.text + + @classmethod + def from_factor(cls, speed_factor: int) -> SimSpeedSetting: + for setting in SimSpeedSetting: + if setting.speed_factor == speed_factor: + return setting + raise ValueError diff --git a/qt_ui/__init__.py b/qt_ui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/qt_ui/simcontroller.py b/qt_ui/simcontroller.py new file mode 100644 index 00000000..6105e4c0 --- /dev/null +++ b/qt_ui/simcontroller.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Callable, Optional, TYPE_CHECKING + +from PySide2.QtCore import QObject, Signal + +from game.sim.gameloop import GameLoop +from game.sim.simspeedsetting import SimSpeedSetting +from game.polldebriefingfilethread import PollDebriefingFileThread + +if TYPE_CHECKING: + from game import Game + from game.debriefing import Debriefing + + +class SimController(QObject): + sim_speed_reset = Signal(SimSpeedSetting) + simulation_complete = Signal() + + def __init__(self, game: Optional[Game]) -> None: + super().__init__() + self.game_loop: Optional[GameLoop] = None + self.recreate_game_loop(game) + self.started = False + + @property + def completed(self) -> bool: + return self.game_loop.completed + + def set_game(self, game: Optional[Game]) -> None: + self.recreate_game_loop(game) + self.sim_speed_reset.emit(SimSpeedSetting.PAUSED) + + def recreate_game_loop(self, game: Optional[Game]) -> None: + if self.game_loop is not None: + self.game_loop.pause() + if game is not None: + self.game_loop = GameLoop(game, self.on_simulation_complete) + self.started = False + + def set_simulation_speed(self, simulation_speed: SimSpeedSetting) -> None: + if self.game_loop.completed and simulation_speed is not SimSpeedSetting.PAUSED: + logging.debug("Cannot unpause sim: already complete") + return + if not self.started and simulation_speed is not SimSpeedSetting.PAUSED: + self.game_loop.start() + self.started = True + self.game_loop.set_simulation_speed(simulation_speed) + + def run_to_first_contact(self) -> None: + self.game_loop.run_to_first_contact() + + def generate_miz(self, output: Path) -> None: + self.game_loop.pause_and_generate_miz(output) + + def wait_for_debriefing( + self, callback: Callable[[Debriefing], None] + ) -> PollDebriefingFileThread: + thread = PollDebriefingFileThread(callback, self.game_loop.sim) + thread.start() + return thread + + def debrief_current_state( + self, state_path: Path, force_end: bool = False + ) -> Debriefing: + return self.game_loop.pause_and_debrief(state_path, force_end) + + def process_results(self, debriefing: Debriefing) -> None: + return self.game_loop.complete_with_results(debriefing) + + def on_simulation_complete(self) -> None: + logging.debug("Simulation complete") + self.simulation_complete.emit() diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index a1e0c730..eda3b093 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -13,15 +13,16 @@ import qt_ui.uiconstants as CONST from game import Game, persistency from game.ato.package import Package from game.profiling import logged_duration -from game.sim import MissionSimulation from game.utils import meters from gen.flights.traveltime import TotEstimator from qt_ui.models import GameModel +from qt_ui.simcontroller import SimController from qt_ui.widgets.QBudgetBox import QBudgetBox from qt_ui.widgets.QConditionsWidget import QConditionsWidget from qt_ui.widgets.QFactionsInfos import QFactionsInfos from qt_ui.widgets.QIntelBox import QIntelBox from qt_ui.widgets.clientslots import MaxPlayerCount +from qt_ui.widgets.simspeedcontrols import SimSpeedControls from qt_ui.windows.AirWingDialog import AirWingDialog from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog @@ -29,21 +30,14 @@ from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResul class QTopPanel(QFrame): - def __init__(self, game_model: GameModel): + def __init__(self, game_model: GameModel, sim_controller: SimController) -> None: super(QTopPanel, self).__init__() self.game_model = game_model + self.sim_controller = sim_controller self.dialog: Optional[QDialog] = None self.setMaximumHeight(70) - self.init_ui() - GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) - GameUpdateSignal.get_instance().budgetupdated.connect(self.budget_update) - @property - def game(self) -> Optional[Game]: - return self.game_model.game - - def init_ui(self): self.conditionsWidget = QConditionsWidget() self.budgetBox = QBudgetBox(self.game) @@ -86,6 +80,7 @@ class QTopPanel(QFrame): self.proceedBox = QGroupBox("Proceed") self.proceedBoxLayout = QHBoxLayout() + self.proceedBoxLayout.addLayout(SimSpeedControls(sim_controller)) self.proceedBoxLayout.addLayout(MaxPlayerCount(self.game_model.ato_model)) self.proceedBoxLayout.addWidget(self.passTurnButton) self.proceedBoxLayout.addWidget(self.proceedButton) @@ -105,6 +100,13 @@ class QTopPanel(QFrame): self.setLayout(self.layout) + GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) + GameUpdateSignal.get_instance().budgetupdated.connect(self.budget_update) + + @property + def game(self) -> Optional[Game]: + return self.game_model.game + def setGame(self, game: Optional[Game]): if game is None: return @@ -277,11 +279,13 @@ class QTopPanel(QFrame): if not self.confirm_negative_start_time(negative_starts): return - sim = MissionSimulation(self.game) - sim.run() - sim.generate_miz(persistency.mission_path_for("liberation_nextturn.miz")) + if self.game.settings.fast_forward_to_first_contact: + self.sim_controller.run_to_first_contact() + self.sim_controller.generate_miz( + persistency.mission_path_for("liberation_nextturn.miz") + ) - waiting = QWaitingForMissionResultWindow(self.game, sim, self) + waiting = QWaitingForMissionResultWindow(self.game, self.sim_controller, self) waiting.exec_() def budget_update(self, game: Game): diff --git a/qt_ui/widgets/simspeedcontrols.py b/qt_ui/widgets/simspeedcontrols.py new file mode 100644 index 00000000..39cdb937 --- /dev/null +++ b/qt_ui/widgets/simspeedcontrols.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import Optional + +from PySide2.QtWidgets import QButtonGroup, QHBoxLayout, QPushButton, QWidget + +from game.sim.simspeedsetting import SimSpeedSetting +from qt_ui.simcontroller import SimController + + +class SimSpeedControls(QHBoxLayout): + def __init__( + self, sim_controller: SimController, parent: Optional[QWidget] = None + ) -> None: + super().__init__(parent) + self.sim_controller = sim_controller + self.button_group = QButtonGroup(self) + self.buttons: dict[SimSpeedSetting, QPushButton] = {} + + for speed_setting in SimSpeedSetting: + button = QPushButton(speed_setting.text) + button.setCheckable(True) # TODO: CSS + self.button_group.addButton(button, id=speed_setting.speed_factor) + self.addWidget(button) + self.buttons[speed_setting] = button + + self.button_group.idPressed.connect(self.speed_changed) + self.sim_controller.sim_speed_reset.connect(self.on_sim_speed_reset) + + def speed_changed(self, speed_factor: int) -> None: + setting = SimSpeedSetting.from_factor(speed_factor) + self.sim_controller.set_simulation_speed(setting) + + def on_sim_speed_reset(self, speed_setting: SimSpeedSetting) -> None: + self.buttons[speed_setting].setChecked(True) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index ed4f3d00..957d3f33 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -23,6 +23,7 @@ from game.debriefing import Debriefing from qt_ui import liberation_install from qt_ui.dialogs import Dialog from qt_ui.models import GameModel +from qt_ui.simcontroller import SimController from qt_ui.uiconstants import URLS from qt_ui.uncaughtexceptionhandler import UncaughtExceptionHandler from qt_ui.widgets.QTopPanel import QTopPanel @@ -31,14 +32,14 @@ from qt_ui.widgets.map.QLiberationMap import QLiberationMap from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.QDebriefingWindow import QDebriefingWindow from qt_ui.windows.infos.QInfoPanel import QInfoPanel +from qt_ui.windows.logs.QLogsWindow import QLogsWindow from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard +from qt_ui.windows.notes.QNotesWindow import QNotesWindow from qt_ui.windows.preferences.QLiberationPreferencesWindow import ( QLiberationPreferencesWindow, ) from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow from qt_ui.windows.stats.QStatsWindow import QStatsWindow -from qt_ui.windows.notes.QNotesWindow import QNotesWindow -from qt_ui.windows.logs.QLogsWindow import QLogsWindow class QLiberationWindow(QMainWindow): @@ -50,6 +51,7 @@ class QLiberationWindow(QMainWindow): self.game = game self.game_model = GameModel(game) Dialog.set_game(self.game_model) + self.sim_controller = SimController(self.game) self.ato_panel = QAirTaskingOrderPanel(self.game_model) self.info_panel = QInfoPanel(self.game) self.liberation_map = QLiberationMap(self.game_model, self) @@ -99,7 +101,7 @@ class QLiberationWindow(QMainWindow): vbox = QVBoxLayout() vbox.setMargin(0) - vbox.addWidget(QTopPanel(self.game_model)) + vbox.addWidget(QTopPanel(self.game_model, self.sim_controller)) vbox.addWidget(hbox) central_widget = QWidget() @@ -314,6 +316,7 @@ class QLiberationWindow(QMainWindow): self.game = game if self.info_panel is not None: self.info_panel.setGame(game) + self.sim_controller.set_game(game) self.game_model.set(self.game) self.liberation_map.set_game(game) except AttributeError: diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index c0019c40..5c07b4cb 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -22,10 +22,10 @@ from PySide2.QtWidgets import ( from jinja2 import Environment, FileSystemLoader, select_autoescape from game import Game -from game.debriefing import Debriefing, wait_for_debriefing +from game.debriefing import Debriefing from game.persistency import base_path from game.profiling import logged_duration -from game.sim import MissionSimulation +from qt_ui.simcontroller import SimController from qt_ui.windows.GameUpdateSignal import GameUpdateSignal @@ -53,13 +53,13 @@ class QWaitingForMissionResultWindow(QDialog): def __init__( self, game: Game, - mission_simulation: MissionSimulation, + sim_controller: SimController, parent: Optional[QWidget] = None, ) -> None: super(QWaitingForMissionResultWindow, self).__init__(parent=parent) self.setWindowModality(QtCore.Qt.WindowModal) self.game = game - self.mission_sim = mission_simulation + self.sim_controller = sim_controller self.setWindowTitle("Waiting for mission completion.") self.setWindowIcon(QIcon("./resources/icon.png")) self.setMinimumHeight(570) @@ -68,8 +68,8 @@ class QWaitingForMissionResultWindow(QDialog): DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect( self.updateLayout ) - self.wait_thread = wait_for_debriefing( - lambda debriefing: self.on_debriefing_update(debriefing), self.mission_sim + self.wait_thread = sim_controller.wait_for_debriefing( + lambda debriefing: self.on_debriefing_update(debriefing) ) def initUi(self): @@ -204,13 +204,13 @@ class QWaitingForMissionResultWindow(QDialog): DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing) except Exception: logging.exception("Got an error while sending debriefing") - self.wait_thread = wait_for_debriefing( - lambda d: self.on_debriefing_update(d), self.mission_sim + self.wait_thread = self.sim_controller.wait_for_debriefing( + lambda d: self.on_debriefing_update(d) ) def process_debriefing(self): with logged_duration("Turn processing"): - self.mission_sim.process_results(self.debriefing) + self.sim_controller.process_results(self.debriefing) self.game.pass_turn() GameUpdateSignal.get_instance().sendDebriefing(self.debriefing) @@ -231,5 +231,5 @@ class QWaitingForMissionResultWindow(QDialog): ) logging.debug("Processing manually submitted %s", file[0]) self.on_debriefing_update( - self.mission_sim.debrief_current_state(Path(file[0], force_end=True)) + self.sim_controller.debrief_current_state(Path(file[0], force_end=True)) )