Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

October Improvements 2/2 #119

Merged
merged 4 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@spcl/sdfv",
"version": "1.1.1",
"version": "1.1.2",
"description": "A standalone viewer for SDFGs",
"homepage": "https://github.com/spcl/dace-webclient",
"main": "out/index.js",
Expand Down
31 changes: 16 additions & 15 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ import { deepCopy, intersectRect, showErrorModal } from '../utils/utils';
import { CanvasManager } from './canvas_manager';
import {
AccessNode, Connector,
Edge, EntryNode, InterstateEdge, LoopScopeBlock, Memlet, NestedSDFG,
Edge, EntryNode, InterstateEdge, LoopRegion, Memlet, NestedSDFG,
SDFG,
SDFGElement,
SDFGElementType,
SDFGElements,
SDFGNode,
ScopeBlock,
ControlFlowRegion,
State,
Tasklet,
drawSDFG,
Expand Down Expand Up @@ -2651,7 +2651,7 @@ export class SDFGRenderer extends EventEmitter {
let sdfg_elem = null;
if (foreground_elem instanceof State)
sdfg_elem = foreground_elem.data.state;
else if (foreground_elem instanceof ScopeBlock)
else if (foreground_elem instanceof ControlFlowRegion)
sdfg_elem = foreground_elem.data.block;
else if (foreground_elem instanceof SDFGNode) {
sdfg_elem = foreground_elem.data.node;
Expand Down Expand Up @@ -3337,9 +3337,9 @@ function relayoutStateMachine(
let blockGraph = null;
if (block.attributes.is_collapsed) {
blockInfo.height = SDFV.LINEHEIGHT;
if (blockElem instanceof LoopScopeBlock) {
if (blockElem instanceof LoopRegion) {
const oldFont = ctx.font;
ctx.font = LoopScopeBlock.LOOP_STATEMENT_FONT;
ctx.font = LoopRegion.LOOP_STATEMENT_FONT;
const labelWidths = [
ctx.measureText(
(block.attributes.scope_condition?.string_data ?? '') +
Expand All @@ -3358,7 +3358,7 @@ function relayoutStateMachine(
ctx.font = oldFont;
blockInfo.width = Math.max(
maxLabelWidth, ctx.measureText(block.label).width
) + 3 * LoopScopeBlock.META_LABEL_MARGIN;
) + 3 * LoopRegion.META_LABEL_MARGIN;
} else if (blockElem instanceof State) {
blockInfo.width = ctx.measureText(blockInfo.label).width;
}
Expand All @@ -3373,16 +3373,16 @@ function relayoutStateMachine(
blockInfo.width += 2 * BLOCK_MARGIN;
blockInfo.height += 2 * BLOCK_MARGIN;

if (blockElem instanceof LoopScopeBlock) {
if (blockElem instanceof LoopRegion) {
// Add spacing for the condition if the loop is not inverted.
if (!block.attributes.inverted)
blockInfo.height += LoopScopeBlock.CONDITION_SPACING;
blockInfo.height += LoopRegion.CONDITION_SPACING;
// If there's an init statement, add space for it.
if (block.attributes.init_statement)
blockInfo.height += LoopScopeBlock.INIT_SPACING;
blockInfo.height += LoopRegion.INIT_SPACING;
// If there's an update statement, also add space for it.
if (block.attributes.update_statement)
blockInfo.height += LoopScopeBlock.UPDATE_SPACING;
blockInfo.height += LoopRegion.UPDATE_SPACING;
}

blockElem.data.layout = blockInfo;
Expand All @@ -3394,7 +3394,8 @@ function relayoutStateMachine(
for (let id = 0; id < stateMachine.edges.length; id++) {
const edge = stateMachine.edges[id];
g.setEdge(edge.src, edge.dst, new InterstateEdge(
edge.attributes.data, id, sdfg
edge.attributes.data, id, sdfg, parent.id, parent, edge.src,
edge.dst
));
}

Expand Down Expand Up @@ -3454,13 +3455,13 @@ function relayoutStateMachine(
// Base spacing for the inside.
let topSpacing = BLOCK_MARGIN;

if (gBlock instanceof LoopScopeBlock) {
if (gBlock instanceof LoopRegion) {
// Add spacing for the condition if the loop isn't inverted.
if (!block.attributes.inverted)
topSpacing += LoopScopeBlock.CONDITION_SPACING;
topSpacing += LoopRegion.CONDITION_SPACING;
// If there's an init statement, add space for it.
if (block.attributes.init_statement)
topSpacing += LoopScopeBlock.INIT_SPACING;
topSpacing += LoopRegion.INIT_SPACING;
}
offset_sdfg(block as any, gBlock.data.graph, {
x: topleft.x + BLOCK_MARGIN,
Expand Down Expand Up @@ -3861,7 +3862,7 @@ function relayoutSDFGBlock(
omitAccessNodes: boolean, parent: SDFGElement
): DagreSDFG | null {
switch (block.type) {
case SDFGElementType.LoopScopeBlock:
case SDFGElementType.LoopRegion:
return relayoutStateMachine(
ctx, block as StateMachineType, sdfg, sdfgList, stateParentList,
omitAccessNodes, parent
Expand Down
148 changes: 109 additions & 39 deletions src/renderer/renderer_elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export enum SDFGElementType {
Reduce = 'Reduce',
BasicBlock = 'BasicBlock',
ControlFlowBlock = 'ControlFlowBlock',
ScopeBlock = 'ScopeBlock',
LoopScopeBlock = 'LoopScopeBlock',
ControlFlowRegion = 'ControlFlowRegion',
LoopRegion = 'LoopRegion',
}

export class SDFGElement {
Expand Down Expand Up @@ -232,7 +232,7 @@ export class ControlFlowBlock extends SDFGElement {
export class BasicBlock extends SDFGElement {
}

export class ScopeBlock extends ControlFlowBlock {
export class ControlFlowRegion extends ControlFlowBlock {
}

export class State extends BasicBlock {
Expand Down Expand Up @@ -385,7 +385,7 @@ export class State extends BasicBlock {

}

export class LoopScopeBlock extends ScopeBlock {
export class LoopRegion extends ControlFlowRegion {

public static readonly META_LABEL_MARGIN: number = 5;

Expand Down Expand Up @@ -458,22 +458,22 @@ export class LoopScopeBlock extends ScopeBlock {
);

const oldFont = ctx.font;
let topSpacing = LoopScopeBlock.META_LABEL_MARGIN;
let topSpacing = LoopRegion.META_LABEL_MARGIN;
let remainingHeight = this.height;

// Draw the init statement if there is one.
if (this.attributes().init_statement) {
topSpacing += LoopScopeBlock.INIT_SPACING;
const initBottomLineY = topleft.y + LoopScopeBlock.INIT_SPACING;
topSpacing += LoopRegion.INIT_SPACING;
const initBottomLineY = topleft.y + LoopRegion.INIT_SPACING;
ctx.beginPath();
ctx.moveTo(topleft.x, initBottomLineY);
ctx.lineTo(topleft.x + this.width, initBottomLineY);
ctx.stroke();

ctx.font = LoopScopeBlock.LOOP_STATEMENT_FONT;
ctx.font = LoopRegion.LOOP_STATEMENT_FONT;
const initStatement = this.attributes().init_statement.string_data;
const initTextY = (
(topleft.y + (LoopScopeBlock.INIT_SPACING / 2)) +
(topleft.y + (LoopRegion.INIT_SPACING / 2)) +
(SDFV.LINEHEIGHT / 2)
);
const initTextMetrics = ctx.measureText(initStatement);
Expand All @@ -482,7 +482,7 @@ export class LoopScopeBlock extends ScopeBlock {

ctx.font = oldFont;
ctx.fillText(
'init', topleft.x + LoopScopeBlock.META_LABEL_MARGIN, initTextY
'init', topleft.x + LoopRegion.META_LABEL_MARGIN, initTextY
);
}

Expand All @@ -491,58 +491,58 @@ export class LoopScopeBlock extends ScopeBlock {
// (do-while-style) loop). If the condition is drawn on top, make sure
// the init statement spacing is respected if there is one.
let condTopY = topleft.y;
let condLineY = condTopY + LoopScopeBlock.CONDITION_SPACING;
let condLineY = condTopY + LoopRegion.CONDITION_SPACING;
if (this.attributes().inverted) {
condTopY = topleft.y +
(this.height - LoopScopeBlock.CONDITION_SPACING);
condLineY = condTopY - LoopScopeBlock.CONDITION_SPACING;
(this.height - LoopRegion.CONDITION_SPACING);
condLineY = condTopY - LoopRegion.CONDITION_SPACING;
} else if (this.attributes().init_statement) {
condTopY += LoopScopeBlock.INIT_SPACING;
condLineY = condTopY + LoopScopeBlock.CONDITION_SPACING;
condTopY += LoopRegion.INIT_SPACING;
condLineY = condTopY + LoopRegion.CONDITION_SPACING;
}
topSpacing += LoopScopeBlock.CONDITION_SPACING;
topSpacing += LoopRegion.CONDITION_SPACING;
ctx.beginPath();
ctx.moveTo(topleft.x, condLineY);
ctx.lineTo(topleft.x + this.width, condLineY);
ctx.stroke();
ctx.font = LoopScopeBlock.LOOP_STATEMENT_FONT;
const condStatement = this.attributes().scope_condition.string_data;
ctx.font = LoopRegion.LOOP_STATEMENT_FONT;
const condStatement = this.attributes().loop_condition.string_data;
const condTextY = (
(condTopY + (LoopScopeBlock.CONDITION_SPACING / 2)) +
(condTopY + (LoopRegion.CONDITION_SPACING / 2)) +
(SDFV.LINEHEIGHT / 2)
);
const condTextMetrics = ctx.measureText(condStatement);
const condTextX = this.x - (condTextMetrics.width / 2);
ctx.fillText(condStatement, condTextX, condTextY);
ctx.font = oldFont;
ctx.fillText(
'while', topleft.x + LoopScopeBlock.META_LABEL_MARGIN, condTextY
'while', topleft.x + LoopRegion.META_LABEL_MARGIN, condTextY
);

// Draw the update statement if there is one.
if (this.attributes().update_statement) {
remainingHeight -= LoopScopeBlock.UPDATE_SPACING;
remainingHeight -= LoopRegion.UPDATE_SPACING;
const updateTopY = topleft.y + (
this.height - LoopScopeBlock.UPDATE_SPACING
this.height - LoopRegion.UPDATE_SPACING
);
ctx.beginPath();
ctx.moveTo(topleft.x, updateTopY);
ctx.lineTo(topleft.x + this.width, updateTopY);
ctx.stroke();

ctx.font = LoopScopeBlock.LOOP_STATEMENT_FONT;
ctx.font = LoopRegion.LOOP_STATEMENT_FONT;
const updateStatement =
this.attributes().update_statement.string_data;
const updateTextY = (
(updateTopY + (LoopScopeBlock.UPDATE_SPACING / 2)) +
(updateTopY + (LoopRegion.UPDATE_SPACING / 2)) +
(SDFV.LINEHEIGHT / 2)
);
const updateTextMetrics = ctx.measureText(updateStatement);
const updateTextX = this.x - (updateTextMetrics.width / 2);
ctx.fillText(updateStatement, updateTextX, updateTextY);
ctx.font = oldFont;
ctx.fillText(
'update', topleft.x + LoopScopeBlock.META_LABEL_MARGIN,
'update', topleft.x + LoopRegion.META_LABEL_MARGIN,
updateTextY
);
}
Expand All @@ -554,7 +554,7 @@ export class LoopScopeBlock extends ScopeBlock {
visibleRect.y <= topleft.y + SDFV.LINEHEIGHT &&
SDFVSettings.showStateNames)
ctx.fillText(
this.label(), topleft.x + LoopScopeBlock.META_LABEL_MARGIN,
this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN,
topleft.y + topSpacing + SDFV.LINEHEIGHT
);

Expand Down Expand Up @@ -1031,6 +1031,19 @@ export class Memlet extends Edge {

export class InterstateEdge extends Edge {

// Parent ID is the state ID, if relevant
public constructor(
data: any,
id: number,
sdfg: JsonSDFG,
parent_id: number | null = null,
parentElem?: SDFGElement,
public readonly src?: string,
public readonly dst?: string,
) {
super(data, id, sdfg, parent_id, parentElem);
}

public create_arrow_line(ctx: CanvasRenderingContext2D): void {
// Draw intersate edges with bezier curves through the arrow points.
ctx.moveTo(this.points[0].x, this.points[0].y);
Expand Down Expand Up @@ -1116,12 +1129,11 @@ export class InterstateEdge extends Edge {
this.points[this.points.length - 1], 3
);

if (SDFVSettings.alwaysOnISEdgeLabels) {
if (SDFVSettings.alwaysOnISEdgeLabels)
this.drawLabel(renderer, ctx);
} else {
if (this.hovered)
renderer.set_tooltip((c) => this.tooltip(c, renderer));
}

if (this.hovered)
renderer.set_tooltip((c) => this.tooltip(c, renderer));
}

public tooltip(container: HTMLElement, renderer?: SDFGRenderer): void {
Expand All @@ -1147,16 +1159,74 @@ export class InterstateEdge extends Edge {
);
const oldFont = ctx.font;
ctx.font = '8px sans-serif';
const labelMetrics = ctx.measureText(this.label());
let label = '';
if (this.attributes().assignments) {
for (const k of Object.keys(this.attributes().assignments))
label += k + ' 🡐 ' + this.attributes().assignments[k] + '\n';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it may not be supported everywhere (encoding dependent). Is there a way of adding a \u code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed it by adding proper multi-line support (i.e., printing individual labels for each line). Thank you for pointing this out - the method I chose is only supported by very few browsers and does not follow the fillText canvas API specifications.

}
const cond = this.attributes().condition.string_data;
if (cond && cond !== '1' && cond !== 'true')
label += cond;
const labelMetrics = ctx.measureText(label);
const labelW = Math.abs(labelMetrics.actualBoundingBoxLeft) +
Math.abs(labelMetrics.actualBoundingBoxRight);
const labelH = Math.abs(labelMetrics.actualBoundingBoxDescent) +
Math.abs(labelMetrics.actualBoundingBoxAscent);
const offsetX = this.points[0].x > this.points[1].x ? -(labelW + 5) : 5;
const offsetY = this.points[0].y > this.points[1].y ? -5 : (labelH + 5);
ctx.fillText(
this.label(), this.points[0].x + offsetX, this.points[0].y + offsetY
);

// The label is positioned at the origin of the interstate edge, offset
// so that it does not intersect the edge or the state it originates
// from. There are a few cases to consider:
// 1. The edge exits from the top/bottom of a node. Then the label is
// placed right next to the source point, offset up/down by
// LABEL_PADDING pixels to not intersect with the state. If the edge
// moves to the right/left, place the label to the left/right of the
// edge to avoid intersection.
// 2. The edge exits from the side of a node. Then the label is placed
// next to the source point, offset up/down by LABEL_PADDING pixels
// depending on whether the edge direction is down/up, so it does not
// intersect with the edge. To avoid intersecting with the node, the
// label is also offset LABEL_PADDING pixels to the left/right,
// depending on whether the edge exits to the left/right of the node.
const LABEL_PADDING = 3;
const srcP = this.points[0];
const srcNode = this.src !== undefined ?
renderer.get_graph()?.node(this.src) : null;
// Initial offsets are good for edges coming out of a node's top border.
let offsetX = LABEL_PADDING;
let offsetY = -LABEL_PADDING;
if (srcNode) {
const stl = srcNode.topleft();
if (Math.abs(srcP.y - (stl.y + srcNode.height)) < 1) {
// Edge exits the bottom of a node.
offsetY = LABEL_PADDING + labelH;
// If the edge moves right, offset the label to the left.
if (this.points[1].x > srcP.x)
offsetX = -(LABEL_PADDING + labelW);
} else if (Math.abs(srcP.x - stl.x) < 1) {
// Edge exits to the left of a node.
offsetX = -(LABEL_PADDING + labelW);
// If the edge moves down, offset the label upwards.
if (this.points[1].y <= srcP.y)
offsetY = LABEL_PADDING + labelH;
} else if (Math.abs(srcP.x - (stl.x + srcNode.width)) < 1) {
// Edge exits to the right of a node.
// If the edge moves down, offset the label upwards.
if (this.points[1].y <= srcP.y)
offsetY = LABEL_PADDING + labelH;
} else {
// Edge exits the top of a node.
// If the edge moves right, offset the label to the left.
if (this.points[1].x > srcP.x)
offsetX = -(LABEL_PADDING + labelW);
}
} else {
// Failsafe offset calculation if no source node is present.
if (this.points[0].x > this.points[1].x)
offsetX = -(labelW + LABEL_PADDING);
if (this.points[0].y <= this.points[1].y)
offsetY = labelH + LABEL_PADDING;
}
ctx.fillText(label, srcP.x + offsetX, srcP.y + offsetY);
ctx.font = oldFont;
}

Expand Down Expand Up @@ -2661,6 +2731,6 @@ export const SDFGElements: { [name: string]: typeof SDFGElement } = {
ControlFlowBlock,
BasicBlock,
State,
ScopeBlock,
LoopScopeBlock,
ControlFlowRegion: ControlFlowRegion,
LoopRegion: LoopRegion,
};
2 changes: 1 addition & 1 deletion src/utils/sdfg/sdfg_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export class SDFGStateParser {
switch (x.type) {
case SDFGElementType.SDFGState:
case SDFGElementType.BasicBlock:
case SDFGElementType.LoopScopeBlock:
case SDFGElementType.LoopRegion:
return new SDFGStateParser(x as JsonSDFGBlock);
default:
return new SDFGNodeParser(x as JsonSDFGNode);
Expand Down