Browse Source

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
shortcuts
Martin Koníček 16 years ago
parent
commit
7d12d7c92f
  1. 2
      src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/Layout/GraphMatcher.cs
  2. 130
      src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectGraphBuilder.cs
  3. 11
      src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/ObjectGraph/ObjectNode.cs
  4. 52
      src/AddIns/Misc/Debugger/Debugger.AddIn/Project/Src/Visualizers/Graph/VisualizerWPFWindow.xaml.cs

2
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) 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();
} }
} }
} }

130
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. /// The underlying debugger service used for getting expression values.
/// </summary> /// </summary>
private WindowsDebugger debuggerService; private WindowsDebugger debuggerService;
private ObjectGraph resultGraph;
/// <summary> /// <summary>
/// The resulting object graph. /// Underlying object graph data struture.
/// </summary> /// </summary>
private ObjectGraph resultGraph; public ObjectGraph ResultGraph { get { return this.resultGraph; } }
/// <summary> /// <summary>
/// System.Runtime.CompilerServices.GetHashCode method, for obtaining non-overriden hash codes from debuggee. /// System.Runtime.CompilerServices.GetHashCode method, for obtaining non-overriden hash codes from debuggee.
/// </summary> /// </summary>
@ -60,7 +63,7 @@ namespace Debugger.AddIn.Visualizers.Graph
/// <summary> /// <summary>
/// Binding flags for getting member expressions. /// Binding flags for getting member expressions.
/// </summary> /// </summary>
private readonly Debugger.MetaData.BindingFlags _bindingFlags = private readonly Debugger.MetaData.BindingFlags _bindingFlags =
BindingFlags.Public | BindingFlags.Instance | BindingFlags.Field | BindingFlags.GetProperty; BindingFlags.Public | BindingFlags.Instance | BindingFlags.Field | BindingFlags.GetProperty;
/// <summary> /// <summary>
@ -92,37 +95,52 @@ namespace Debugger.AddIn.Visualizers.Graph
// empty graph for null expression // empty graph for null expression
if (!debuggerService.GetValueFromName(expression).IsNull) 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; 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);
}
/// <summary> /// <summary>
/// Builds the subgraph representing given value. /// Returns node in the graph that represents given value, or returns new node if no node found.
/// </summary> /// </summary>
/// <param name="rootValue">The Value for which the subgraph will be built.</param> /// <param name="value">Value for which to obtain the node/</param>
/// <returns>ObjectNode representing the value + all recursive members.</returns> /// <param name="createdNew">True if new node was created, false if existing node was returned.</param>
private ObjectNode buildGraphRecursive(Value rootValue, ExpandedNodes expandedNodes) public ObjectNode ObtainNodeForValue(Value value, out bool createdNew)
{ {
ObjectNode thisNode = createNewNode(rootValue); createdNew = false;
ObjectNode nodeForValue = getExistingNodeForValue(value);
// David: by calling this, we get an array of values, most of them probably invalid, if (nodeForValue == null)
// 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)
{ {
//Value memberValuePerm = memberValue.GetPermanentReference(); // if no node for memberValue exists, create it
nodeForValue = createNewNode(value);
}*/ loadNodeProperties(nodeForValue);
createdNew = true;
foreach(Expression memberExpr in rootValue.Expression.AppendObjectMembers(rootValue.Type, _bindingFlags)) }
return nodeForValue;
}
/// <summary>
/// Fills node contents by adding properties.
/// </summary>
/// <param name="thisNode"></param>
private void loadNodeProperties(ObjectNode thisNode)
{
foreach(Expression memberExpr in thisNode.DebuggerValue.Expression.AppendObjectMembers(thisNode.DebuggerValue.Type, _bindingFlags))
{ {
checkIsOfSupportedType(memberExpr); checkIsOfSupportedType(memberExpr);
@ -131,34 +149,48 @@ namespace Debugger.AddIn.Visualizers.Graph
{ {
// atomic members are added to the list of node's "properties" // atomic members are added to the list of node's "properties"
string memberValueAsString = memberExpr.Evaluate(debuggerService.DebuggedProcess).AsString; string memberValueAsString = memberExpr.Evaluate(debuggerService.DebuggedProcess).AsString;
thisNode.AddAtomicProperty(memberName, memberValueAsString, memberExpr); thisNode.AddAtomicProperty(memberName, memberValueAsString, memberExpr);
} }
else else
{ {
// for object members, complex properties are added
ObjectNode targetNode = null; ObjectNode targetNode = null;
bool memberIsNull = isNull(memberExpr); bool memberIsNull = isNull(memberExpr);
if (!memberIsNull && expandedNodes.IsExpanded(memberExpr.Code)) thisNode.AddComplexProperty(memberName, "", memberExpr, targetNode, memberIsNull);
{ }
// 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) /// <summary>
targetNode = getNodeForValue(memberValue); /// Creates child nodes of this node for each complex property and connects them to property.TargetNode.
if (targetNode == null) /// </summary>
{ /// <param name="thisNode"></param>
// if no node for memberValue exists, build the subgraph for the value /// <param name="expandedNodes"></param>
targetNode = buildGraphRecursive(memberValue, expandedNodes); private void loadChildrenRecursive(ObjectNode thisNode, ExpandedNodes expandedNodes)
} {
} foreach(ObjectProperty complexProperty in thisNode.ComplexProperties)
else {
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;
} }
/// <summary> /// <summary>
@ -175,9 +207,9 @@ namespace Debugger.AddIn.Visualizers.Graph
// remember this node's hashcode for quick lookup // remember this node's hashcode for quick lookup
objectNodesForHashCode.Add(newNode.HashCode, newNode); 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 // and matching nodes in animations
newNode.PermanentReference = permanentReference; newNode.DebuggerValue = permanentReference;
return newNode; return newNode;
} }
@ -187,7 +219,7 @@ namespace Debugger.AddIn.Visualizers.Graph
/// </summary> /// </summary>
/// <param name="value">Valid value representing an instance.</param> /// <param name="value">Valid value representing an instance.</param>
/// <returns></returns> /// <returns></returns>
private ObjectNode getNodeForValue(Value value) private ObjectNode getExistingNodeForValue(Value value)
{ {
int objectHashCode = invokeGetHashCode(value); int objectHashCode = invokeGetHashCode(value);
// are there any nodes with the same hash code? // 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) // (hash codes are not uniqe - http://stackoverflow.com/questions/750947/-net-unique-object-identifier)
ulong objectAddress = value.GetObjectAddress(); ulong objectAddress = value.GetObjectAddress();
ObjectNode nodeWithSameAddress = nodesWithSameHashCode.Find( ObjectNode nodeWithSameAddress = nodesWithSameHashCode.Find(
node => { return objectAddress == node.PermanentReference.GetObjectAddress(); } ); node => { return node.DebuggerValue.GetObjectAddress() == objectAddress; } );
return nodeWithSameAddress; return nodeWithSameAddress;
} }
} }
@ -251,7 +283,7 @@ namespace Debugger.AddIn.Visualizers.Graph
} }
#region Expression helpers #region Expression helpers
private Value getPermanentReference(Expression expr) private Value getPermanentReference(Expression expr)
{ {
return expr.Evaluate(debuggerService.DebuggedProcess).GetPermanentReference(); return expr.Evaluate(debuggerService.DebuggedProcess).GetPermanentReference();

11
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 public class ObjectNode
{ {
/// <summary> /// <summary>
/// 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.
/// </summary>
internal Debugger.Value DebuggerValue { get; set; }
/// <summary>
/// Hash code in the debuggee of the DebuggerValue this node represents.
/// </summary> /// </summary>
internal Debugger.Value PermanentReference { get; set; }
internal int HashCode { get; set; } internal int HashCode { get; set; }
/// <summary>
/// Expression used to obtain this node.
/// </summary>
public Expressions.Expression Expression { get { return this.DebuggerValue.Expression; } }
/*private List<ObjectEdge> _edges = new List<ObjectEdge>(); /*private List<ObjectEdge> _edges = new List<ObjectEdge>();
/// <summary> /// <summary>

52
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 WindowsDebugger debuggerService;
private EnumViewModel<LayoutDirection> layoutViewModel; private EnumViewModel<LayoutDirection> layoutViewModel;
private ObjectGraph objectGraph; private ObjectGraph objectGraph;
private ObjectGraphBuilder objectGraphBuilder;
private PositionedGraph oldGraph; private PositionedGraph oldPosGraph;
private PositionedGraph currentGraph; private PositionedGraph currentPosGraph;
private GraphDrawer graphDrawer; private GraphDrawer graphDrawer;
/// <summary> /// <summary>
@ -91,42 +92,43 @@ namespace Debugger.AddIn.Visualizers.Graph
void refreshGraph() void refreshGraph()
{ {
ObjectGraphBuilder graphBuilder = new ObjectGraphBuilder(debuggerService); this.objectGraph = rebuildGraph();
this.objectGraph = null; layoutGraph(this.objectGraph);
//GraphDrawer drawer = new GraphDrawer(graph);
//drawer.Draw(canvas);
}
ObjectGraph rebuildGraph()
{
this.objectGraphBuilder = new ObjectGraphBuilder(debuggerService);
try try
{ {
ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Building graph for expression: " + txtExpression.Text); 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) catch(DebuggerVisualizerException ex)
{ {
guiHandleException(ex); guiHandleException(ex);
return; return null;
} }
catch(Debugger.GetValueException ex) catch(Debugger.GetValueException ex)
{ {
guiHandleException(ex); guiHandleException(ex);
return; return null;
} }
}
layoutGraph(this.objectGraph);
//GraphDrawer drawer = new GraphDrawer(graph);
//drawer.Draw(canvas);
}
void layoutGraph(ObjectGraph graph) void layoutGraph(ObjectGraph graph)
{ {
ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Calculating graph layout"); ICSharpCode.Core.LoggingService.Debug("Debugger visualizer: Calculating graph layout");
Layout.TreeLayouter layouter = new Layout.TreeLayouter(); Layout.TreeLayouter layouter = new Layout.TreeLayouter();
this.oldGraph = this.currentGraph; this.oldPosGraph = this.currentPosGraph;
this.currentGraph = layouter.CalculateLayout(graph, layoutViewModel.SelectedEnumValue, this.expandedNodes); this.currentPosGraph = layouter.CalculateLayout(graph, layoutViewModel.SelectedEnumValue, this.expandedNodes);
registerExpandCollapseEvents(this.currentGraph); registerExpandCollapseEvents(this.currentPosGraph);
var graphDiff = new GraphMatcher().MatchGraphs(oldGraph, currentGraph); var graphDiff = new GraphMatcher().MatchGraphs(oldPosGraph, currentPosGraph);
this.graphDrawer.StartAnimation(oldGraph, currentGraph, graphDiff); this.graphDrawer.StartAnimation(oldPosGraph, currentPosGraph, graphDiff);
//this.graphDrawer.Draw(currentGraph); //this.graphDrawer.Draw(currentGraph);
} }
@ -146,14 +148,22 @@ namespace Debugger.AddIn.Visualizers.Graph
void node_Expanded(object sender, PositionedPropertyEventArgs e) void node_Expanded(object sender, PositionedPropertyEventArgs e)
{ {
// remember this property is expanded (for later graph rebuilds)
expandedNodes.SetExpanded(e.Property.Expression.Code); 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) void node_Collapsed(object sender, PositionedPropertyEventArgs e)
{ {
// remember this property is collapsed (for later graph rebuilds)
expandedNodes.SetCollapsed(e.Property.Expression.Code); 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);
} }
} }
} }
Loading…
Cancel
Save