[graph] automatically trigger Graph updates

* update graph internals / node status when:
 - an input attribute is set
 - edges are added/removed
* add GraphModification context manager to group graph updates
This commit is contained in:
Yann Lanthony 2017-10-30 16:27:05 +01:00
parent a0cdc65143
commit 5e4b26fa5e

View file

@ -11,6 +11,7 @@ import shutil
import time
import uuid
from collections import defaultdict
from contextlib import contextmanager
from enum import Enum # available by default in python3. For python2: "pip install enum34"
import logging
@ -48,6 +49,29 @@ else:
basestring = basestring
@contextmanager
def GraphModification(graph):
"""
A Context Manager that can be used to trigger only one Graph update
for a group of several modifications.
GraphModifications can be nested.
"""
if not isinstance(graph, Graph):
raise ValueError("GraphModification expects a Graph instance")
# Store update policy
enabled = graph.updateEnabled
# Disable graph update for nested block
# (does nothing if already disabled)
graph.updateEnabled = False
try:
yield # Execute nested block
except Exception:
raise
finally:
# Restore update policy
graph.updateEnabled = enabled
def hash(v):
hashObject = hashlib.sha1(str(v).encode('utf-8'))
return hashObject.hexdigest()
@ -120,6 +144,14 @@ class Attribute(BaseObject):
if self._value == value:
return
self._value = value
# Request graph update when input parameter value is set
# and parent node belongs to a graph
# Output attributes value are set internally during the update process,
# which is why we don't trigger any update in this case
# TODO: update only the nodes impacted by this change
# TODO: only update the graph if this attribute participates to a UID
if self.node.graph and self.attributeDesc.isInput:
self.node.graph.update()
self.valueChanged.emit()
@property
@ -409,6 +441,7 @@ class Node(BaseObject):
self.attributesPerUid[uidIndex].add(attr)
def _applyExpr(self):
with GraphModification(self.graph):
for attr in self._attributes:
attr._applyExpr()
@ -690,6 +723,8 @@ class Graph(BaseObject):
def __init__(self, name, parent=None):
super(Graph, self).__init__(parent)
self.name = name
self._updateEnabled = True
self._updateRequested = False
self._nodes = DictModel(keyAttrName='name', parent=self)
self._edges = DictModel(keyAttrName='dst', parent=self) # use dst attribute as unique key since it can only have one input connection
self._cacheDir = ''
@ -718,6 +753,18 @@ class Graph(BaseObject):
# Create graph edges by resolving attributes expressions
self._applyExpr()
@property
def updateEnabled(self):
return self._updateEnabled
@updateEnabled.setter
def updateEnabled(self, enabled):
self._updateEnabled = enabled
if enabled and self._updateRequested:
# Trigger an update if requested while disabled
self.update()
self._updateRequested = False
def _addNode(self, node, uniqueName):
"""
Internal method to add the given node to this Graph, with the given name (must be unique).
@ -734,10 +781,6 @@ class Graph(BaseObject):
self._nodes.add(node)
self.stopExecutionRequested.connect(node.stopProcess)
# Trigger internal update when an attribute is modified
for attr in node.attributes: # type: Attribute
attr.valueChanged.connect(self.updateInternals)
def addNode(self, node, uniqueName=None):
"""
Add the given node to this Graph with an optional unique name,
@ -763,6 +806,7 @@ class Graph(BaseObject):
inEdges = {}
outEdges = {}
with GraphModification(self):
for attr in node._attributes:
for edge in self.outEdges(attr):
self.edges.remove(edge)
@ -771,7 +815,6 @@ class Graph(BaseObject):
edge = self.edges.pop(attr)
inEdges[edge.dst.fullName()] = edge.src.fullName()
self.updateInternals()
return inEdges, outEdges
@Slot(str, result=Node)
@ -838,9 +881,11 @@ class Graph(BaseObject):
self.edges.add(edge)
dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit()
self.update()
return edge
def addEdges(self, *edges):
with GraphModification(self):
for edge in edges:
self.addEdge(*edge)
@ -850,6 +895,7 @@ class Graph(BaseObject):
edge = self.edges.pop(dstAttr)
dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit()
self.update()
return edge
def getDepth(self, node):
@ -1028,6 +1074,7 @@ class Graph(BaseObject):
return flowEdges
def _applyExpr(self):
with GraphModification(self):
for node in self._nodes:
node._applyExpr()
@ -1060,6 +1107,8 @@ class Graph(BaseObject):
node.updateStatisticsFromCache()
def update(self):
if not self._updateEnabled:
self._updateRequested = True
self.updateInternals()
self.updateStatusFromCache()