From d16371d4c8a03ac680152d3c7043461a3218f092 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Fri, 7 Apr 2023 17:14:32 +0200
Subject: [PATCH] Do simulation in separate thread

---
 b_asic/GUI/main_window.py       | 42 ++++++++++++++++-----------------
 b_asic/GUI/simulation_worker.py | 21 +++++++++++++++++
 b_asic/gui_utils/plot_window.py |  7 +++---
 test/test_gui.py                |  4 ++--
 4 files changed, 47 insertions(+), 27 deletions(-)
 create mode 100644 b_asic/GUI/simulation_worker.py

diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py
index 698dc612..f4a39e06 100644
--- a/b_asic/GUI/main_window.py
+++ b/b_asic/GUI/main_window.py
@@ -12,7 +12,7 @@ from collections import deque
 from pprint import pprint
 from typing import Dict, List, Optional, Tuple, cast
 
-from qtpy.QtCore import QCoreApplication, QFileInfo, QSettings, QSize, Qt
+from qtpy.QtCore import QCoreApplication, QFileInfo, QSettings, QSize, Qt, QThread
 from qtpy.QtGui import QCursor, QIcon, QKeySequence, QPainter
 from qtpy.QtWidgets import (
     QAction,
@@ -39,9 +39,8 @@ from b_asic.GUI.gui_interface import Ui_main_window
 from b_asic.GUI.port_button import PortButton
 from b_asic.GUI.select_sfg_window import SelectSFGWindow
 from b_asic.GUI.show_pc_window import ShowPCWindow
-
-# from b_asic.GUI.simulate_sfg_window import Plot, SimulateSFGWindow
 from b_asic.GUI.simulate_sfg_window import SimulateSFGWindow
+from b_asic.GUI.simulation_worker import SimulationWorker
 from b_asic.GUI.util_dialogs import FaqWindow, KeybindingsWindow
 from b_asic.gui_utils.about_window import AboutWindow
 from b_asic.gui_utils.decorators import decorate_class, handle_error
@@ -51,9 +50,7 @@ from b_asic.port import InputPort, OutputPort
 from b_asic.save_load_structure import python_to_sfg, sfg_to_python
 from b_asic.signal import Signal
 from b_asic.signal_flow_graph import SFG
-
-# from b_asic import FastSimulation
-from b_asic.simulation import Simulation as FastSimulation
+from b_asic.simulation import Simulation
 from b_asic.special_operations import Input, Output
 
 logging.basicConfig(level=logging.INFO)
@@ -89,6 +86,7 @@ class MainWindow(QMainWindow):
         self.sfg_dict = {}
         self._window = self
         self.logger = logging.getLogger(__name__)
+        self._plot: Dict[Simulation, PlotWindow] = dict()
 
         # Create Graphics View
         self.graphic_view = QGraphicsView(self.scene, self)
@@ -237,7 +235,6 @@ class MainWindow(QMainWindow):
         module, accepted = QFileDialog().getOpenFileName()
         if not accepted:
             return
-        self.addRecentFile(module)
         self._load_from_file(module)
 
     def _load_from_file(self, module) -> None:
@@ -250,6 +247,8 @@ class MainWindow(QMainWindow):
             )
             return
 
+        self.addRecentFile(module)
+
         while sfg.name in self.sfg_dict:
             self.logger.warning(
                 f"Duplicate SFG with name: {sfg.name} detected. "
@@ -741,22 +740,23 @@ class MainWindow(QMainWindow):
         self.pressed_operations = selected
 
     def _simulate_sfg(self) -> None:
+        self._thread = dict()
+        self._sim_worker = dict()
         for sfg, properties in self._simulation_dialog.properties.items():
             self.logger.info("Simulating SFG with name: %s" % str(sfg.name))
-            simulation = FastSimulation(sfg, input_providers=properties["input_values"])
-            l_result = simulation.run_for(
-                properties["iteration_count"],
-                save_results=properties["all_results"],
-            )
-
-            print(f"{'=' * 10} {sfg.name} {'=' * 10}")
-            pprint(simulation.results if properties["all_results"] else l_result)
-            print(f"{'=' * 10} /{sfg.name} {'=' * 10}")
-
-            if properties["show_plot"]:
-                self.logger.info("Opening plot for SFG with name: " + str(sfg.name))
-                self._plot = PlotWindow(simulation.results, sfg_name=sfg.name)
-                self._plot.show()
+            self._sim_worker[sfg] = SimulationWorker(sfg, properties)
+            self._thread[sfg] = QThread()
+            self._sim_worker[sfg].moveToThread(self._thread[sfg])
+            self._thread[sfg].started.connect(self._sim_worker[sfg].start_simulation)
+            self._sim_worker[sfg].finished.connect(self._thread[sfg].quit)
+            self._sim_worker[sfg].finished.connect(self._show_plot_window)
+            self._sim_worker[sfg].finished.connect(self._sim_worker[sfg].deleteLater)
+            self._thread[sfg].finished.connect(self._thread[sfg].deleteLater)
+            self._thread[sfg].start()
+
+    def _show_plot_window(self, sim: Simulation):
+        self._plot[sim] = PlotWindow(sim.results, sfg_name=sim._sfg.name)
+        self._plot[sim].show()
 
     def simulate_sfg(self, event=None) -> None:
         self._simulation_dialog = SimulateSFGWindow(self)
diff --git a/b_asic/GUI/simulation_worker.py b/b_asic/GUI/simulation_worker.py
new file mode 100644
index 00000000..6411a791
--- /dev/null
+++ b/b_asic/GUI/simulation_worker.py
@@ -0,0 +1,21 @@
+from qtpy.QtCore import QObject, Signal
+
+from b_asic.signal_flow_graph import SFG
+from b_asic.simulation import Simulation
+
+
+class SimulationWorker(QObject):
+    finished = Signal(Simulation)
+
+    def __init__(self, sfg: SFG, properties):
+        super().__init__()
+        self._sfg = sfg
+        self._props = properties
+
+    def start_simulation(self):
+        simulation = Simulation(self._sfg, input_providers=self._props["input_values"])
+        simulation.run_for(
+            self._props["iteration_count"],
+            save_results=self._props["all_results"],
+        )
+        self.finished.emit(simulation)
diff --git a/b_asic/gui_utils/plot_window.py b/b_asic/gui_utils/plot_window.py
index 7aec9c35..e2d5dcf1 100644
--- a/b_asic/gui_utils/plot_window.py
+++ b/b_asic/gui_utils/plot_window.py
@@ -12,20 +12,20 @@ from qtpy.QtCore import Qt
 from qtpy.QtWidgets import (  # QFrame,; QScrollArea,; QLineEdit,; QSizePolicy,; QLabel,; QFileDialog,; QShortcut,
     QApplication,
     QCheckBox,
-    QDialog,
     QHBoxLayout,
     QListWidget,
     QListWidgetItem,
     QPushButton,
     QSizePolicy,
     QVBoxLayout,
+    QWidget,
 )
 
 from b_asic.operation import ResultKey
 from b_asic.types import Num
 
 
-class PlotWindow(QDialog):
+class PlotWindow(QWidget):
     """
     Dialog for plotting the result of a simulation.
 
@@ -43,9 +43,8 @@ class PlotWindow(QDialog):
         self,
         sim_result: Mapping[ResultKey, Sequence[Num]],
         sfg_name: Optional[str] = None,
-        parent=None,
     ):
-        super().__init__(parent=parent)
+        super().__init__()
         self.setWindowFlags(
             Qt.WindowTitleHint
             | Qt.WindowCloseButtonHint
diff --git a/test/test_gui.py b/test/test_gui.py
index 1feca3c6..0e1af8bb 100644
--- a/test/test_gui.py
+++ b/test/test_gui.py
@@ -157,8 +157,8 @@ def test_simulate(qtbot, datadir):
     assert 'twotapfir' in widget.sfg_dict
     widget.simulate_sfg()
     qtbot.wait(100)
-    # widget.dialog.save_properties()
-    # qtbot.wait(100)
+    widget._simulation_dialog.save_properties()
+    qtbot.wait(100)
     widget._simulation_dialog.close()
 
     widget.exit_app()
-- 
GitLab