Browse Source

Fix (?) TkInter thread-safety issues

Yentl Van Tendeloo 7 years ago
parent
commit
b6c8f7ec6c

+ 28 - 18
src/python_sccd/python_sccd_runtime/statecharts_core.py

@@ -5,6 +5,7 @@ The classes and functions needed to run (compiled) SCCD models.
 import abc
 import re
 import threading
+import thread
 import traceback
 import math
 from heapq import heappush, heappop, heapify
@@ -580,18 +581,20 @@ class EventLoopControllerBase(ControllerBase):
         self.behind = False
 
         self.event_loop.bind_controller(self)
+        self.event_queue = []
+        self.main_thread = thread.get_ident()
 
     def addInput(self, input_event, time_offset = 0, force_internal=False):
-        with self.input_condition:
-            print("1")
+        if self.main_thread == thread.get_ident():
+            # Running on the main thread, so just execute what we want
+            self.simulated_time = self.accurate_time.get_wct()
             ControllerBase.addInput(self, input_event, time_offset, force_internal)
-            print("2")
-            self.event_loop.clear()
-            print("3")
-            self.simulated_time = self.getEarliestEventTime()
-            print("4")
-            self.event_loop.schedule(self.run, 0, True)
-            print("5")
+        else:
+            # Not on the main thread, so we have to queue these events for the main thread instead
+            self.event_queue.append((input_event, time_offset, force_internal))
+
+        self.event_loop.clear()
+        self.event_loop.schedule(self.run, 0, True)
 
     def start(self):
         ControllerBase.start(self)
@@ -602,15 +605,22 @@ class EventLoopControllerBase(ControllerBase):
         ControllerBase.stop(self)
 
     def run(self, tkinter_event=None):
-        print("RUN")
         start_time = self.accurate_time.get_wct()
         try:
             self.running = True
+            # Process external events first
             while 1:
-                with self.input_condition:
-                    # clear existing timeout
-                    self.event_loop.clear()
-                    self.handleInput()
+                while self.event_queue:
+                    self.addInput(*self.event_queue.pop(0))
+
+                if self.accurate_time.get_wct() >= self.getEarliestEventTime():
+                    self.simulated_time = self.getEarliestEventTime()
+                else:
+                    return
+
+                # clear existing timeout
+                self.event_loop.clear()
+                self.handleInput()
                 self.object_manager.stepAll()
                 # schedule next timeout
                 earliest_event_time = self.getEarliestEventTime()
@@ -621,8 +631,7 @@ class EventLoopControllerBase(ControllerBase):
                 if earliest_event_time - now > 0:
                     if self.behind:
                         self.behind = False
-                    with self.input_condition:
-                        self.event_loop.schedule(self.run, earliest_event_time - now, now - start_time > 10)
+                    self.event_loop.schedule(self.run, earliest_event_time - now, now - start_time > 10)
                 else:
                     if now - earliest_event_time > 10 and now - self.last_print_time >= 1000:
                         if self.behind_schedule_callback:
@@ -630,12 +639,13 @@ class EventLoopControllerBase(ControllerBase):
                         print_debug('\rrunning %ims behind schedule' % (now - earliest_event_time))
                         self.last_print_time = now
                     self.behind = True
-                self.simulated_time = earliest_event_time
                 if not self.behind:
                     return
         finally:
             self.running = False
-            print("FINISHED")
+            if self.event_queue:
+                self.event_loop.clear()
+                self.event_loop.schedule(self.run, 0, True)
         
 class ThreadsControllerBase(ControllerBase):
     def __init__(self, object_manager, keep_running, behind_schedule_callback = None):

+ 0 - 5
src/python_sccd/python_sccd_runtime/tkinter_eventloop.py

@@ -27,9 +27,7 @@ class TkEventLoop(EventLoop):
             if self.main_thread != thread.get_ident():
                 # Use events, as Tk operations are far from thread safe...
                 # Should there be a timeout, event_generate will automatically schedule this for real from inside the main loop
-                print("GEN1")
                 tk.event_generate("<<TriggerSCCDEvent>>", when="tail")
-                print("OK1")
             else:
                 # As usual, use Tk after events
                 if behind:
@@ -40,14 +38,11 @@ class TkEventLoop(EventLoop):
             if self.main_thread != thread.get_ident():
                 # This will also remove the pending events, while also triggering a run first
                 # That initial run, however, will not execute anything
-                print("GEN2")
                 tk.event_generate("<<TriggerSCCDEvent>>", when="tail")
-                print("OK2")
             else:
                 tk.after_cancel(evt)
 
         EventLoop.__init__(self, schedule, cancel)
 
     def bind_controller(self, controller):
-        print("BIND")
         self.tk.bind("<<TriggerSCCDEvent>>", controller.run)