diff --git a/src/simudator/gui/gui.py b/src/simudator/gui/gui.py index 68bc762047c28a7c940f6f671ade20401f92db41..448c73d3298e2f453749b48e2ae087c8273110c7 100644 --- a/src/simudator/gui/gui.py +++ b/src/simudator/gui/gui.py @@ -52,9 +52,7 @@ class GUI(QMainWindow): super().__init__() self.cpu = cpu - self.processor_handler = ProcessorHandler( - cpu, QtCore.QThreadPool(self), parent=self - ) + self.processor_handler = ProcessorHandler(cpu, parent=self) self.setWindowTitle("SimuDator") self.graphics_scene = CpuGraphicsScene(cpu) @@ -110,9 +108,7 @@ class GUI(QMainWindow): # Connect signal for managing breakpoints self.menu_bar.show_breakpoints.connect(self.openBreakpointWindow) - self.processor_handler.processor_running.connect( - self.menu_bar.set_disabled_when_running - ) + self.processor_handler.running.connect(self.menu_bar.set_disabled_when_running) def _create_tool_bar(self) -> None: """ @@ -127,10 +123,8 @@ class GUI(QMainWindow): self.toolbar.undo.connect(self.processor_handler.undo_cycles) self.toolbar.step_asm.connect(self.processor_handler.step_asm_instructions) self.toolbar.undo_asm.connect(self.processor_handler.undo_asm_instructions) - self.processor_handler.processor_tick.connect(self.toolbar.update_clock) - self.processor_handler.processor_running.connect( - self.toolbar.set_disabled_when_running - ) + self.processor_handler.cycle_changed.connect(self.toolbar.update_clock) + self.processor_handler.running.connect(self.toolbar.set_disabled_when_running) def connectModuleActions(self, action_signals: []) -> None: """ @@ -169,7 +163,7 @@ class GUI(QMainWindow): self.pipeline.set_height(size[0]) self.pipeline.set_width(size[1]) - self.processor_handler.processor_instruction.connect( + self.processor_handler.changed_instruction.connect( self.pipeline.set_instructions ) self.processor_handler._signal_processor_changed() @@ -301,7 +295,7 @@ class GUI(QMainWindow): """ self.graphics_scene.addModuleGraphicsItem(graphics_item) self.connectModuleActions(graphics_item.getActionSignals()) - self.processor_handler.processor_changed.connect(graphics_item.update) + self.processor_handler.changed.connect(graphics_item.update) def addAllSignals(self) -> None: """ diff --git a/src/simudator/gui/processor_handler.py b/src/simudator/gui/processor_handler.py index a62e235902582d571190ec3c553995caea71f456..90c46dbcc57641d8fc754c1b13f9b3d1fe0d7b1f 100644 --- a/src/simudator/gui/processor_handler.py +++ b/src/simudator/gui/processor_handler.py @@ -9,38 +9,68 @@ from simudator.gui.run_continuously_thread import RunThread class ProcessorHandler(QObject): - processor_running = pyqtSignal(bool) - processor_tick = pyqtSignal() - processor_changed = pyqtSignal() - processor_instruction = pyqtSignal(list) + """ + Wrapper class for interacting with the processor in the GUI. + + Parameters + ---------- + processor : Processor + parent : QObject | None + Optional parent QObject that takes ownership of this QObject. + """ + + running = pyqtSignal(bool) + """ + PyQT signal emitted when the processor has started or finished running. + Emits ``True`` when it has started and ``False`` when it has finished. + """ + cycle_changed = pyqtSignal(int) + """ + PyQT signal emitted when the current clock cycle count of the processor + has changed. Emits the current clock cycle count as an ``int``. + """ + changed = pyqtSignal() + """ + PyQT signal emitted when the processor has changed state. + """ + changed_instruction = pyqtSignal(list) + """ + PyQT signal emitted when the processor has changed the current instruction(s) + being executed. Emits a list of instructions. + + See Also + -------- + simudator.core.processor.get_current_instructions : + Method of the processor used to get instructions that are emitted. + """ def __init__( self, processor: Processor, - threadpool: QThreadPool, parent: QObject | None = None, ) -> None: super().__init__(parent) self._processor = processor # Threadpool for running cpu and gui concurrently # self.threadpool = QtCore.QThreadPool() - self.threadpool = threadpool + self._threadpool = QThreadPool.globalInstance() # Used to set if all values in the gui should be updated each tick # or only the clock counter - self.update_all_values = False + self._update_all_values = False # Used to set the update delay # Useful when watching the values being updated while running - self.update_delay = 0.00 + self._update_delay = 0.00 # Used to lock some actions in ui when cpu is running in another thread # Using the cpu's internal status directly could case problems - self.cpu_running = False + self._processor_running = False def _signal_processor_changed(self) -> None: - self.processor_changed.emit() - self.processor_instruction.emit(self._processor.get_current_instructions()) + self.cycle_changed.emit(self._processor.get_clock()) + self.changed.emit() + self.changed_instruction.emit(self._processor.get_current_instructions()) def messageBox(self, message, title="Message") -> None: """ @@ -58,20 +88,20 @@ class ProcessorHandler(QObject): """ # Don't do steps if cpu is running - if self.cpu_running: + if self._processor_running: return - self.cpu_running = True - self.processor_running.emit(True) + self._processor_running = True + self.running.emit(True) simulation_thread = RunThread( self._processor, - self.update_delay, + self._update_delay, False, False, steps, ) simulation_thread.processor_tick.connect(self.on_processor_tick) - self.threadpool.start(simulation_thread) + self._threadpool.start(simulation_thread) @Slot(int) def step_asm_instructions(self, steps: int): @@ -80,16 +110,16 @@ class ProcessorHandler(QObject): """ # Don't do steps if cpu is running - if self.cpu_running: + if self._processor_running: return - self.cpu_running = True - self.processor_running.emit(True) + self._processor_running = True + self.running.emit(True) simulation_thread = RunThread( - self._processor, self.update_delay, False, True, steps + self._processor, self._update_delay, False, True, steps ) simulation_thread.processor_tick.connect(self.on_processor_tick) - self.threadpool.start(simulation_thread) + self._threadpool.start(simulation_thread) @Slot() def run_simulation(self) -> None: @@ -98,15 +128,15 @@ class ProcessorHandler(QObject): """ # Don't run if already running - if self.cpu_running: + if self._processor_running: return # Create own thread for cpu simulation so gui dosent freeze - self.cpu_running = True - self.processor_running.emit(True) - simulation_thread = RunThread(self._processor, self.update_delay) + self._processor_running = True + self.running.emit(True) + simulation_thread = RunThread(self._processor, self._update_delay) simulation_thread.processor_tick.connect(self.on_processor_tick) - self.threadpool.start(simulation_thread) + self._threadpool.start(simulation_thread) @Slot(int) def on_processor_tick(self, steps: int) -> None: @@ -116,18 +146,18 @@ class ProcessorHandler(QObject): """ # Update cpu clock counter every tick - self.processor_tick.emit() + self.cycle_changed.emit(self._processor.get_clock()) - if self.update_all_values: - self.processor_changed.emit() + if self._update_all_values: + self._signal_processor_changed() # A signal of 0 steps signifies end of execution, i.e. the CPU has # halted or run the specified amount of ticks # => Enable the relevant parts of the GUI again if steps == 0: - self.cpu_running = False - self.processor_running.emit(False) - self.processor_changed.emit() + self._processor_running = False + self.running.emit(False) + self._signal_processor_changed() # Inform user of reached break point if self._processor.breakpoint_reached: @@ -182,7 +212,7 @@ class ProcessorHandler(QObject): """ # not safe to load while cpu is running - if self.cpu_running: + if self._processor_running: return path = self.folderLoadDialog() @@ -210,7 +240,7 @@ class ProcessorHandler(QObject): """ # not safe to reset while cpu running - if self.cpu_running: + if self._processor_running: return answer = QMessageBox.question( @@ -230,7 +260,7 @@ class ProcessorHandler(QObject): """ # Not safe to save while running - if self.cpu_running: + if self._processor_running: return path = self.folderSaveDialog() @@ -273,7 +303,7 @@ class ProcessorHandler(QObject): """ Toggles whether all values or only clock cycle is being updated each tick. """ - self.update_all_values = not self.update_all_values + self._update_all_values = not self._update_all_values @Slot() def set_update_delay(self): @@ -284,4 +314,4 @@ class ProcessorHandler(QObject): None, "Input Dialog", "Enter a float value:", decimals=5 ) if ok: - self.update_delay = delay + self._update_delay = delay diff --git a/src/simudator/gui/run_continuously_thread.py b/src/simudator/gui/run_continuously_thread.py index 5e2a334ad2c8b4642156e3a59a5a182992d8fcb2..13368de4f10407cce26c1fb8cf2bb1bc46c10186 100644 --- a/src/simudator/gui/run_continuously_thread.py +++ b/src/simudator/gui/run_continuously_thread.py @@ -4,19 +4,27 @@ from qtpy.QtCore import QObject, QRunnable from qtpy.QtCore import Signal as pyqtSignal -class RunThread(QObject, QRunnable): +class SignalHolder(QObject): """ - This class is used to run the simulated cpu several ticks or continuously on - a seperate thread. This allows the user to interact with the GUI while the - simulation is running. - - After each CPU tick, this thread will emit to its given QT signal so that - the GUI can update itself and possibly inform the user of when the execution - has halted. + Helper class for RunThread as the thread cannot inherit from QObject to + have signals directly. Instead, an instance of this class is used to hold + signals for the thread. """ processor_tick = pyqtSignal(int) + +class RunThread(QRunnable): + """ + This class is used to run the simulated cpu several ticks or continuously + on a separate thread. This allows the user to interact with the GUI while + the simulation is running. + + After each CPU tick, this thread will emit to its QT signal so that the GUI + can update itself and possibly inform the user of when the execution + has halted. + """ + def __init__( self, cpu, delay: float, run_continuously=True, step_asm=False, steps=0 ): @@ -27,10 +35,10 @@ class RunThread(QObject, QRunnable): self.steps = steps self.delay = delay self.step_asm = step_asm - print("Running thread created") + self.signals = SignalHolder() + self.processor_tick = self.signals.processor_tick def run(self): - print("Running thread started") if self.step_asm: while self.steps > 0: @@ -56,7 +64,6 @@ class RunThread(QObject, QRunnable): else: for _ in range(self.steps): - print("Ticking cycle") self.cpu.do_tick() self.processor_tick.emit(1) time.sleep(self.delay) diff --git a/src/simudator/gui/simulation_toolbar.py b/src/simudator/gui/simulation_toolbar.py index 752eaf93c674b576c9f5a7d9d22fbbc72c3bc2cb..898ff6fb61efbc37d1a9c2445ffb4d73034aa1a2 100644 --- a/src/simudator/gui/simulation_toolbar.py +++ b/src/simudator/gui/simulation_toolbar.py @@ -146,6 +146,7 @@ class SimulationToolBar(QToolBar): # is currently no way of showing it again pass + @Slot(int) def update_clock(self, clock_cycle: int) -> None: """ Set the displayed number of the clock cycle counter.