From 7d12d7c92f99be7873b5a8bf744c88cc2e8d7410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kon=C3=AD=C4=8Dek?= Date: Sat, 13 Jun 2009 15:14:07 +0000 Subject: [PATCH] Proper expanding of nodes. Refactored ObjectGraphBuilder to better support expanding of nodes. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4291 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Visualizers/Graph/Layout/GraphMatcher.cs | 2 +- .../Graph/ObjectGraph/ObjectGraphBuilder.cs | 130 +++++++++++------- .../Graph/ObjectGraph/ObjectNode.cs | 11 +- .../Graph/VisualizerWPFWindow.xaml.cs | 52 ++++--- 4 files changed, 122 insertions(+), 73 deletions(-) diff --git a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/Layout/GraphMatcher.cs b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/Layout/GraphMatcher.cs index e8a583c588..3dc739887c 100644 --- a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/Layout/GraphMatcher.cs +++ b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/Layout/GraphMatcher.cs @@ -94,7 +94,7 @@ namespace Debugger.AddIn.Visualizers.Graph.Layout private bool isSameAddress(PositionedNode node1, PositionedNode node2) { - return node1.ObjectNode.PermanentReference.GetObjectAddress() == node2.ObjectNode.PermanentReference.GetObjectAddress(); + return node1.ObjectNode.DebuggerValue.GetObjectAddress() == node2.ObjectNode.DebuggerValue.GetObjectAddress(); } } } diff --git a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectGraphBuilder.cs b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectGraphBuilder.cs index 5daaabf17d..87ca058879 100644 --- a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectGraphBuilder.cs +++ b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectGraphBuilder.cs @@ -43,10 +43,13 @@ namespace Debugger.AddIn.Visualizers.Graph /// The underlying debugger service used for getting expression values. /// private WindowsDebugger debuggerService; + + private ObjectGraph resultGraph; /// - /// The resulting object graph. + /// Underlying object graph data struture. /// - private ObjectGraph resultGraph; + public ObjectGraph ResultGraph { get { return this.resultGraph; } } + /// /// System.Runtime.CompilerServices.GetHashCode method, for obtaining non-overriden hash codes from debuggee. /// @@ -60,7 +63,7 @@ namespace Debugger.AddIn.Visualizers.Graph /// /// Binding flags for getting member expressions. /// - private readonly Debugger.MetaData.BindingFlags _bindingFlags = + private readonly Debugger.MetaData.BindingFlags _bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Field | BindingFlags.GetProperty; /// @@ -92,37 +95,52 @@ namespace Debugger.AddIn.Visualizers.Graph // empty graph for null expression if (!debuggerService.GetValueFromName(expression).IsNull) { - resultGraph.Root = buildGraphRecursive(debuggerService.GetValueFromName(expression).GetPermanentReference(), expandedNodes); + //resultGraph.Root = buildGraphRecursive(debuggerService.GetValueFromName(expression).GetPermanentReference(), expandedNodes); + resultGraph.Root = createNewNode(debuggerService.GetValueFromName(expression).GetPermanentReference()); + loadNodeProperties(resultGraph.Root); + loadChildrenRecursive(resultGraph.Root, expandedNodes); } return resultGraph; } + public ObjectNode ObtainNodeForExpression(Expression expr, out bool createdNewNode) + { + return ObtainNodeForValue(getPermanentReference(expr), out createdNewNode); + } + + public ObjectNode ObtainNodeForExpression(Expression expr) + { + bool createdNewNode; // ignore (caller is not interested, otherwise he would use the other overload) + return ObtainNodeForExpression(expr, out createdNewNode); + } + /// - /// Builds the subgraph representing given value. + /// Returns node in the graph that represents given value, or returns new node if no node found. /// - /// The Value for which the subgraph will be built. - /// ObjectNode representing the value + all recursive members. - private ObjectNode buildGraphRecursive(Value rootValue, ExpandedNodes expandedNodes) + /// Value for which to obtain the node/ + /// True if new node was created, false if existing node was returned. + public ObjectNode ObtainNodeForValue(Value value, out bool createdNew) { - ObjectNode thisNode = createNewNode(rootValue); - - // David: by calling this, we get an array of values, most of them probably invalid, - // it would be nice to be able to get a collection of 'values' - // that are valid (basically a snapshot of object's state) - // - a collection of custom objects, - // that contain the string value and DebugType, - // and can enumerate child values. - // It would be also nice to return IEnumerable or ReadonlyCollection - // http://blogs.msdn.com/ericlippert/archive/2008/09/22/arrays-considered-somewhat-harmful.aspx - /*string[] memberValues = rootValue.GetMemberValuesAsString(_bindingFlags); - foreach (string memberValue in memberValues) + createdNew = false; + ObjectNode nodeForValue = getExistingNodeForValue(value); + if (nodeForValue == null) { - //Value memberValuePerm = memberValue.GetPermanentReference(); - - }*/ - - foreach(Expression memberExpr in rootValue.Expression.AppendObjectMembers(rootValue.Type, _bindingFlags)) + // if no node for memberValue exists, create it + nodeForValue = createNewNode(value); + loadNodeProperties(nodeForValue); + createdNew = true; + } + return nodeForValue; + } + + /// + /// Fills node contents by adding properties. + /// + /// + private void loadNodeProperties(ObjectNode thisNode) + { + foreach(Expression memberExpr in thisNode.DebuggerValue.Expression.AppendObjectMembers(thisNode.DebuggerValue.Type, _bindingFlags)) { checkIsOfSupportedType(memberExpr); @@ -131,34 +149,48 @@ namespace Debugger.AddIn.Visualizers.Graph { // atomic members are added to the list of node's "properties" string memberValueAsString = memberExpr.Evaluate(debuggerService.DebuggedProcess).AsString; - thisNode.AddAtomicProperty(memberName, memberValueAsString, memberExpr); + thisNode.AddAtomicProperty(memberName, memberValueAsString, memberExpr); } else { + // for object members, complex properties are added ObjectNode targetNode = null; bool memberIsNull = isNull(memberExpr); - if (!memberIsNull && expandedNodes.IsExpanded(memberExpr.Code)) - { - // for object members, edges are added - Value memberValue = getPermanentReference(memberExpr); - - // if node for memberValue already exists, only add edge to it (so loops etc. are solved correctly) - targetNode = getNodeForValue(memberValue); - if (targetNode == null) - { - // if no node for memberValue exists, build the subgraph for the value - targetNode = buildGraphRecursive(memberValue, expandedNodes); - } - } - else + thisNode.AddComplexProperty(memberName, "", memberExpr, targetNode, memberIsNull); + } + } + } + + /// + /// Creates child nodes of this node for each complex property and connects them to property.TargetNode. + /// + /// + /// + private void loadChildrenRecursive(ObjectNode thisNode, ExpandedNodes expandedNodes) + { + foreach(ObjectProperty complexProperty in thisNode.ComplexProperties) + { + Expression memberExpr = complexProperty.Expression; + ObjectNode targetNode = null; + if (!complexProperty.IsNull && expandedNodes.IsExpanded(memberExpr.Code)) + { + Value memberValue = getPermanentReference(memberExpr); + + bool createdNew; + // get existing node (loop) or create new + targetNode = ObtainNodeForValue(memberValue, out createdNew); + if (createdNew) { - targetNode = null; + // if member node is new, recursively build its subtree + loadChildrenRecursive(targetNode, expandedNodes); } - thisNode.AddComplexProperty(memberName, "", memberExpr, targetNode, memberIsNull); } + else + { + targetNode = null; + } + complexProperty.TargetNode = targetNode; } - - return thisNode; } /// @@ -175,9 +207,9 @@ namespace Debugger.AddIn.Visualizers.Graph // remember this node's hashcode for quick lookup objectNodesForHashCode.Add(newNode.HashCode, newNode); - // permanent reference to the object this node represents is useful for graph building, + // permanent reference to the object this node represents is useful for graph building, // and matching nodes in animations - newNode.PermanentReference = permanentReference; + newNode.DebuggerValue = permanentReference; return newNode; } @@ -187,7 +219,7 @@ namespace Debugger.AddIn.Visualizers.Graph /// /// Valid value representing an instance. /// - private ObjectNode getNodeForValue(Value value) + private ObjectNode getExistingNodeForValue(Value value) { int objectHashCode = invokeGetHashCode(value); // are there any nodes with the same hash code? @@ -202,7 +234,7 @@ namespace Debugger.AddIn.Visualizers.Graph // (hash codes are not uniqe - http://stackoverflow.com/questions/750947/-net-unique-object-identifier) ulong objectAddress = value.GetObjectAddress(); ObjectNode nodeWithSameAddress = nodesWithSameHashCode.Find( - node => { return objectAddress == node.PermanentReference.GetObjectAddress(); } ); + node => { return node.DebuggerValue.GetObjectAddress() == objectAddress; } ); return nodeWithSameAddress; } } @@ -251,7 +283,7 @@ namespace Debugger.AddIn.Visualizers.Graph } #region Expression helpers - + private Value getPermanentReference(Expression expr) { return expr.Evaluate(debuggerService.DebuggedProcess).GetPermanentReference(); diff --git a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectNode.cs b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectNode.cs index 61ecc65193..562fad418c 100644 --- a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectNode.cs +++ b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectNode.cs @@ -17,10 +17,17 @@ namespace Debugger.AddIn.Visualizers.Graph public class ObjectNode { /// - /// Additional info useful for internal algorithms, not to be visible to the user. + /// Permanent reference to the value in the the debugee this node represents. + /// + internal Debugger.Value DebuggerValue { get; set; } + /// + /// Hash code in the debuggee of the DebuggerValue this node represents. /// - internal Debugger.Value PermanentReference { get; set; } internal int HashCode { get; set; } + /// + /// Expression used to obtain this node. + /// + public Expressions.Expression Expression { get { return this.DebuggerValue.Expression; } } /*private List _edges = new List(); /// diff --git a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/VisualizerWPFWindow.xaml.cs b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/VisualizerWPFWindow.xaml.cs index 08457c8a09..05efc25f66 100644 --- a/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/VisualizerWPFWindow.xaml.cs +++ b/src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/VisualizerWPFWindow.xaml.cs @@ -31,9 +31,10 @@ namespace Debugger.AddIn.Visualizers.Graph private WindowsDebugger debuggerService; private EnumViewModel layoutViewModel; private ObjectGraph objectGraph; + private ObjectGraphBuilder objectGraphBuilder; - private PositionedGraph oldGraph; - private PositionedGraph currentGraph; + private PositionedGraph oldPosGraph; + private PositionedGraph currentPosGraph; private GraphDrawer graphDrawer; /// @@ -91,42 +92,43 @@ namespace Debugger.AddIn.Visualizers.Graph void refreshGraph() { - ObjectGraphBuilder graphBuilder = new ObjectGraphBuilder(debuggerService); - this.objectGraph = null; - + this.objectGraph = rebuildGraph(); + layoutGraph(this.objectGraph); + //GraphDrawer drawer = new GraphDrawer(graph); + //drawer.Draw(canvas); + } + + ObjectGraph rebuildGraph() + { + this.objectGraphBuilder = new ObjectGraphBuilder(debuggerService); try { ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Building graph for expression: " + txtExpression.Text); - this.objectGraph = graphBuilder.BuildGraphForExpression(txtExpression.Text, expandedNodes); + return this.objectGraphBuilder.BuildGraphForExpression(txtExpression.Text, this.expandedNodes); } catch(DebuggerVisualizerException ex) { guiHandleException(ex); - return; + return null; } catch(Debugger.GetValueException ex) { guiHandleException(ex); - return; + return null; } - - layoutGraph(this.objectGraph); - - //GraphDrawer drawer = new GraphDrawer(graph); - //drawer.Draw(canvas); - } + } void layoutGraph(ObjectGraph graph) { ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Calculating graph layout"); Layout.TreeLayouter layouter = new Layout.TreeLayouter(); - this.oldGraph = this.currentGraph; - this.currentGraph = layouter.CalculateLayout(graph, layoutViewModel.SelectedEnumValue, this.expandedNodes); - registerExpandCollapseEvents(this.currentGraph); + this.oldPosGraph = this.currentPosGraph; + this.currentPosGraph = layouter.CalculateLayout(graph, layoutViewModel.SelectedEnumValue, this.expandedNodes); + registerExpandCollapseEvents(this.currentPosGraph); - var graphDiff = new GraphMatcher().MatchGraphs(oldGraph, currentGraph); - this.graphDrawer.StartAnimation(oldGraph, currentGraph, graphDiff); + var graphDiff = new GraphMatcher().MatchGraphs(oldPosGraph, currentPosGraph); + this.graphDrawer.StartAnimation(oldPosGraph, currentPosGraph, graphDiff); //this.graphDrawer.Draw(currentGraph); } @@ -146,14 +148,22 @@ namespace Debugger.AddIn.Visualizers.Graph void node_Expanded(object sender, PositionedPropertyEventArgs e) { + // remember this property is expanded (for later graph rebuilds) expandedNodes.SetExpanded(e.Property.Expression.Code); - refreshGraph(); + + // add edge (+ possibly node) to underlying object graph (no need to rebuild) + e.Property.ObjectProperty.TargetNode = this.objectGraphBuilder.ObtainNodeForExpression(e.Property.Expression); + layoutGraph(this.objectGraph); } void node_Collapsed(object sender, PositionedPropertyEventArgs e) { + // remember this property is collapsed (for later graph rebuilds) expandedNodes.SetCollapsed(e.Property.Expression.Code); - refreshGraph(); + + // just remove edge from underlying object graph (no need to rebuild) + e.Property.ObjectProperty.TargetNode = null; + layoutGraph(this.objectGraph); } } } \ No newline at end of file