Repository: ManiacalLabs/BiblioPixel
Pull request #702 (2018-03-26 22:19:19)

Give the layout the same event_queue as the animation
Not even a one-liner.  :-D

`layout` and `animation` have to have the same event queue because they both use the same `color_list`.  

The layout does communicate to the drivers and that's still a possible race condition, as evidenced by `setBrightness`.  We will at some point want an EventQueue for the drivers too and no more `setBrightness`.




Patch:
diff --git a/bibliopixel/layout/layout.py b/bibliopixel/layout/layout.py
index da7d1a6..a1bf4e7 100644
--- a/bibliopixel/layout/layout.py
+++ b/bibliopixel/layout/layout.py
@@ -99,8 +99,32 @@ class Layout(object):
         return self._colors
 
     @color_list.setter
-    def color_list(self, cl):
-        self._colors[:] = cl
+    def color_list(self, color_list):
+        self.set_color_list(color_list)
+
+    def set_color_list(self, color_list, offset=0):
+        """
+        Set the internal colors starting at an optional offset.
+
+        If `color_list` is a list or other 1-dimensional array, it is reshaped
+        into an N x 3 list.
+
+        If `color_list` too long it is truncated; if it is too short then only
+        the initial colors are set.
+        """
+        if not len(color_list):
+            return
+        try:
+            color_list[0][0]
+        except TypeError:
+            # It's a 1-dimensional list
+            assert not (len(color_list) % 3)
+            color_list = list(zip(*[iter(color_list)] * 3))
+
+        size = len(self._colors) - offset
+        if len(color_list) > size:
+            color_list = color_list[:size]
+        self._colors[offset:offset + len(color_list)] = color_list
 
     def _get_base(self, pixel):
         if pixel >= 0 and pixel < self.numLEDs:
@@ -126,24 +150,6 @@ class Layout(object):
         # This is overridden elsewhere.
         self.threading.push_to_driver()
 
-    # use with caution!
-    def set_colors(self, buf):
-        """Use with extreme caution!
-        Directly sets the internal buffer and bypasses all brightness and rotation control.
-        buf must also be in the exact format required by the display type.
-        """
-        if len(self._colors) != len(buf):
-            raise IOError("Data buffer size incorrect! "
-                          "Expected: {} bytes / Received: {} bytes"
-                          .format(len(self._colors), len(buf)))
-        self._colors[:] = buf
-
-    def setBuffer(self, buf):
-        util.deprecated.deprecated('layout.setBuffer')
-
-        # https://stackoverflow.com/questions/1624883
-        self.set_colors(buf=list(zip(*(iter(buf),) * 3)))
-
     def set_brightness(self, brightness):
         self.brightness = brightness
         for d in self.drivers:
@@ -202,3 +208,22 @@ class MultiLayout(Layout):
 
     def gen_multi(self, *args, **kwds):
         raise NotImplementedError
+
+    def set_colors(self, buf):
+        """
+        DEPRECATED: use self.color_list
+
+        Use with extreme caution!
+        Directly sets the internal buffer and bypasses all brightness and rotation control.
+        buf must also be in the exact format required by the display type.
+        """
+        util.deprecated.deprecated('layout.set_colors')
+        if len(self._colors) != len(buf):
+            raise IOError("Data buffer size incorrect! "
+                          "Expected: {} bytes / Received: {} bytes"
+                          .format(len(self._colors), len(buf)))
+        self._colors[:] = buf
+
+    def setBuffer(self, buf):
+        util.deprecated.deprecated('layout.setBuffer')
+        self.color_list = buf
diff --git a/bibliopixel/project/project.py b/bibliopixel/project/project.py
index 8389553..f7b7343 100644
--- a/bibliopixel/project/project.py
+++ b/bibliopixel/project/project.py
@@ -49,7 +49,7 @@ class Project:
         self.animation = create(animation, 'animation')
 
         eq = event_queue.EventQueue(maxsize=event_queue_maxsize)
-        self.animation.event_queue = eq
+        self.layout.event_queue = self.animation.event_queue = eq
         self.animation.preframe_callback = eq.get_and_run_events
 
         # Unfortunately, the whole animation cycle is controlled by methods on


Test patch:
diff --git a/test/bibliopixel/layout/layout_test.py b/test/bibliopixel/layout/layout_test.py
index 4136d81..485e8e2 100644
--- a/test/bibliopixel/layout/layout_test.py
+++ b/test/bibliopixel/layout/layout_test.py
@@ -1,6 +1,11 @@
 import unittest
 from bibliopixel.layout import Strip
-from bibliopixel.util import colors
+from bibliopixel.util.colors import COLORS
+
+RED = COLORS.Red
+GREEN = COLORS.Green
+BLACK = COLORS.Black
+HOT_PINK = COLORS['hot pink']
 
 
 class MockDriver:
@@ -13,10 +18,34 @@ class MockDriver:
 
 
 class LayoutTest(unittest.TestCase):
-    def test_set_colors(self):
+    def test_set_individual_colors(self):
         strip = Strip(MockDriver(4))
         strip.set(0, 'red'),
-        strip.set(1, colors.Green)
+        strip.set(1, GREEN)
         strip.set(2, 'hot pink')
-        expected = [(255, 0, 0), (0, 255, 0), (255, 105, 180), (0, 0, 0)]
+        expected = [RED, GREEN, HOT_PINK, BLACK]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_color(self):
+        strip = Strip(MockDriver(4))
+        strip.color_list = RED, GREEN, HOT_PINK
+        expected = [RED, GREEN, HOT_PINK, BLACK]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_linear_color(self):
+        strip = Strip(MockDriver(4))
+        strip.color_list = RED + GREEN + HOT_PINK
+        expected = [RED, GREEN, HOT_PINK, BLACK]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_too_much_color(self):
+        strip = Strip(MockDriver(4))
+        strip.color_list = RED + GREEN + HOT_PINK + GREEN + HOT_PINK
+        expected = [RED, GREEN, HOT_PINK, GREEN]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_offset(self):
+        strip = Strip(MockDriver(4))
+        strip.set_color_list(RED + GREEN, 2)
+        expected = [BLACK, BLACK, RED, GREEN]
         self.assertEquals(expected, strip.color_list)