Spline UI run graph calculations on thread

Much smoother when the gtk event loop isn't as clogged

Signed-off-by: Ravago Jones <ravagojones@gmail.com>
Change-Id: Ied4b0a8bd80957a1e3ae09ccf977a5f4ac3373eb
diff --git a/frc971/control_loops/python/graph.py b/frc971/control_loops/python/graph.py
index 456edd1..c974d68 100644
--- a/frc971/control_loops/python/graph.py
+++ b/frc971/control_loops/python/graph.py
@@ -2,6 +2,9 @@
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 import numpy as np
+import queue
+import threading
+import copy
 from points import Points
 from libspline import Spline, DistanceSpline, Trajectory
 
@@ -9,7 +12,6 @@
                                                  FigureCanvas)
 from matplotlib.figure import Figure
 
-
 class Graph(Gtk.Bin):
     def __init__(self):
         super(Graph, self).__init__()
@@ -19,10 +21,31 @@
         canvas.set_vexpand(True)
         canvas.set_size_request(800, 250)
         self.add(canvas)
+        self.queue = queue.Queue(maxsize=1)
+
+        thread = threading.Thread(target=self.worker)
+        thread.daemon = True
+        thread.start()
+
+    def schedule_recalculate(self, points):
+        if not points.getLibsplines() or self.queue.full(): return
+        new_copy = copy.deepcopy(points)
+
+        # empty the queue
+        try:
+            self.queue.get_nowait()
+        except queue.Empty:
+            pass # was already empty
+
+        # replace with new request
+        self.queue.put_nowait(new_copy)
+
+    def worker(self):
+        while True:
+            self.recalculate_graph(self.queue.get())
 
     def recalculate_graph(self, points):
         if not points.getLibsplines(): return
-
         # set the size of a timestep
         dt = 0.00505
 
@@ -32,11 +55,12 @@
         points.addConstraintsToTrajectory(traj)
         traj.Plan()
         XVA = traj.GetPlanXVA(dt)
+        if XVA is None: return
 
         # extract values to be graphed
         total_steps_taken = XVA.shape[1]
         total_time = dt * total_steps_taken
-        time = np.arange(total_time, step=dt)
+        time = np.linspace(0, total_time, num=total_steps_taken)
         position, velocity, acceleration = XVA
         left_voltage, right_voltage = zip(*(traj.Voltage(x) for x in position))
 
@@ -54,4 +78,5 @@
         # the total time to drive the spline
         self.axis.xaxis.set_ticks(np.linspace(0, total_time, num=8))
 
+        # ask to be redrawn
         self.queue_draw()
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index c639379..50400b9 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -259,7 +259,7 @@
                                                pxToM(self.mousey), difs)
 
             self.points.update_lib_spline()
-            self.graph.recalculate_graph(self.points)
+            self.graph.schedule_recalculate(self.points)
         self.queue_draw()
 
     def export_json(self, file_name):
@@ -374,7 +374,7 @@
                 self.points.splineExtrapolate(self.spline_edit)
 
                 self.points.update_lib_spline()
-                self.graph.recalculate_graph(self.points)
+                self.graph.schedule_recalculate(self.points)
 
                 self.index_of_edit = -1
                 self.spline_edit = -1
diff --git a/frc971/control_loops/python/points.py b/frc971/control_loops/python/points.py
index d874306..5fc4c4a 100644
--- a/frc971/control_loops/python/points.py
+++ b/frc971/control_loops/python/points.py
@@ -1,7 +1,7 @@
 from constants import *
 import numpy as np
 from libspline import Spline, DistanceSpline, Trajectory
-
+import copy
 
 class Points():
     def __init__(self):
@@ -21,6 +21,14 @@
             }
         ]
 
+    def __deepcopy__(self, memo):
+        new_copy = Points()
+        new_copy.points = copy.deepcopy(self.points, memo)
+        new_copy.splines = copy.deepcopy(self.splines, memo)
+        new_copy.constraints = copy.deepcopy(self.constraints, memo)
+        new_copy.update_lib_spline()
+        return new_copy
+
     def getPoints(self):
         return self.points