Repository: vispy/vispy

Issue:
Bug with ModularProgram: variables not updated
In [this example](https://github.com/vispy/experimental/blob/master/cr_plot/bug.py), using the mouse wheel should allow you to zoom in or out. It doesn't work, but it does if the program is reconstructed at every draw (which can be done by setting `_dirty` to `True` all the time for instance).

This example requires the `scenegraph` branch of Vispy.



Discussion:

Resolving patch:
diff --git a/vispy/gloo/program.py b/vispy/gloo/program.py
--- a/vispy/gloo/program.py
+++ b/vispy/gloo/program.py
@@ -307,7 +307,10 @@ def _activate(self):
         logger.debug("GPU: Activating program")
         
         gl.glUseProgram(self.handle)
+        
+        self._activate_variables()
 
+    def _activate_variables(self):
         for uniform in self._uniforms.values():
             if uniform.active:
                 uniform.activate()
diff --git a/vispy/scene/shaders/composite.py b/vispy/scene/shaders/composite.py
--- a/vispy/scene/shaders/composite.py
+++ b/vispy/scene/shaders/composite.py
@@ -120,6 +120,9 @@ def __init__(self, vmain, fmain):
 
         # hook definitions
         self._hook_defs = {}  # {'hook_name': Function}
+        
+        # Cache state of Variables so we know which ones require update
+        self._variable_state = {}
 
         self._find_hooks()
 
@@ -267,12 +270,15 @@ def _build(self):
         self.vshader = vs
         self.fshader = fs
 
-        # set all variables..
-        self._apply_variables()
-
         # and continue.
         super(ModularProgram, self)._build()
 
+    def _activate_variables(self):
+        # set all variables
+        self._apply_variables()
+        
+        super(ModularProgram, self)._activate_variables()
+        
     def _find_hooks(self):
         # Locate all undefined function prototypes in both shaders
         vprots = parsing.find_prototypes(self.vmain)
@@ -463,4 +469,7 @@ def _apply_variables(self):
             if isinstance(spec, Function) or spec.vtype == 'varying':
                 continue
             logger.debug("    %s = %s" % (name, spec.value))
-            self[name] = spec.value
+            state_id = spec.state_id
+            if self._variable_state.get(name, None) != state_id:
+                self[name] = spec.value
+                self._variable_state[name] = state_id
diff --git a/vispy/scene/shaders/function.py b/vispy/scene/shaders/function.py
--- a/vispy/scene/shaders/function.py
+++ b/vispy/scene/shaders/function.py
@@ -68,10 +68,11 @@ class Variable(ShaderObject):
     Created by Function.__getitem__
     """
     def __init__(self, function, name, spec=None, anonymous=False):
+        self._state_counter = 0
         super(Variable, self).__init__()
         self.function = function
         self._name = name  # local name within the function
-        self.spec = None  # (vtype, dtype, value)
+        self._spec = None  # (vtype, dtype, value)
         if spec is not None:
             self.spec = spec
         self.anonymous = anonymous  # if True, variable may be renamed.
@@ -116,6 +117,22 @@ def value(self):
                             "determine value." % self)
         return self.spec[2]
 
+    @property
+    def spec(self):
+        return self._spec
+    
+    @spec.setter
+    def spec(self, s):
+        self._spec = s
+        self._state_counter += 1
+        
+    @property
+    def state_id(self):
+        """Return a unique ID that changes whenever the state of the Variable
+        has changed. This allows ModularProgram to quickly determine whether
+        the value has changed since it was last used."""
+        return id(self), self._state_counter
+
     def __repr__(self):
         return ("<Variable '%s' on %s (%d)>" %
                 (self.name, self.function.name, id(self)))


Test patch: