[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 time
import uuid import uuid
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager
from enum import Enum # available by default in python3. For python2: "pip install enum34" from enum import Enum # available by default in python3. For python2: "pip install enum34"
import logging import logging
@ -48,6 +49,29 @@ else:
basestring = basestring 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): def hash(v):
hashObject = hashlib.sha1(str(v).encode('utf-8')) hashObject = hashlib.sha1(str(v).encode('utf-8'))
return hashObject.hexdigest() return hashObject.hexdigest()
@ -120,6 +144,14 @@ class Attribute(BaseObject):
if self._value == value: if self._value == value:
return return
self._value = value 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() self.valueChanged.emit()
@property @property
@ -409,6 +441,7 @@ class Node(BaseObject):
self.attributesPerUid[uidIndex].add(attr) self.attributesPerUid[uidIndex].add(attr)
def _applyExpr(self): def _applyExpr(self):
with GraphModification(self.graph):
for attr in self._attributes: for attr in self._attributes:
attr._applyExpr() attr._applyExpr()
@ -690,6 +723,8 @@ class Graph(BaseObject):
def __init__(self, name, parent=None): def __init__(self, name, parent=None):
super(Graph, self).__init__(parent) super(Graph, self).__init__(parent)
self.name = name self.name = name
self._updateEnabled = True
self._updateRequested = False
self._nodes = DictModel(keyAttrName='name', parent=self) 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._edges = DictModel(keyAttrName='dst', parent=self) # use dst attribute as unique key since it can only have one input connection
self._cacheDir = '' self._cacheDir = ''
@ -718,6 +753,18 @@ class Graph(BaseObject):
# Create graph edges by resolving attributes expressions # Create graph edges by resolving attributes expressions
self._applyExpr() 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): def _addNode(self, node, uniqueName):
""" """
Internal method to add the given node to this Graph, with the given name (must be unique). 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._nodes.add(node)
self.stopExecutionRequested.connect(node.stopProcess) 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): def addNode(self, node, uniqueName=None):
""" """
Add the given node to this Graph with an optional unique name, Add the given node to this Graph with an optional unique name,
@ -763,6 +806,7 @@ class Graph(BaseObject):
inEdges = {} inEdges = {}
outEdges = {} outEdges = {}
with GraphModification(self):
for attr in node._attributes: for attr in node._attributes:
for edge in self.outEdges(attr): for edge in self.outEdges(attr):
self.edges.remove(edge) self.edges.remove(edge)
@ -771,7 +815,6 @@ class Graph(BaseObject):
edge = self.edges.pop(attr) edge = self.edges.pop(attr)
inEdges[edge.dst.fullName()] = edge.src.fullName() inEdges[edge.dst.fullName()] = edge.src.fullName()
self.updateInternals()
return inEdges, outEdges return inEdges, outEdges
@Slot(str, result=Node) @Slot(str, result=Node)
@ -838,9 +881,11 @@ class Graph(BaseObject):
self.edges.add(edge) self.edges.add(edge)
dstAttr.valueChanged.emit() dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit() dstAttr.isLinkChanged.emit()
self.update()
return edge return edge
def addEdges(self, *edges): def addEdges(self, *edges):
with GraphModification(self):
for edge in edges: for edge in edges:
self.addEdge(*edge) self.addEdge(*edge)
@ -850,6 +895,7 @@ class Graph(BaseObject):
edge = self.edges.pop(dstAttr) edge = self.edges.pop(dstAttr)
dstAttr.valueChanged.emit() dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit() dstAttr.isLinkChanged.emit()
self.update()
return edge return edge
def getDepth(self, node): def getDepth(self, node):
@ -1028,6 +1074,7 @@ class Graph(BaseObject):
return flowEdges return flowEdges
def _applyExpr(self): def _applyExpr(self):
with GraphModification(self):
for node in self._nodes: for node in self._nodes:
node._applyExpr() node._applyExpr()
@ -1060,6 +1107,8 @@ class Graph(BaseObject):
node.updateStatisticsFromCache() node.updateStatisticsFromCache()
def update(self): def update(self):
if not self._updateEnabled:
self._updateRequested = True
self.updateInternals() self.updateInternals()
self.updateStatusFromCache() self.updateStatusFromCache()