diff --git a/src/overlays/avg_parallelism_overlay.ts b/src/overlays/avg_parallelism_overlay.ts
index 84b95567..6edc264e 100644
--- a/src/overlays/avg_parallelism_overlay.ts
+++ b/src/overlays/avg_parallelism_overlay.ts
@@ -230,8 +230,8 @@ export class AvgParallelismOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- block.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(block.width * block.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
block.attributes()?.is_collapsed) {
this.shadeNode(block, ctx);
} else if (block instanceof State) {
@@ -245,20 +245,20 @@ export class AvgParallelismOverlay extends GenericSdfgOverlay {
visibleRect.y, visibleRect.w, visibleRect.h))
return;
- if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
- this.shadeNode(node, ctx);
- } else {
- if (node instanceof NestedSDFG &&
- node.attributes().sdfg &&
- node.attributes().sdfg.type !== 'SDFGShell') {
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if ((ctx as any).lod && nodeppp < SDFV.STATE_LOD) {
+ this.shadeNode(node, ctx);
+ }
+ else if (node.attributes().sdfg && node.attributes().sdfg.type !== 'SDFGShell') {
this.recursivelyShadeCFG(
node.data.graph, ctx, ppp, visibleRect
);
- } else {
- this.shadeNode(node, ctx);
}
}
+ else {
+ this.shadeNode(node, ctx);
+ }
});
}
} else if (block instanceof ControlFlowRegion) {
diff --git a/src/overlays/depth_overlay.ts b/src/overlays/depth_overlay.ts
index 955d6046..52b3277f 100644
--- a/src/overlays/depth_overlay.ts
+++ b/src/overlays/depth_overlay.ts
@@ -215,8 +215,8 @@ export class DepthOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- state.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(state.width * state.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
state.data.state.attributes.is_collapsed) {
this.shade_node(state, ctx);
} else {
@@ -230,20 +230,20 @@ export class DepthOverlay extends GenericSdfgOverlay {
visible_rect.y, visible_rect.w, visible_rect.h))
return;
- if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
- this.shade_node(node, ctx);
- } else {
- if (node instanceof NestedSDFG &&
- node.attributes().sdfg &&
- node.attributes().sdfg.type !== 'SDFGShell') {
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if ((ctx as any).lod && nodeppp < SDFV.STATE_LOD) {
+ this.shade_node(node, ctx);
+ }
+ else if (node.attributes().sdfg && node.attributes().sdfg.type !== 'SDFGShell') {
this.recursively_shade_sdfg(
node.data.graph, ctx, ppp, visible_rect
);
- } else {
- this.shade_node(node, ctx);
}
}
+ else {
+ this.shade_node(node, ctx);
+ }
});
}
}
diff --git a/src/overlays/logical_group_overlay.ts b/src/overlays/logical_group_overlay.ts
index 90badcc9..6acc5064 100644
--- a/src/overlays/logical_group_overlay.ts
+++ b/src/overlays/logical_group_overlay.ts
@@ -88,8 +88,13 @@ export class LogicalGroupOverlay extends GenericSdfgOverlay {
// In that case, we overlay the correct grouping color(s).
// If it's expanded or zoomed in close enough, we traverse inside.
const sdfgGroups = sdfg.attributes.logical_groups;
- if (sdfgGroups === undefined)
+ if (sdfgGroups === undefined || sdfgGroups.length === 0) {
return;
+ }
+
+ if (!graph) {
+ return;
+ }
graph?.nodes().forEach(v => {
const block = graph.node(v);
@@ -101,8 +106,8 @@ export class LogicalGroupOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- block.width / ppp <= SDFV.STATE_LOD)) ||
+ const blockppp = Math.sqrt(block.width * block.height) / ppp;
+ if (((ctx as any).lod && (blockppp < SDFV.STATE_LOD)) ||
block.attributes().is_collapsed
) {
this.shadeNode(block, sdfgGroups, ctx);
@@ -121,7 +126,7 @@ export class LogicalGroupOverlay extends GenericSdfgOverlay {
return;
if (node.attributes().is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
+ ((ctx as any).lod && ppp > SDFV.NODE_LOD)) {
this.shadeNode(node, sdfgGroups, ctx);
} else {
if (node instanceof NestedSDFG &&
diff --git a/src/overlays/memory_location_overlay.ts b/src/overlays/memory_location_overlay.ts
index 5cb2f23c..914ba3c0 100644
--- a/src/overlays/memory_location_overlay.ts
+++ b/src/overlays/memory_location_overlay.ts
@@ -243,10 +243,10 @@ export class MemoryLocationOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- block.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(block.width * block.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
block.attributes()?.is_collapsed) {
- // The block is collapsed or invisible, so we don't need to
+ // The state is collapsed or too small, so we don't need to
// traverse its insides.
return;
} else if (block instanceof State) {
@@ -267,7 +267,9 @@ export class MemoryLocationOverlay extends GenericSdfgOverlay {
node.data.graph, ctx, ppp, visibleRect
);
} else if (node instanceof AccessNode) {
- this.shadeNode(node, ctx);
+ if (!(ctx as any).lod || ppp < SDFV.NODE_LOD) {
+ this.shadeNode(node, ctx);
+ }
}
});
}
diff --git a/src/overlays/memory_volume_overlay.ts b/src/overlays/memory_volume_overlay.ts
index faf8e863..cebec124 100644
--- a/src/overlays/memory_volume_overlay.ts
+++ b/src/overlays/memory_volume_overlay.ts
@@ -173,13 +173,6 @@ export class MemoryVolumeOverlay extends GenericSdfgOverlay {
graph.nodes().forEach(v => {
const block: ControlFlowBlock = graph.node(v);
- // If we're zoomed out enough that the contents aren't visible, we
- // skip the state.
- if ((ctx as any).lod && (
- ppp >= SDFV.STATE_LOD || block.width / ppp < SDFV.STATE_LOD
- ))
- return;
-
// If the node's invisible, we skip it.
if ((ctx as any).lod && !block.intersect(
visibleRect.x, visibleRect.y,
@@ -187,6 +180,12 @@ export class MemoryVolumeOverlay extends GenericSdfgOverlay {
) || block.attributes()?.is_collapsed)
return;
+ // If we're zoomed out enough that the contents aren't visible, we
+ // skip the state.
+ const stateppp = Math.sqrt(block.width * block.height) / ppp;
+ if ((ctx as any).lod && (stateppp < SDFV.STATE_LOD))
+ return;
+
if (block instanceof State) {
const state_graph = block.data.graph;
if (state_graph) {
@@ -203,7 +202,7 @@ export class MemoryVolumeOverlay extends GenericSdfgOverlay {
// If we're zoomed out enough that the node's contents
// aren't visible or the node is collapsed, we skip it.
if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD))
+ ((ctx as any).lod && ppp > SDFV.NODE_LOD))
return;
if (node instanceof NestedSDFG &&
@@ -218,8 +217,12 @@ export class MemoryVolumeOverlay extends GenericSdfgOverlay {
state_graph.edges().forEach((e: any) => {
const edge: Edge = state_graph.edge(e);
- if ((ctx as any).lod && !edge.intersect(visibleRect.x,
- visibleRect.y, visibleRect.w, visibleRect.h))
+ // Skip if edge is invisible, or zoomed out far
+ if ((ctx as any).lod
+ && (!edge.intersect(visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h)
+ || ppp > SDFV.EDGE_LOD
+ )
+ )
return;
this.shadeEdge(edge, ctx);
diff --git a/src/overlays/operational_intensity_overlay.ts b/src/overlays/operational_intensity_overlay.ts
index 6cd7d4f3..a98b5422 100644
--- a/src/overlays/operational_intensity_overlay.ts
+++ b/src/overlays/operational_intensity_overlay.ts
@@ -258,8 +258,8 @@ export class OperationalIntensityOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- state.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(state.width * state.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
state.data.state.attributes.is_collapsed) {
this.shade_node(state, ctx);
} else {
@@ -273,20 +273,20 @@ export class OperationalIntensityOverlay extends GenericSdfgOverlay {
visible_rect.y, visible_rect.w, visible_rect.h))
return;
- if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
- this.shade_node(node, ctx);
- } else {
- if (node instanceof NestedSDFG &&
- node.attributes().sdfg &&
- node.attributes().sdfg.type !== 'SDFGShell') {
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if ((ctx as any).lod && nodeppp < SDFV.STATE_LOD) {
+ this.shade_node(node, ctx);
+ }
+ else if (node.attributes().sdfg && node.attributes().sdfg.type !== 'SDFGShell') {
this.recursively_shade_sdfg(
node.data.graph, ctx, ppp, visible_rect
);
- } else {
- this.shade_node(node, ctx);
}
}
+ else {
+ this.shade_node(node, ctx);
+ }
});
}
}
diff --git a/src/overlays/runtime_micro_seconds_overlay.ts b/src/overlays/runtime_micro_seconds_overlay.ts
index bc22790b..b68c911f 100644
--- a/src/overlays/runtime_micro_seconds_overlay.ts
+++ b/src/overlays/runtime_micro_seconds_overlay.ts
@@ -131,8 +131,8 @@ export class RuntimeMicroSecondsOverlay extends RuntimeReportOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- state.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(state.width * state.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
state.data.state.attributes.is_collapsed) {
this.shade_node(state, ctx);
} else {
@@ -146,20 +146,20 @@ export class RuntimeMicroSecondsOverlay extends RuntimeReportOverlay {
visible_rect.y, visible_rect.w, visible_rect.h))
return;
- if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
- this.shade_node(node, ctx);
- } else {
- if (node instanceof NestedSDFG &&
- node.attributes().sdfg &&
- node.attributes().sdfg.type !== 'SDFGShell') {
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if ((ctx as any).lod && nodeppp < SDFV.STATE_LOD) {
+ this.shade_node(node, ctx);
+ }
+ else if (node.attributes().sdfg && node.attributes().sdfg.type !== 'SDFGShell') {
this.recursively_shade_sdfg(
node.data.graph, ctx, ppp, visible_rect
);
- } else {
- this.shade_node(node, ctx);
}
}
+ else {
+ this.shade_node(node, ctx);
+ }
});
}
}
diff --git a/src/overlays/simulated_operational_intensity_overlay.ts b/src/overlays/simulated_operational_intensity_overlay.ts
index b73d4301..529c68ff 100644
--- a/src/overlays/simulated_operational_intensity_overlay.ts
+++ b/src/overlays/simulated_operational_intensity_overlay.ts
@@ -221,8 +221,8 @@ export class SimulatedOperationalIntensityOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- state.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(state.width * state.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
state.data.state.attributes.is_collapsed) {
this.shade_node(state, ctx);
} else {
@@ -236,20 +236,20 @@ export class SimulatedOperationalIntensityOverlay extends GenericSdfgOverlay {
visible_rect.y, visible_rect.w, visible_rect.h))
return;
- if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
- this.shade_node(node, ctx);
- } else {
- if (node instanceof NestedSDFG &&
- node.attributes().sdfg &&
- node.attributes().sdfg.type !== 'SDFGShell') {
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if ((ctx as any).lod && nodeppp < SDFV.STATE_LOD) {
+ this.shade_node(node, ctx);
+ }
+ else if (node.attributes().sdfg && node.attributes().sdfg.type !== 'SDFGShell') {
this.recursively_shade_sdfg(
node.data.graph, ctx, ppp, visible_rect
);
- } else {
- this.shade_node(node, ctx);
}
}
+ else {
+ this.shade_node(node, ctx);
+ }
});
}
}
diff --git a/src/overlays/static_flops_overlay.ts b/src/overlays/static_flops_overlay.ts
index b68326c8..98febc74 100644
--- a/src/overlays/static_flops_overlay.ts
+++ b/src/overlays/static_flops_overlay.ts
@@ -215,8 +215,8 @@ export class StaticFlopsOverlay extends GenericSdfgOverlay {
))
return;
- if (((ctx as any).lod && (ppp >= SDFV.STATE_LOD ||
- state.width / ppp <= SDFV.STATE_LOD)) ||
+ const stateppp = Math.sqrt(state.width * state.height) / ppp;
+ if (((ctx as any).lod && (stateppp < SDFV.STATE_LOD)) ||
state.data.state.attributes.is_collapsed) {
this.shade_node(state, ctx);
} else {
@@ -230,20 +230,20 @@ export class StaticFlopsOverlay extends GenericSdfgOverlay {
visible_rect.y, visible_rect.w, visible_rect.h))
return;
- if (node.data.node.attributes.is_collapsed ||
- ((ctx as any).lod && ppp >= SDFV.NODE_LOD)) {
- this.shade_node(node, ctx);
- } else {
- if (node instanceof NestedSDFG &&
- node.attributes().sdfg &&
- node.attributes().sdfg.type !== 'SDFGShell') {
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if ((ctx as any).lod && nodeppp < SDFV.STATE_LOD) {
+ this.shade_node(node, ctx);
+ }
+ else if (node.attributes().sdfg && node.attributes().sdfg.type !== 'SDFGShell') {
this.recursively_shade_sdfg(
node.data.graph, ctx, ppp, visible_rect
);
- } else {
- this.shade_node(node, ctx);
}
}
+ else {
+ this.shade_node(node, ctx);
+ }
});
}
}
diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts
index 0f023bf7..1c4ff3a8 100644
--- a/src/renderer/renderer.ts
+++ b/src/renderer/renderer.ts
@@ -51,6 +51,7 @@ import {
ControlFlowBlock,
ControlFlowRegion,
Edge, EntryNode, InterstateEdge, LoopRegion, Memlet, NestedSDFG,
+ ScopeNode,
SDFG,
SDFGElement,
SDFGElementType,
@@ -262,9 +263,6 @@ export class SDFGRenderer extends EventEmitter {
this.overlay_manager = new OverlayManager(this);
- // Register overlays that are turned on by default.
- this.overlay_manager.register_overlay(LogicalGroupOverlay);
-
this.in_vscode = false;
try {
vscode;
@@ -596,7 +594,7 @@ export class SDFGRenderer extends EventEmitter {
}).appendTo(this.toolbar).append(overlayDropdown);
const addOverlayToMenu = (
- txt: string, ol: typeof GenericSdfgOverlay
+ txt: string, ol: typeof GenericSdfgOverlay, default_state: boolean
) => {
const olItem = $('
', {
css: {
@@ -609,6 +607,7 @@ export class SDFGRenderer extends EventEmitter {
const olInput = $('', {
class: 'form-check-input',
type: 'checkbox',
+ checked: default_state,
change: () => {
if (olInput.prop('checked'))
this.overlay_manager?.register_overlay(ol);
@@ -622,11 +621,13 @@ export class SDFGRenderer extends EventEmitter {
}).appendTo(olContainer);
};
- addOverlayToMenu('Logical groups', LogicalGroupOverlay);
- addOverlayToMenu('Storage locations', MemoryLocationOverlay);
- addOverlayToMenu(
- 'Logical data movement volume', MemoryVolumeOverlay
- );
+ // Register overlays that are turned on by default.
+ this.overlay_manager.register_overlay(LogicalGroupOverlay);
+ addOverlayToMenu('Logical groups', LogicalGroupOverlay, true);
+
+ // Add overlays that are turned off by default.
+ addOverlayToMenu('Storage locations', MemoryLocationOverlay, false);
+ addOverlayToMenu('Logical data movement volume', MemoryVolumeOverlay, false);
}
// Zoom to fit.
@@ -901,10 +902,12 @@ export class SDFGRenderer extends EventEmitter {
else
this.bgcolor = window.getComputedStyle(this.canvas).backgroundColor;
- // Create the initial SDFG layout
- // Loading animation already started in the file_read_complete function
- // in sdfv.ts to also include the JSON parsing step.
+
this.updateCFGList();
+
+ // Create the initial SDFG layout
+ // Loading animation already started in the file_read_complete function in sdfv.ts
+ // to also include the JSON parsing step.
this.relayout();
// Set mouse event handlers
@@ -2674,6 +2677,53 @@ export class SDFGRenderer extends EventEmitter {
return correctedMovement;
}
+ // Toggles collapsed state of foreground_elem if applicable.
+ // Returns true if re-layout occured and re-draw is necessary.
+ public toggle_element_collapse(foreground_elem: any): boolean {
+
+ const sdfg = (foreground_elem ? foreground_elem.sdfg : null);
+ let sdfg_elem = null;
+ if (foreground_elem instanceof State) {
+ sdfg_elem = foreground_elem.data.state;
+ } else if (foreground_elem instanceof ControlFlowRegion) {
+ sdfg_elem = foreground_elem.data.block;
+ } else if (foreground_elem instanceof SDFGNode) {
+ sdfg_elem = foreground_elem.data.node;
+
+ // If a scope exit node, use entry instead
+ if (sdfg_elem.type.endsWith('Exit') &&
+ foreground_elem.parent_id !== null) {
+ sdfg_elem = sdfg.nodes[foreground_elem.parent_id].nodes[
+ sdfg_elem.scope_entry
+ ];
+ }
+ } else {
+ sdfg_elem = null;
+ }
+
+ // Toggle collapsed state
+ if (foreground_elem.COLLAPSIBLE) {
+ if ('is_collapsed' in sdfg_elem.attributes) {
+ sdfg_elem.attributes.is_collapsed =
+ !sdfg_elem.attributes.is_collapsed;
+ } else {
+ sdfg_elem.attributes['is_collapsed'] = true;
+ }
+
+ this.emit('collapse_state_changed');
+
+ // Re-layout SDFG
+ this.add_loading_animation();
+ setTimeout(() => {
+ this.relayout();
+ }, 10);
+
+ return true;
+ }
+
+ return false;
+ }
+
// TODO(later): Improve event system using event types (instanceof) instead
// of passing string eventtypes.
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
@@ -2921,7 +2971,9 @@ export class SDFGRenderer extends EventEmitter {
return false;
}
} else if (evtype === 'wheel') {
- if (SDFVSettings.useVerticalScrollNavigation && !event.ctrlKey) {
+ if (SDFVSettings.useVerticalScrollNavigation && !event.ctrlKey
+ || !SDFVSettings.useVerticalScrollNavigation && event.ctrlKey
+ ) {
// If vertical scroll navigation is turned on, use this to
// move the viewport up and down. If the control key is held
// down while scrolling, treat it as a typical zoom operation.
@@ -3043,7 +3095,7 @@ export class SDFGRenderer extends EventEmitter {
highlighting_changed = true;
hover_changed = true;
}
-
+
// Highlight all edges of the memlet tree
if (obj instanceof Edge && obj.parent_id !== null) {
if (obj.hovered && hover_changed) {
@@ -3100,8 +3152,46 @@ export class SDFGRenderer extends EventEmitter {
}
if (obj instanceof Connector) {
- // Highlight all access nodes with the same name as
- // the hovered connector in the nested sdfg.
+
+ // Highlight the incoming/outgoing Edge
+ const parent_node = obj.linkedElem;
+ if (obj.hovered && (hover_changed || (!parent_node?.hovered))) {
+ const state = obj.linkedElem?.parentElem;
+ if (state && state instanceof State && state.data) {
+ const state_json = state.data.state;
+ const state_graph = state.data.graph;
+ state_json.edges.forEach((edge: JsonSDFGEdge, id: number) => {
+ if (edge.src_connector === obj.data.name || edge.dst_connector === obj.data.name) {
+ const gedge = state_graph.edge(edge.src, edge.dst, id.toString()) as Memlet;
+ if (gedge) {
+ gedge.highlighted = true;
+ }
+ }
+ });
+ }
+ }
+ if (!obj.hovered && hover_changed) {
+ // Prevent de-highlighting of edge if parent is already hovered (to show all edges)
+ if (parent_node && !parent_node.hovered) {
+ const state = obj.linkedElem?.parentElem;
+ if (state && state instanceof State && state.data) {
+ const state_json = state.data.state;
+ const state_graph = state.data.graph;
+ state_json.edges.forEach((edge: JsonSDFGEdge, id: number) => {
+ if (edge.src_connector === obj.data.name || edge.dst_connector === obj.data.name) {
+ const gedge = state_graph.edge(edge.src, edge.dst, id.toString()) as Memlet;
+ if (gedge) {
+ gedge.highlighted = false;
+ }
+ }
+ });
+ }
+ }
+ }
+
+
+ // Highlight all access nodes with the same name as the
+ // hovered connector in the nested sdfg
if (obj.hovered && hover_changed) {
const nGraph = obj.parentElem?.data.graph;
if (nGraph) {
@@ -3178,6 +3268,44 @@ export class SDFGRenderer extends EventEmitter {
}
}
}
+
+ // Make all edges of a node visible and remove the edge summary symbol
+ if (obj.hovered && hover_changed) {
+ // Setting these to false will cause the summary symbol
+ // not to be drawn in renderer_elements.ts
+ obj.summarize_in_edges = false;
+ obj.summarize_out_edges = false;
+ const state = obj.parentElem;
+ if (state && state instanceof State && state.data) {
+ const state_json = state.data.state;
+ const state_graph = state.data.graph;
+ state_json.edges.forEach((edge: JsonSDFGEdge, id: number) => {
+ if (edge.src === obj.id.toString() || edge.dst === obj.id.toString()) {
+ const gedge = state_graph.edge(edge.src, edge.dst, id.toString()) as Memlet;
+ if (gedge) {
+ gedge.highlighted = true;
+ }
+ }
+ });
+ }
+ }
+ else if (!obj.hovered && hover_changed) {
+ obj.summarize_in_edges = true;
+ obj.summarize_out_edges = true;
+ const state = obj.parentElem;
+ if (state && state instanceof State && state.data) {
+ const state_json = state.data.state;
+ const state_graph = state.data.graph;
+ state_json.edges.forEach((edge: JsonSDFGEdge, id: number) => {
+ if (edge.src === obj.id.toString() || edge.dst === obj.id.toString()) {
+ const gedge = state_graph.edge(edge.src, edge.dst, id.toString()) as Memlet;
+ if (gedge) {
+ gedge.highlighted = false;
+ }
+ }
+ });
+ }
+ }
}
);
@@ -3200,42 +3328,8 @@ export class SDFGRenderer extends EventEmitter {
}
if (evtype === 'dblclick') {
- const sdfg = (foreground_elem ? foreground_elem.sdfg : null);
- let sdfg_elem = null;
- if (foreground_elem instanceof State) {
- sdfg_elem = foreground_elem.data.state;
- } else if (foreground_elem instanceof ControlFlowRegion) {
- sdfg_elem = foreground_elem.data.block;
- } else if (foreground_elem instanceof SDFGNode) {
- sdfg_elem = foreground_elem.data.node;
-
- // If a scope exit node, use entry instead
- if (sdfg_elem.type.endsWith('Exit') &&
- foreground_elem.parent_id !== null) {
- sdfg_elem = sdfg.nodes[foreground_elem.parent_id].nodes[
- sdfg_elem.scope_entry
- ];
- }
- } else {
- sdfg_elem = null;
- }
-
- // Toggle collapsed state
- if (foreground_elem.COLLAPSIBLE) {
- if ('is_collapsed' in sdfg_elem.attributes) {
- sdfg_elem.attributes.is_collapsed =
- !sdfg_elem.attributes.is_collapsed;
- } else {
- sdfg_elem.attributes['is_collapsed'] = true;
- }
-
- this.emit('collapse_state_changed');
-
- // Re-layout SDFG
- this.add_loading_animation();
- setTimeout(() => {
- this.relayout();
- }, 10);
+ const relayout_happened = this.toggle_element_collapse(foreground_elem);
+ if (relayout_happened) {
dirty = true;
element_focus_changed = true;
}
@@ -3414,6 +3508,7 @@ export class SDFGRenderer extends EventEmitter {
el.selected = true;
});
+ // Handle right-clicks
if (evtype === 'contextmenu') {
if (this.mouse_mode === 'move') {
let elements_to_reset = [foreground_elem];
@@ -3509,6 +3604,17 @@ export class SDFGRenderer extends EventEmitter {
if (this.panmode_btn?.onclick)
this.panmode_btn?.onclick(event);
}
+ else if (this.mouse_mode === 'pan') {
+
+ // Shift + Rightclick to toggle expand/collapse
+ if (event.shiftKey) {
+ const relayout_happened = this.toggle_element_collapse(foreground_elem);
+ if (relayout_happened) {
+ dirty = true;
+ element_focus_changed = true;
+ }
+ }
+ }
}
const mouse_x = comp_x_func(event);
@@ -4113,10 +4219,11 @@ function relayoutSDFGState(
const g: DagreGraph = new dagre.graphlib.Graph({ multigraph: true });
// Set layout options and a simpler algorithm for large graphs.
- const layoutOptions: any = { ranksep: 30 };
+ const layoutOptions: any = { ranksep: SDFV.RANKSEP };
if (state.nodes.length >= 1000)
layoutOptions.ranker = 'longest-path';
+ layoutOptions.nodesep = SDFV.NODESEP;
g.setGraph(layoutOptions);
// Set an object for the graph label.
@@ -4367,7 +4474,7 @@ function relayoutSDFGState(
state.nodes.forEach((node: JsonSDFGNode, id: number) => {
const gnode: any = g.node(id.toString());
if (!gnode || (omitAccessNodes && gnode instanceof AccessNode)) {
- // Rgnore nodes that should not be drawn.
+ // Ignore nodes that should not be drawn.
return;
}
const topleft = gnode.topleft();
@@ -4409,6 +4516,104 @@ function relayoutSDFGState(
}
});
+
+ // Re-order in_connectors for the edges to not intertwine
+ state.nodes.forEach((node: JsonSDFGNode, id: number) => {
+ const gnode: any = g.node(id.toString());
+ if (!gnode || (omitAccessNodes && gnode instanceof AccessNode)) {
+ // Ignore nodes that should not be drawn.
+ return;
+ }
+
+ // Summarize edges for NestedSDFGs and ScopeNodes
+ if (gnode instanceof NestedSDFG || gnode instanceof ScopeNode) {
+ const n_of_in_connectors = gnode.in_connectors.length;
+ const n_of_out_connectors = gnode.out_connectors.length;
+
+ if (n_of_in_connectors > 10) {
+ gnode.summarize_in_edges = true;
+ gnode.in_summary_has_effect = true;
+ }
+ if (n_of_out_connectors > 10) {
+ gnode.summarize_out_edges = true;
+ gnode.out_summary_has_effect = true;
+ }
+ }
+
+ const SPACING = SDFV.LINEHEIGHT;
+ const iConnLength = (SDFV.LINEHEIGHT + SPACING) * Object.keys(
+ node.attributes.layout.in_connectors
+ ).length - SPACING;
+ let iConnX = gnode.x - iConnLength / 2.0 + SDFV.LINEHEIGHT / 2.0;
+
+ // Dictionary that saves the x coordinates of each connector's source node or source connector.
+ // This is later used to reorder the in_connectors based on the sources' x coordinates.
+ let sources_x_coordinates: { [key: string]: number } = {};
+
+ // For each in_connector, find the x coordinate of the source node connector
+ for (const c of gnode.in_connectors) {
+ state.edges.forEach((edge: JsonSDFGEdge, id: number) => {
+ if (edge.dst === gnode.id.toString() && edge.dst_connector === c.data.name) {
+
+ // If in-edges are to be summarized, set Memlet.summarized
+ const gedge = g.edge(edge.src, edge.dst, id.toString()) as Memlet;
+ if (gedge && gnode.summarize_in_edges) {
+ gedge.summarized = true;
+ }
+
+ const source_node: SDFGNode = g.node(edge.src);
+ if (source_node) {
+
+ // If source node doesn't have out_connectors, take
+ // the source node's own x coordinate
+ if (source_node.out_connectors.length === 0) {
+ sources_x_coordinates[c.data.name] = source_node.x;
+ }
+ else {
+ // Find the corresponding out_connector and take its x coordinate
+ for (let i = 0; i < source_node.out_connectors.length; ++i) {
+ if (source_node.out_connectors[i].data.name === edge.src_connector) {
+ sources_x_coordinates[c.data.name] = source_node.out_connectors[i].x;
+ break;
+ }
+ }
+ }
+ }
+ }
+ });
+
+ }
+
+ // Sort the dictionary by x coordinate values
+ let sources_x_coordinates_sorted = Object.entries(sources_x_coordinates);
+ sources_x_coordinates_sorted.sort((a, b) => a[1] - b[1]);
+
+ // In the order of the sorted source x coordinates, set the x coordinates of the in_connectors
+ for (const element of sources_x_coordinates_sorted) {
+ for (const c of gnode.in_connectors) {
+ if (c.data.name === element[0]) {
+ c.x = iConnX;
+ iConnX += SDFV.LINEHEIGHT + SPACING;
+ continue;
+ }
+ }
+ }
+
+ // For out_connectors set Memlet.summarized for all out-edges if needed
+ if (gnode.summarize_out_edges) {
+ for (const c of gnode.out_connectors) {
+ state.edges.forEach((edge: JsonSDFGEdge, id: number) => {
+ if (edge.src === gnode.id.toString() && edge.src_connector === c.data.name) {
+ const gedge = g.edge(edge.src, edge.dst, id.toString()) as Memlet;
+ if (gedge) {
+ gedge.summarized = true;
+ }
+ }
+ });
+ }
+ }
+ });
+
state.edges.forEach((edge: JsonSDFGEdge, id: number) => {
const nedge = check_and_redirect_edge(edge, drawnNodes, state);
if (!nedge)
diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts
index 77d0615a..1784eb44 100644
--- a/src/renderer/renderer_elements.ts
+++ b/src/renderer/renderer_elements.ts
@@ -59,6 +59,19 @@ export class SDFGElement {
public selected: boolean = false;
public highlighted: boolean = false;
public hovered: boolean = false;
+
+ // Used to draw edge summary instead of all edges separately.
+ // Helps with rendering performance when too many edges would be drawn on the screen.
+ // These two fields get set in the layouter, depending on the number of in/out_connectors
+ // of a node. They also get toggled in the mousehandler when the hover status changes.
+ // Currently only used for NestedSDFGs and ScopeNodes.
+ public summarize_in_edges: boolean = false;
+ public summarize_out_edges: boolean = false;
+ // Used in draw_edge_summary to decide if edge summary is applicable. Set in the layouter
+ // only for NestedSDFGs and ScopeNodes. This prevents the summary to get toggled on
+ // by the mousehandler when it is not applicable.
+ public in_summary_has_effect: boolean = false;
+ public out_summary_has_effect: boolean = false;
public x: number = 0;
public y: number = 0;
@@ -213,7 +226,133 @@ export class SDFGElement {
): string {
return renderer.getCssProperty(propertyName);
}
+
+ public draw_edge_summary(
+ renderer: SDFGRenderer, ctx: CanvasRenderingContext2D
+ ): void {
+
+ // Only draw if close enough
+ const canvas_manager = renderer.get_canvas_manager();
+ const ppp = canvas_manager?.points_per_pixel();
+ if (!(ctx as any).lod || (ppp && ppp < SDFV.EDGE_LOD)) {
+ const topleft = this.topleft();
+ ctx.strokeStyle = this.strokeStyle(renderer);
+ ctx.fillStyle = ctx.strokeStyle;
+
+ function draw_summary_symbol(ctx: CanvasRenderingContext2D,
+ min_connector_x: number, max_connector_x: number,
+ horizontal_line_level: number, draw_arrows_above_line: boolean
+ ): void {
+
+ // Draw horizontal line (looks better without)
+ // ctx.beginPath();
+ // ctx.moveTo(min_connector_x, horizontal_line_level);
+ // ctx.lineTo(max_connector_x, horizontal_line_level);
+ // ctx.closePath();
+ // ctx.stroke();
+
+ // Draw left arrow
+ const middle_of_line = (min_connector_x + max_connector_x) / 2;
+ const left_arrow_x = middle_of_line - 10;
+ const righ_arrow_x = middle_of_line + 10;
+ let arrow_start_y = horizontal_line_level + 2;
+ let arrow_end_y = horizontal_line_level + 8;
+ if (draw_arrows_above_line) {
+ arrow_start_y = horizontal_line_level - 10;
+ arrow_end_y = horizontal_line_level - 4;
+ }
+ const dot_height = (arrow_start_y + arrow_end_y) / 2;
+ // Arrow line
+ ctx.beginPath();
+ ctx.moveTo(left_arrow_x, arrow_start_y);
+ ctx.lineTo(left_arrow_x, arrow_end_y);
+ ctx.closePath();
+ ctx.stroke();
+ // Arrow head
+ ctx.beginPath();
+ ctx.moveTo(left_arrow_x, arrow_end_y + 2);
+ ctx.lineTo(left_arrow_x - 2, arrow_end_y);
+ ctx.lineTo(left_arrow_x + 2, arrow_end_y);
+ ctx.lineTo(left_arrow_x, arrow_end_y + 2);
+ ctx.closePath();
+ ctx.fill();
+
+ // 3 dots
+ ctx.beginPath();
+ ctx.moveTo(middle_of_line - 5, dot_height)
+ ctx.lineTo(middle_of_line - 4, dot_height)
+ ctx.closePath();
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(middle_of_line - 0.5, dot_height)
+ ctx.lineTo(middle_of_line + 0.5, dot_height)
+ ctx.closePath();
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(middle_of_line + 4, dot_height)
+ ctx.lineTo(middle_of_line + 5, dot_height)
+ ctx.closePath();
+ ctx.stroke();
+
+ // Draw right arrow
+ // Arrow line
+ ctx.beginPath();
+ ctx.moveTo(righ_arrow_x, arrow_start_y);
+ ctx.lineTo(righ_arrow_x, arrow_end_y);
+ ctx.closePath();
+ ctx.stroke();
+ // Arrow head
+ ctx.beginPath();
+ ctx.moveTo(righ_arrow_x, arrow_end_y + 2);
+ ctx.lineTo(righ_arrow_x - 2, arrow_end_y);
+ ctx.lineTo(righ_arrow_x + 2, arrow_end_y);
+ ctx.lineTo(righ_arrow_x, arrow_end_y + 2);
+ ctx.closePath();
+ ctx.fill();
+ }
+
+ if (this.summarize_in_edges && this.in_summary_has_effect) {
+ // Find the left most and right most connector coordinates
+ if (this.in_connectors.length > 0) {
+ let min_connector_x = Number.MAX_SAFE_INTEGER;
+ let max_connector_x = Number.MIN_SAFE_INTEGER;
+ this.in_connectors.forEach((c: Connector) => {
+ if (c.x < min_connector_x) {
+ min_connector_x = c.x;
+ }
+ if (c.x > max_connector_x) {
+ max_connector_x = c.x;
+ }
+ });
+ // Draw the summary symbol above the node
+ draw_summary_symbol(ctx,
+ min_connector_x, max_connector_x,
+ topleft.y - 8, true);
+ }
+ }
+ if (this.summarize_out_edges && this.out_summary_has_effect) {
+ // Find the left most and right most connector coordinates
+ if (this.out_connectors.length > 0) {
+ let min_connector_x = Number.MAX_SAFE_INTEGER;
+ let max_connector_x = Number.MIN_SAFE_INTEGER;
+ this.out_connectors.forEach((c: Connector) => {
+ if (c.x < min_connector_x) {
+ min_connector_x = c.x;
+ }
+ if (c.x > max_connector_x) {
+ max_connector_x = c.x;
+ }
+ });
+
+ // Draw the summary symbol below the node
+ draw_summary_symbol(ctx,
+ min_connector_x, max_connector_x,
+ topleft.y + this.height + 8, false);
+ }
+ }
+ }
+ }
}
// SDFG as an element (to support properties)
@@ -1115,7 +1254,11 @@ export abstract class Edge extends SDFGElement {
}
-export class Memlet extends Edge {
+export class Memlet extends Edge {
+
+ // Currently used for Memlets to decide if they need to be drawn or not.
+ // Set in the layouter.
+ public summarized: boolean = false;
public create_arrow_line(ctx: CanvasRenderingContext2D): void {
// Draw memlet edges with quadratic curves through the arrow points.
@@ -1385,7 +1528,7 @@ export class InterstateEdge extends Edge {
const ppp = renderer.get_canvas_manager()?.points_per_pixel();
if (ppp === undefined)
return;
- if ((ctx as any).lod && ppp >= SDFV.SCOPE_LOD)
+ if ((ctx as any).lod && ppp > SDFV.SCOPE_LOD)
return;
const labelLines = [];
@@ -1752,6 +1895,9 @@ export class ScopeNode extends SDFGNode {
renderer: SDFGRenderer, ctx: CanvasRenderingContext2D,
_mousepos?: Point2D
): void {
+
+ this.draw_edge_summary(renderer, ctx);
+
let draw_shape;
if (this.data.node.attributes.is_collapsed) {
draw_shape = () => {
@@ -2338,6 +2484,9 @@ export class NestedSDFG extends SDFGNode {
renderer: SDFGRenderer, ctx: CanvasRenderingContext2D,
mousepos?: Point2D
): void {
+
+ this.draw_edge_summary(renderer, ctx);
+
if (this.data.node.attributes.is_collapsed) {
const topleft = this.topleft();
drawOctagon(ctx, topleft, this.width, this.height);
@@ -2604,6 +2753,10 @@ function batchedDrawEdges(
deferredEdges.push(edge);
return;
}
+ // Dont draw if Memlet is summarized
+ else if (edge instanceof Memlet && edge.summarized) {
+ return;
+ }
const lPoint = edge.points[edge.points.length - 1];
if (visible_rect && lPoint.x >= visible_rect.x &&
@@ -2669,16 +2822,17 @@ export function drawStateContents(
))
continue;
- if (node instanceof NestedSDFG) {
- if (lod && (
- Math.sqrt(node.height * node.width) / ppp
- ) < SDFV.STATE_LOD) {
+ // Simple draw for non-collapsed NestedSDFGs
+ if (node instanceof NestedSDFG && !node.data.node.attributes.is_collapsed) {
+ const nodeppp = Math.sqrt(node.width * node.height) / ppp;
+ if (lod && nodeppp < SDFV.STATE_LOD) {
node.simple_draw(renderer, ctx, mousePos);
node.debug_draw(renderer, ctx);
// SDFGRenderer.rendered_elements_count++;
continue;
}
} else {
+ // Simple draw node
if (lod && ppp > SDFV.NODE_LOD) {
node.simple_draw(renderer, ctx, mousePos);
node.debug_draw(renderer, ctx);
@@ -2919,7 +3073,7 @@ export function drawAdaptiveText(
if (ppp === undefined)
return;
- const is_far: boolean = (ctx as any).lod && ppp >= ppp_thres;
+ const is_far: boolean = (ctx as any).lod && ppp > ppp_thres;
const label = is_far ? far_text : close_text;
let font_size = Math.min(
diff --git a/src/sdfv.ts b/src/sdfv.ts
index 89a0d4b2..7997fc85 100644
--- a/src/sdfv.ts
+++ b/src/sdfv.ts
@@ -60,13 +60,22 @@ export class SDFV {
public static NODE_LOD: number = 5.0; // 5.0
// Points-per-pixel threshold for not drawing node text.
public static TEXT_LOD: number = 1.5; // 1.5
- // Pixel threshold for not drawing state contents.
+
+ // Pixel threshold for not drawing State and NestedSDFG contents.
+ // This threshold behaves differently than the ones above. The State's size is compared to this
+ // threshold and if the State is smaller its contents are not drawn in the renderer.
public static STATE_LOD: number = 100; // 100
public static DEFAULT_CANVAS_FONTSIZE: number = 10;
- public static DEFAULT_MAX_FONTSIZE: number = 20; // 50
+ public static DEFAULT_MAX_FONTSIZE: number = 20; // 20
public static DEFAULT_FAR_FONT_MULTIPLIER: number = 16; // 16
+ // Dagre layout options.
+ // Separation between ranks (vertically) in pixels.
+ public static RANKSEP: number = 70; // Dagre default: 50
+ // Separation between nodes (horizontally) in pixels.
+ public static NODESEP: number = 20; // Dagre default: 50
+
protected renderer: SDFGRenderer | null = null;
protected localViewRenderer: LViewRenderer | null = null;
@@ -253,11 +262,10 @@ export class SDFV {
const contents = $(contentsRaw);
contents.html('');
- if (elem instanceof Memlet && elem.parent_id && elem.id) {
- const sdfg_edge = elem.parentElem?.data.state.edges[elem.id];
- contents.append($('', {
- html: 'Connectors: ' + sdfg_edge.src_connector + ' → ' +
- sdfg_edge.dst_connector,
+ if (elem instanceof Memlet) {
+ contents.append($('
', {
+ html: 'Connectors: ' + elem.src_connector + ' → ' +
+ elem.dst_connector,
}));
}
contents.append($('
'));
@@ -545,7 +553,7 @@ function file_read_complete(sdfv: SDFV): void {
new SDFGRenderer(sdfv, sdfg, container, mouse_event)
);
sdfv.close_menu();
- }, 20);
+ }, 10);
}
}