瀏覽代碼

WIP on master: b111a48 Revert "Depend on mtTkinter (included)" as this makes the UI unusably slow

Yentl Van Tendeloo 7 年之前
父節點
當前提交
571cea4b86

+ 14 - 3
src/python_sccd/python_sccd_runtime/statecharts_core.py

@@ -563,6 +563,9 @@ class EventLoop:
             self.clear_callback(self.scheduled_id)
             self.clear_callback(self.scheduled_id)
             self.scheduled_id = None
             self.scheduled_id = None
 
 
+    def bind_controller(self, controller):
+        pass
+
 class EventLoopControllerBase(ControllerBase):
 class EventLoopControllerBase(ControllerBase):
     def __init__(self, object_manager, event_loop, finished_callback = None, behind_schedule_callback = None):
     def __init__(self, object_manager, event_loop, finished_callback = None, behind_schedule_callback = None):
         ControllerBase.__init__(self, object_manager)
         ControllerBase.__init__(self, object_manager)
@@ -576,13 +579,19 @@ class EventLoopControllerBase(ControllerBase):
         self.input_condition = threading.Condition()
         self.input_condition = threading.Condition()
         self.behind = False
         self.behind = False
 
 
+        self.event_loop.bind_controller(self)
+
     def addInput(self, input_event, time_offset = 0, force_internal=False):
     def addInput(self, input_event, time_offset = 0, force_internal=False):
         with self.input_condition:
         with self.input_condition:
+            print("1")
             ControllerBase.addInput(self, input_event, time_offset, force_internal)
             ControllerBase.addInput(self, input_event, time_offset, force_internal)
+            print("2")
             self.event_loop.clear()
             self.event_loop.clear()
+            print("3")
             self.simulated_time = self.getEarliestEventTime()
             self.simulated_time = self.getEarliestEventTime()
-        if not self.running:
-            self.run()
+            print("4")
+            self.event_loop.schedule(self.run, 0, True)
+            print("5")
 
 
     def start(self):
     def start(self):
         ControllerBase.start(self)
         ControllerBase.start(self)
@@ -592,7 +601,8 @@ class EventLoopControllerBase(ControllerBase):
         self.event_loop.clear()
         self.event_loop.clear()
         ControllerBase.stop(self)
         ControllerBase.stop(self)
 
 
-    def run(self):
+    def run(self, tkinter_event=None):
+        print("RUN")
         start_time = self.accurate_time.get_wct()
         start_time = self.accurate_time.get_wct()
         try:
         try:
             self.running = True
             self.running = True
@@ -625,6 +635,7 @@ class EventLoopControllerBase(ControllerBase):
                     return
                     return
         finally:
         finally:
             self.running = False
             self.running = False
+            print("FINISHED")
         
         
 class ThreadsControllerBase(ControllerBase):
 class ThreadsControllerBase(ControllerBase):
     def __init__(self, object_manager, keep_running, behind_schedule_callback = None):
     def __init__(self, object_manager, keep_running, behind_schedule_callback = None):

+ 41 - 4
src/python_sccd/python_sccd_runtime/tkinter_eventloop.py

@@ -1,16 +1,53 @@
+"""
+Yentl added many patches to make this code TkInter compatible.
+TkInter is NOT thread-safe, and this applies to the after operations as well.
+Therefore, calling tk.after (or after_cancel) should NOT be done from any thread apart from the thread running TkInter itself.
+See https://mail.python.org/pipermail/tkinter-discuss/2013-November/003522.html for a discussion...
+What actually happens in this code, is that we check whether we are on the main thread or not.
+If we are on the main thread, we invoke the code as usual.
+If we are not on the main thread, we force the *run* operation to execute, even though it might not have been scheduled.
+This operation, however, will run on the main thread, and from there we can then call this schedule function again.
+"""
+
+# If we are not on the main thread, we force the *run* operation
+
 from sccd.runtime.statecharts_core import EventLoop
 from sccd.runtime.statecharts_core import EventLoop
 
 
 import math
 import math
+import thread
 
 
 class TkEventLoop(EventLoop):
 class TkEventLoop(EventLoop):
     def __init__(self, tk):
     def __init__(self, tk):
         self.ctr = 0
         self.ctr = 0
+        self.tk = tk
+        self.main_thread = thread.get_ident()
 
 
         # bind scheduler callback
         # bind scheduler callback
         def schedule(callback, timeout, behind = False):
         def schedule(callback, timeout, behind = False):
-            if behind:
-                tk.update_idletasks()
-            return tk.after(timeout, callback)
+            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:
+                    tk.update_idletasks()
+                return tk.after(timeout, callback)
+
+        def cancel(evt):
+            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, tk.after_cancel)
+        EventLoop.__init__(self, schedule, cancel)
 
 
+    def bind_controller(self, controller):
+        print("BIND")
+        self.tk.bind("<<TriggerSCCDEvent>>", controller.run)