Skip to content

Commit ee9f405

Browse files
committed
Use breadth-first-search in GenericEvaluationEngine
A BFS generates a better graph if the graph is very deep since a DFS would get stuck in the first branch (if the limit is low).
1 parent cc272ac commit ee9f405

File tree

1 file changed

+73
-47
lines changed

1 file changed

+73
-47
lines changed

extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,7 @@ export class GenericEvaluator implements Evaluator {
5151
// Use structural information about variables
5252
// from the evaluation response if present.
5353
if (reply.variablesReference) {
54-
let graph: GraphVisualizationData = {
55-
kind: { graph: true },
56-
nodes: [],
57-
edges: []
58-
};
59-
await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference, graph);
54+
const graph = await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference);
6055

6156
return {
6257
kind: "data",
@@ -97,54 +92,85 @@ export class GenericEvaluator implements Evaluator {
9792
}
9893
}
9994

95+
/**
96+
* Constructs GraphVisualizationData from a DAP variables
97+
* reference by successively querying the debug adapter for
98+
* variables. Objects are considered to be equivalent if
99+
* they share the same variables reference (this is important
100+
* for representing cyclic relationships).
101+
*
102+
* @param rootLabel - The root object's label
103+
* @param rootVariablesReference - The root object's DAP variables reference
104+
* @param maxDepth - The maximum depth to search at
105+
* @param maxKnownNodes - The maximum number of nodes
106+
*/
100107
private async constructGraphFromVariablesReference(
101-
label: string,
102-
variablesReference: number,
103-
graph: GraphVisualizationData,
104-
isTopLevel: boolean = true,
105-
knownNodeIds: { [ref: number]: string; } = {},
106-
recursionDepth: number = 0,
107-
maxRecursionDepth: number = 30,
108+
rootLabel: string,
109+
rootVariablesReference: number,
110+
maxDepth: number = 30,
108111
maxKnownNodes: number = 100,
109-
): Promise<string> {
110-
const hasChilds = variablesReference > 0;
111-
const knownCount = Object.keys(knownNodeIds).length + 1;
112-
const canRecurse = recursionDepth < maxRecursionDepth && knownCount < maxKnownNodes;
113-
let result: GraphNode = {
114-
id: hasChilds ? `${variablesReference}` : `__${label}@${knownCount}__`,
115-
label,
116-
color: isTopLevel ? "lightblue" : undefined,
117-
shape: "box",
112+
): Promise<GraphVisualizationData> {
113+
// Perform a breadth-first search on the object to construct the graph
114+
115+
const graph: GraphVisualizationData = {
116+
kind: { graph: true },
117+
nodes: [],
118+
edges: []
118119
};
119-
knownNodeIds[variablesReference] = result.id;
120-
121-
if (hasChilds && canRecurse) {
122-
for (const variable of await this.session.getVariables({ variablesReference })) {
123-
let childId: string;
124-
125-
if (variable.variablesReference > 0 && variable.variablesReference in knownNodeIds) {
126-
// If the object is known, we have a (potentially cyclic) reference.
127-
childId = knownNodeIds[variable.variablesReference];
128-
} else {
129-
// Otherwise recurse
130-
childId = await this.constructGraphFromVariablesReference(
131-
variable.value,
132-
variable.variablesReference,
133-
graph,
134-
false, // isTopLevel
135-
knownNodeIds,
136-
recursionDepth + 1,
137-
maxRecursionDepth,
138-
maxKnownNodes
139-
);
120+
const knownNodeIds: { [ref: number]: string; } = {};
121+
const bfsQueue: { source: { id: string, name: string } | undefined, label: string, variablesReference: number, depth: number }[] = [{
122+
source: undefined,
123+
label: rootLabel,
124+
variablesReference: rootVariablesReference,
125+
depth: 0,
126+
}];
127+
128+
let knownCount: number = 0;
129+
130+
do {
131+
const variable = bfsQueue.shift()!;
132+
const hasChilds = variable.variablesReference > 0;
133+
134+
if (variable.depth > maxDepth) {
135+
break;
136+
}
137+
138+
let nodeId: string;
139+
140+
if (!hasChilds || !(variable.variablesReference in knownNodeIds)) {
141+
// The variable is a leaf or an unvisited object: create the node.
142+
143+
const node: GraphNode = {
144+
id: hasChilds ? `${variable.variablesReference}` : `__${variable.label}@${knownCount}__`,
145+
label: variable.label,
146+
color: variable.depth == 0 ? "lightblue" : undefined,
147+
shape: "box",
148+
};
149+
150+
graph.nodes.push(node);
151+
knownCount++;
152+
153+
if (hasChilds) {
154+
knownNodeIds[variable.variablesReference] = node.id;
155+
156+
for (const child of await this.session.getVariables({ variablesReference: variable.variablesReference })) {
157+
bfsQueue.push({ source: { id: node.id, name: child.name }, label: child.value, variablesReference: child.variablesReference, depth: variable.depth + 1 });
158+
}
140159
}
141160

142-
graph.edges.push({ from: result.id, to: childId, label: variable.name });
161+
nodeId = node.id;
162+
} else {
163+
// The variable is a visited object (e.g. due to a cyclic reference)
164+
165+
nodeId = knownNodeIds[variable.variablesReference];
143166
}
144-
}
145167

146-
graph.nodes.push(result);
147-
return result.id;
168+
if (variable.source) {
169+
graph.edges.push({ from: variable.source.id, to: nodeId, label: variable.source.name });
170+
}
171+
} while (bfsQueue.length > 0 && knownCount <= maxKnownNodes);
172+
173+
return graph;
148174
}
149175

150176
protected getFinalExpression(args: {

0 commit comments

Comments
 (0)