[core] fix memory management

* parent Nodes, Edges and Attributes to their respective models
* use weakrefs for those objects to avoid cyclic references
* add 'root' property on Attribute for parenting to List/GroupAttribute (parent still exists for Qt-style parenting)
* UI: update commands to match those changes
This commit is contained in:
Yann Lanthony 2017-11-07 20:18:17 +01:00
parent f029a573b2
commit e38f112c55
2 changed files with 54 additions and 38 deletions

View file

@ -5,8 +5,7 @@ import collections
import hashlib import hashlib
import json import json
import os import os
import psutil import weakref
import re
import shutil import shutil
import time import time
import uuid import uuid
@ -82,7 +81,7 @@ def hash(v):
return hashObject.hexdigest() return hashObject.hexdigest()
def attribute_factory(description, value, isOutput, node, parent=None): def attribute_factory(description, value, isOutput, node, root=None, parent=None):
# type: (desc.Attribute, (), bool, Node, Attribute) -> Attribute # type: (desc.Attribute, (), bool, Node, Attribute) -> Attribute
""" """
Create an Attribute based on description type. Create an Attribute based on description type.
@ -92,7 +91,8 @@ def attribute_factory(description, value, isOutput, node, parent=None):
value: value of the Attribute. Will be set if not None. value: value of the Attribute. Will be set if not None.
isOutput: whether is Attribute is an output attribute. isOutput: whether is Attribute is an output attribute.
node: node owning the Attribute. Note that the created Attribute is not added to Node's attributes node: node owning the Attribute. Note that the created Attribute is not added to Node's attributes
parent: (optional) parent Attribute (must be ListAttribute or GroupAttribute) root: (optional) parent Attribute (must be ListAttribute or GroupAttribute)
parent (BaseObject): (optional) the parent BaseObject if any
""" """
if isinstance(description, meshroom.core.desc.GroupAttribute): if isinstance(description, meshroom.core.desc.GroupAttribute):
cls = GroupAttribute cls = GroupAttribute
@ -100,7 +100,7 @@ def attribute_factory(description, value, isOutput, node, parent=None):
cls = ListAttribute cls = ListAttribute
else: else:
cls = Attribute cls = Attribute
attr = cls(node, description, isOutput, parent=parent) attr = cls(node, description, isOutput, root, parent)
if value is not None: if value is not None:
attr.value = value attr.value = value
return attr return attr
@ -110,10 +110,21 @@ class Attribute(BaseObject):
""" """
""" """
def __init__(self, node, attributeDesc, isOutput, parent=None): def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
"""
Attribute constructor
Args:
node (Node): the Node hosting this Attribute
attributeDesc (desc.Attribute): the description of this Attribute
isOutput (bool): whether this Attribute is an output of the Node
root (Attribute): (optional) the root Attribute (List or Group) containing this one
parent (BaseObject): (optional) the parent BaseObject
"""
super(Attribute, self).__init__(parent) super(Attribute, self).__init__(parent)
self._name = attributeDesc.name self._name = attributeDesc.name
self.node = node # type: Node self._root = None if root is None else weakref.ref(root)
self._node = weakref.ref(node)
self.attributeDesc = attributeDesc self.attributeDesc = attributeDesc
self._isOutput = isOutput self._isOutput = isOutput
self._value = attributeDesc.value self._value = attributeDesc.value
@ -122,15 +133,22 @@ class Attribute(BaseObject):
# invalidation value for output attributes # invalidation value for output attributes
self._invalidationValue = "" self._invalidationValue = ""
@property
def node(self):
return self._node()
@property
def root(self):
return self._root() if self._root else None
def absoluteName(self): def absoluteName(self):
return '{}.{}.{}'.format(self.node.graph.name, self.node.name, self._name) return '{}.{}.{}'.format(self.node.graph.name, self.node.name, self._name)
def fullName(self): def fullName(self):
""" Name inside the Graph: nodeName.name """ """ Name inside the Graph: nodeName.name """
if isinstance(self.parent(), ListAttribute): if isinstance(self.root, ListAttribute):
return '{}[{}]'.format(self.parent().fullName(), self.parent().index(self)) return '{}[{}]'.format(self.root.fullName(), self.root.index(self))
elif isinstance(self.parent(), GroupAttribute): elif isinstance(self.root, GroupAttribute):
return '{}.{}'.format(self.parent().fullName(), self._name) return '{}.{}'.format(self.root.fullName(), self._name)
return '{}.{}'.format(self.node.name, self._name) return '{}.{}'.format(self.node.name, self._name)
def getName(self): def getName(self):
@ -234,8 +252,8 @@ class Attribute(BaseObject):
class ListAttribute(Attribute): class ListAttribute(Attribute):
def __init__(self, node, attributeDesc, isOutput, parent=None): def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
super(ListAttribute, self).__init__(node, attributeDesc, isOutput, parent) super(ListAttribute, self).__init__(node, attributeDesc, isOutput, root, parent)
self._value = ListModel(parent=self) self._value = ListModel(parent=self)
def __getitem__(self, item): def __getitem__(self, item):
@ -251,19 +269,17 @@ class ListAttribute(Attribute):
def append(self, value): def append(self, value):
self.extend([value]) self.extend([value])
def insert(self, value, index): def insert(self, index, value):
attr = attribute_factory(self.attributeDesc.elementDesc, value, self.isOutput, self.node, self) values = value if isinstance(value, list) else [value]
self._value.insert(index, [attr]) attrs = [attribute_factory(self.attributeDesc.elementDesc, v, self.isOutput, self.node, self) for v in values]
self._value.insert(index, attrs)
def index(self, item): def index(self, item):
return self._value.indexOf(item) return self._value.indexOf(item)
def extend(self, values): def extend(self, values):
childAttributes = [] values = [attribute_factory(self.attributeDesc.elementDesc, v, self.isOutput, self.node, self) for v in values]
for value in values: self._value.extend(values)
attr = attribute_factory(self.attributeDesc.elementDesc, value, self.isOutput, self.node, parent=self)
childAttributes.append(attr)
self._value.extend(childAttributes)
def remove(self, index): def remove(self, index):
self._value.removeAt(index) self._value.removeAt(index)
@ -288,13 +304,13 @@ class ListAttribute(Attribute):
class GroupAttribute(Attribute): class GroupAttribute(Attribute):
def __init__(self, node, attributeDesc, isOutput, parent=None): def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
super(GroupAttribute, self).__init__(node, attributeDesc, isOutput, parent) super(GroupAttribute, self).__init__(node, attributeDesc, isOutput, root, parent)
self._value = DictModel(keyAttrName='name', parent=self) self._value = DictModel(keyAttrName='name', parent=self)
subAttributes = [] subAttributes = []
for subAttrDesc in self.attributeDesc.groupDesc: for subAttrDesc in self.attributeDesc.groupDesc:
childAttr = attribute_factory(subAttrDesc, None, self.isOutput, self.node, parent=self) childAttr = attribute_factory(subAttrDesc, None, self.isOutput, self.node, self)
subAttributes.append(childAttr) subAttributes.append(childAttr)
self._value.reset(subAttributes) self._value.reset(subAttributes)
@ -335,16 +351,17 @@ class Edge(BaseObject):
def __init__(self, src, dst, parent=None): def __init__(self, src, dst, parent=None):
super(Edge, self).__init__(parent) super(Edge, self).__init__(parent)
self._src = src self._src = weakref.ref(src)
self._dst = dst self._dst = weakref.ref(dst)
self._repr = "<Edge> {} -> {}".format(self._src(), self._dst())
@property @property
def src(self): def src(self):
return self._src return self._src()
@property @property
def dst(self): def dst(self):
return self._dst return self._dst()
src = Property(Attribute, src.fget, constant=True) src = Property(Attribute, src.fget, constant=True)
dst = Property(Attribute, dst.fget, constant=True) dst = Property(Attribute, dst.fget, constant=True)
@ -477,11 +494,11 @@ class Node(BaseObject):
for attrDesc in self.nodeDesc.inputs: for attrDesc in self.nodeDesc.inputs:
assert isinstance(attrDesc, meshroom.core.desc.Attribute) assert isinstance(attrDesc, meshroom.core.desc.Attribute)
self._attributes.add(attribute_factory(attrDesc, None, False, self, self)) self._attributes.add(attribute_factory(attrDesc, None, False, self))
for attrDesc in self.nodeDesc.outputs: for attrDesc in self.nodeDesc.outputs:
assert isinstance(attrDesc, meshroom.core.desc.Attribute) assert isinstance(attrDesc, meshroom.core.desc.Attribute)
self._attributes.add(attribute_factory(attrDesc, None, True, self, self)) self._attributes.add(attribute_factory(attrDesc, None, True, self))
# List attributes per uid # List attributes per uid
for attr in self._attributes: for attr in self._attributes:
@ -771,7 +788,7 @@ class Graph(BaseObject):
for nodeName, nodeData in graphData.items(): for nodeName, nodeData in graphData.items():
if not isinstance(nodeData, dict): if not isinstance(nodeData, dict):
raise RuntimeError('loadGraph error: Node is not a dict. File: {}'.format(filepath)) raise RuntimeError('loadGraph error: Node is not a dict. File: {}'.format(filepath))
n = Node(nodeData['nodeType'], parent=self, **nodeData['attributes']) n = Node(nodeData['nodeType'], **nodeData['attributes'])
# Add node to the graph with raw attributes values # Add node to the graph with raw attributes values
self._addNode(n, nodeName) self._addNode(n, nodeName)
@ -851,8 +868,7 @@ class Graph(BaseObject):
:return: :return:
:rtype: Node :rtype: Node
""" """
node = self.addNode(Node(nodeDesc=nodeType, parent=self, **kwargs)) return self.addNode(Node(nodeDesc=nodeType, **kwargs))
return node
def _createUniqueNodeName(self, inputName): def _createUniqueNodeName(self, inputName):
i = 1 i = 1
@ -917,11 +933,10 @@ class Graph(BaseObject):
def removeEdge(self, dstAttr): def removeEdge(self, dstAttr):
if dstAttr not in self.edges.keys(): if dstAttr not in self.edges.keys():
raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.fullName())) raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.fullName()))
edge = self.edges.pop(dstAttr) self.edges.pop(dstAttr)
dstAttr.valueChanged.emit() dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit() dstAttr.isLinkChanged.emit()
self.update() self.update()
return edge
def getDepth(self, node): def getDepth(self, node):
# TODO: would be better to use bfs instead of recursive function # TODO: would be better to use bfs instead of recursive function

View file

@ -101,7 +101,7 @@ class RemoveNodeCommand(GraphCommand):
def undoImpl(self): def undoImpl(self):
node = self.graph.addNode(Node(nodeDesc=self.nodeDict["nodeType"], node = self.graph.addNode(Node(nodeDesc=self.nodeDict["nodeType"],
parent=self.graph, **self.nodeDict["attributes"] **self.nodeDict["attributes"]
), self.nodeName) ), self.nodeName)
assert (node.getName() == self.nodeName) assert (node.getName() == self.nodeName)
# recreate out edges deleted on node removal # recreate out edges deleted on node removal
@ -186,7 +186,7 @@ class ListAttributeAppendCommand(GraphCommand):
class ListAttributeRemoveCommand(GraphCommand): class ListAttributeRemoveCommand(GraphCommand):
def __init__(self, graph, attribute, parent=None): def __init__(self, graph, attribute, parent=None):
super(ListAttributeRemoveCommand, self).__init__(graph, parent) super(ListAttributeRemoveCommand, self).__init__(graph, parent)
listAttribute = attribute.parent() listAttribute = attribute.root
assert isinstance(listAttribute, ListAttribute) assert isinstance(listAttribute, ListAttribute)
self.listAttrName = listAttribute.fullName() self.listAttrName = listAttribute.fullName()
self.index = listAttribute.index(attribute) self.index = listAttribute.index(attribute)
@ -200,5 +200,6 @@ class ListAttributeRemoveCommand(GraphCommand):
def undoImpl(self): def undoImpl(self):
listAttribute = self.graph.attribute(self.listAttrName) listAttribute = self.graph.attribute(self.listAttrName)
listAttribute.insert(self.value, self.index) listAttribute.insert(self.index, self.value)