From 6988be12ccd8a0bdca686149bc1233ca0f8fd3ad Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Thu, 25 Jan 2024 08:59:28 +0100 Subject: [PATCH 01/30] Improve compatibility with hierarchical SDFGs --- src/overlays/memory_location_overlay.ts | 2 +- src/renderer/renderer_elements.ts | 6 +++--- src/sdfv.ts | 8 +++++--- src/utils/sdfg/traversal.ts | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/overlays/memory_location_overlay.ts b/src/overlays/memory_location_overlay.ts index 62f90ae6..7d492218 100644 --- a/src/overlays/memory_location_overlay.ts +++ b/src/overlays/memory_location_overlay.ts @@ -147,7 +147,7 @@ export class MemoryLocationOverlay extends GenericSdfgOverlay { if (node instanceof SDFGNode) { if (node.data?.node?.scope_entry !== undefined && node.parent_id !== null) { - scopeNode = node.sdfg.nodes[node.parent_id].nodes[ + scopeNode = node.parentElem?.data.state.nodes[ node.data.node.scope_entry ]; parentId = node.parent_id; diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index f84cc246..01387404 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -1685,7 +1685,7 @@ export class ScopeNode extends SDFGNode { public schedule_label(): string { let attrs = this.attributes(); if (this.scopeend() && this.parent_id !== null) { - const entry = this.sdfg.nodes[this.parent_id].nodes[ + const entry = this.parentElem?.data.state.nodes[ this.data.node.scope_entry ]; if (entry !== undefined) @@ -1715,7 +1715,7 @@ export class ScopeNode extends SDFGNode { let attrs = this.attributes(); if (this.scopeend() && this.parent_id !== null) { - const entry = this.sdfg.nodes[this.parent_id].nodes[ + const entry = this.parentElem?.data.state.nodes[ this.data.node.scope_entry ]; if (entry !== undefined) @@ -1753,7 +1753,7 @@ export class ScopeNode extends SDFGNode { let result = ''; if (this.scopeend() && this.parent_id !== null) { - const entry = this.sdfg.nodes[this.parent_id].nodes[ + const entry = this.parentElem?.data.state.nodes[ this.data.node.scope_entry ]; if (entry !== undefined) diff --git a/src/sdfv.ts b/src/sdfv.ts index 0bfd1947..893da1cd 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -180,7 +180,7 @@ export class SDFV { // If a scope has children, remove the name "Entry" from the type if (node.type().endsWith('Entry') && node.parent_id && node.id) { - const state = node.sdfg.nodes[node.parent_id]; + const state = node.parentElem?.data.state.nodes[node.parent_id]; if (state.scope_dict[node.id] !== undefined) { node_type = node_type.slice(0, -5); } @@ -194,7 +194,9 @@ export class SDFV { const nodes_to_display = [node]; if (node.type().endsWith('Entry') && node.parent_id && node.id) { - const state = node.sdfg.nodes[node.parent_id]; + const state = node.parentElem?.data.state.nodes[ + node.parent_id + ]; if (state.scope_dict[node.id] !== undefined) { for (const subnode_id of state.scope_dict[node.id]) nodes_to_display.push(parent.node(subnode_id)); @@ -241,7 +243,7 @@ export class SDFV { contents.html(''); if (elem instanceof Memlet && elem.parent_id && elem.id) { - const sdfg_edge = elem.sdfg.nodes[elem.parent_id].edges[elem.id]; + const sdfg_edge = elem.parentElem?.data.state.edges[elem.id]; contents.append($('

', { html: 'Connectors: ' + sdfg_edge.src_connector + ' → ' + sdfg_edge.dst_connector, diff --git a/src/utils/sdfg/traversal.ts b/src/utils/sdfg/traversal.ts index 0fe45d37..faa0ca70 100644 --- a/src/utils/sdfg/traversal.ts +++ b/src/utils/sdfg/traversal.ts @@ -48,7 +48,7 @@ import { // Traverse scopes recursively (if scope_dict provided). if (node.type().endsWith('Entry') && node.parent_id !== null && node.id !== null) { - const state = node.sdfg.nodes[node.parent_id]; + const state = node.parentElem?.data.state; if (state.scope_dict[node.id] !== undefined) scopesRecursive( graph, state.scope_dict[node.id], processedNodes From e7536c0828f2d1bc22c4d5dd4004929681e31608 Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Mon, 29 Jan 2024 15:03:04 +0100 Subject: [PATCH 02/30] Adapt to SDFG_list refactor --- samples/example.sdfg | 4 +-- src/index.ts | 4 +-- src/renderer/canvas_manager.ts | 10 +++---- src/renderer/renderer.ts | 52 +++++++++++++++++----------------- src/sdfv.ts | 6 ++-- src/utils/sdfg/sdfg_utils.ts | 14 ++++----- src/utils/utils.ts | 8 +++--- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/samples/example.sdfg b/samples/example.sdfg index 8f80a082..8d27e21a 100644 --- a/samples/example.sdfg +++ b/samples/example.sdfg @@ -584,7 +584,7 @@ } ], "edges": [], - "sdfg_list_id": 0, - "start_state": 0, + "cfg_list_id": 0, + "start_block": 0, "dace_version": "0.13.3" } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 0900d62c..e73cde06 100644 --- a/src/index.ts +++ b/src/index.ts @@ -86,8 +86,8 @@ export interface JsonSDFGState extends JsonSDFGBlock { export type JsonSDFG = { type: string, - start_state: number, - sdfg_list_id: number, + start_block: number, + cfg_list_id: number, attributes: any, edges: any[], // TODO nodes: any[], // TODO diff --git a/src/renderer/canvas_manager.ts b/src/renderer/canvas_manager.ts index b01e0b4d..93734fe3 100644 --- a/src/renderer/canvas_manager.ts +++ b/src/renderer/canvas_manager.ts @@ -7,7 +7,7 @@ import { getPositioningInfo, initialize_positioning_info, } from '../utils/sdfg/sdfg_utils'; -import { SDFGRenderer, SDFGListType } from './renderer'; +import { SDFGRenderer, CFGListType } from './renderer'; import { DagreSDFG, intersectRect, Point2D } from '../index'; const animation_duration = 1000; @@ -322,7 +322,7 @@ export class CanvasManager { * @param {*} old_mousepos Old mouse position in canvas coordinates * @param {*} new_mousepos New mouse position in canvas coordinates * @param {*} entire_graph Reference to the entire graph - * @param {*} sdfg_list List of SDFGs and nested SDFGs + * @param {*} cfg_list List of CFGs in this SDFG * @param {*} state_parent_list List of parent elements to SDFG states * @param {*} drag_start Drag starting event, undefined if no drag * @param {*} update_position_info Whether to update positioning information @@ -334,7 +334,7 @@ export class CanvasManager { old_mousepos: Point2D, new_mousepos: Point2D, entire_graph: DagreSDFG, - sdfg_list: SDFGListType, + cfg_list: CFGListType, state_parent_list: any[], drag_start: any, update_position_info: boolean = true, @@ -348,7 +348,7 @@ export class CanvasManager { const in_edges: any[] = []; // Find the parent graph in the list of available SDFGs - let parent_graph = sdfg_list[el.sdfg.sdfg_list_id]; + let parent_graph = cfg_list[el.sdfg.cfg_list_id]; let parent_element: SDFGElement | null = null; if ( @@ -359,7 +359,7 @@ export class CanvasManager { // we're currently in a nested SDFG. If we're also moving a state, // this means that its parent element is found in the list of // parents to states (state_parent_list) - parent_element = state_parent_list[el.sdfg.sdfg_list_id]; + parent_element = state_parent_list[el.sdfg.cfg_list_id]; } else if (el.parent_id !== null && parent_graph) { // If the parent_id isn't null and there is a parent graph, we can // look up the parent node via the element's parent_id diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index b078da02..cfb68594 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -71,7 +71,7 @@ declare const vscode: any | null; type SDFGElementGroup = 'states' | 'nodes' | 'edges' | 'isedges'; // If type is explicitly set, dagre typecheck fails with integer node ids -export type SDFGListType = any[];//{ [key: number]: DagreSDFG }; +export type CFGListType = any[];//{ [key: number]: DagreSDFG }; function check_valid_add_position( type: SDFGElementType | null, @@ -127,7 +127,7 @@ export interface SDFGRenderer { export class SDFGRenderer extends EventEmitter { - protected sdfg_list: any = {}; + protected cfg_list: any = {}; protected graph: DagreSDFG | null = null; // Parent-pointing SDFG tree. protected sdfg_tree: { [key: number]: number } = {}; @@ -993,8 +993,8 @@ export class SDFGRenderer extends EventEmitter { (otype: SDFGElementGroup, odict: any, obj: any) => { if (obj.type === SDFGElementType.NestedSDFG && obj.attributes.sdfg) - this.sdfg_tree[obj.attributes.sdfg.sdfg_list_id] = - odict.sdfg.sdfg_list_id; + this.sdfg_tree[obj.attributes.sdfg.cfg_list_id] = + odict.sdfg.cfg_list_id; } ); } @@ -1063,9 +1063,9 @@ export class SDFGRenderer extends EventEmitter { if (!this.ctx) throw new Error('No context found while performing layouting'); - this.sdfg_list = {}; + this.cfg_list = {}; this.graph = relayoutStateMachine( - this.ctx, this.sdfg, this.sdfg, this.sdfg_list, + this.ctx, this.sdfg, this.sdfg, this.cfg_list, this.state_parent_list, !SDFVSettings.showAccessNodes, undefined ); this.onresize(); @@ -1130,7 +1130,7 @@ export class SDFGRenderer extends EventEmitter { this.canvas_manager?.translate_element( node, { x: node.x, y: node.y }, { x: node.x + dx, y: node.y + dy }, this.graph, - this.sdfg_list, this.state_parent_list, undefined, false + this.cfg_list, this.state_parent_list, undefined, false ); } @@ -1163,7 +1163,7 @@ export class SDFGRenderer extends EventEmitter { if (this.graph) this.canvas_manager?.translate_element( edge, { x: 0, y: 0 }, - { x: 0, y: 0 }, this.graph, this.sdfg_list, + { x: 0, y: 0 }, this.graph, this.cfg_list, this.state_parent_list, undefined, false, false, final_pos_d ); @@ -1913,7 +1913,7 @@ export class SDFGRenderer extends EventEmitter { traverseRecursive( node.data.graph, node.attributes().sdfg.attributes.name, - node.attributes().sdfg.sdfg_list_id + node.attributes().sdfg.cfg_list_id ); } // Connectors @@ -1984,7 +1984,7 @@ export class SDFGRenderer extends EventEmitter { // Start with top-level SDFG. traverseRecursive( - this.graph, this.sdfg.attributes.name, this.sdfg.sdfg_list_id + this.graph, this.sdfg.attributes.name, this.sdfg.cfg_list_id ); } @@ -2322,7 +2322,7 @@ export class SDFGRenderer extends EventEmitter { // Do not move connectors (individually) if (el instanceof Connector) return false; - const list_id = el.sdfg.sdfg_list_id; + const list_id = el.sdfg.cfg_list_id; // Do not move element individually if it is // moved together with a nested SDFG @@ -2337,7 +2337,7 @@ export class SDFGRenderer extends EventEmitter { // Do not move element individually if it is // moved together with its parent state const state_parent = - this.sdfg_list[list_id].node( + this.cfg_list[list_id].node( el.parent_id!.toString() ); if (state_parent && @@ -2357,7 +2357,7 @@ export class SDFGRenderer extends EventEmitter { if (old_mousepos) this.canvas_manager?.translate_element( el, old_mousepos, this.mousepos, - this.graph, this.sdfg_list, + this.graph, this.cfg_list, this.state_parent_list, this.drag_start, true, @@ -2577,7 +2577,7 @@ export class SDFGRenderer extends EventEmitter { // nested sdfg if (intersected && obj instanceof AccessNode) { traverseSDFGScopes( - this.sdfg_list[obj.sdfg.sdfg_list_id], + this.cfg_list[obj.sdfg.cfg_list_id], (node: any) => { // If node is a state, then visit sub-scope if (node instanceof State) @@ -2894,7 +2894,7 @@ export class SDFGRenderer extends EventEmitter { // Move it to original position this.canvas_manager?.translate_element( edge_el, { x: 0, y: 0 }, { x: 0, y: 0 }, - this.graph, this.sdfg_list, + this.graph, this.cfg_list, this.state_parent_list, undefined, false, false, new_points ); @@ -2916,7 +2916,7 @@ export class SDFGRenderer extends EventEmitter { this.canvas_manager?.translate_element( el, { x: el.x, y: el.y }, { x: new_x, y: new_y }, this.graph, - this.sdfg_list, this.state_parent_list, + this.cfg_list, this.state_parent_list, undefined, false, false, undefined ); @@ -3159,15 +3159,15 @@ export class SDFGRenderer extends EventEmitter { */ // Collect nodes and states const sdfgs: Set = new Set(); - const sdfg_list: { [key: string]: JsonSDFG } = {}; + const cfg_list: { [key: string]: JsonSDFG } = {}; const states: { [key: string]: Array } = {}; const nodes: { [key: string]: Array } = {}; for (const elem of this.selected_elements) { // Ignore edges and connectors if (elem instanceof Edge || elem instanceof Connector) continue; - const sdfg_id = elem.sdfg.sdfg_list_id; - sdfg_list[sdfg_id] = elem.sdfg; + const sdfg_id = elem.sdfg.cfg_list_id; + cfg_list[sdfg_id] = elem.sdfg; sdfgs.add(sdfg_id); let state_id: number = -1; if (elem.parent_id !== null) { @@ -3203,20 +3203,20 @@ export class SDFGRenderer extends EventEmitter { // Find root SDFG and root state (if possible) const root_sdfg_id = find_root_sdfg(sdfgs, this.sdfg_tree); if (root_sdfg_id !== null) { - const root_sdfg = sdfg_list[root_sdfg_id]; + const root_sdfg = cfg_list[root_sdfg_id]; // For every participating state, filter out irrelevant nodes and // memlets. for (const nkey of Object.keys(nodes)) { const [sdfg_id, state_id] = JSON.parse(nkey); - const sdfg = sdfg_list[sdfg_id]; + const sdfg = cfg_list[sdfg_id]; delete_sdfg_nodes(sdfg, state_id, nodes[nkey], true); } // For every participating SDFG, filter out irrelevant states and // interstate edges. for (const sdfg_id of Object.keys(states)) { - const sdfg = sdfg_list[sdfg_id]; + const sdfg = cfg_list[sdfg_id]; delete_sdfg_states(sdfg, states[sdfg_id], true); } @@ -3301,7 +3301,7 @@ type StateMachineType = { function relayoutStateMachine( ctx: CanvasRenderingContext2D, stateMachine: StateMachineType, - sdfg: JsonSDFG, sdfgList: SDFGListType, stateParentList: any[], + sdfg: JsonSDFG, sdfgList: CFGListType, stateParentList: any[], omitAccessNodes: boolean, parent?: SDFGElement ): DagreSDFG { const BLOCK_MARGIN = 3 * SDFV.LINEHEIGHT; @@ -3408,7 +3408,7 @@ function relayoutStateMachine( // Fall back to dagre for anything that cannot be laid out with // the vertical layout (e.g., irreducible control flow). try { - SMLayouter.layoutDagreCompat(g, sdfg.start_state?.toString()); + SMLayouter.layoutDagreCompat(g, sdfg.start_block?.toString()); } catch (_ignored) { dagre.layout(g); } @@ -3481,7 +3481,7 @@ function relayoutStateMachine( (g as any).height = bb.height; // Add SDFG to global store. - sdfgList[sdfg.sdfg_list_id] = g; + sdfgList[sdfg.cfg_list_id] = g; return g; } @@ -3578,7 +3578,7 @@ function relayoutSDFGState( if ((node.type === SDFGElementType.NestedSDFG || node.type === SDFGElementType.ExternalNestedSDFG) && node.attributes.sdfg && node.attributes.sdfg.type !== 'SDFGShell') - stateParentList[node.attributes.sdfg.sdfg_list_id] = obj; + stateParentList[node.attributes.sdfg.cfg_list_id] = obj; // Add input connectors. let i = 0; diff --git a/src/sdfv.ts b/src/sdfv.ts index 893da1cd..ab8e9844 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -731,16 +731,16 @@ export function find_in_graph( } function recursive_find_graph( - graph: DagreSDFG, sdfg_id: number + graph: DagreSDFG, cfg_id: number ): DagreSDFG | undefined { let found = undefined; for (const n_id of graph.nodes()) { const n = graph.node(n_id); - if (n && n.sdfg.sdfg_list_id === sdfg_id) { + if (n && n.sdfg.cfg_list_id === cfg_id) { found = graph; return found; } else if (n && n.data.graph) { - found = recursive_find_graph(n.data.graph, sdfg_id); + found = recursive_find_graph(n.data.graph, cfg_id); if (found) return found; } diff --git a/src/utils/sdfg/sdfg_utils.ts b/src/utils/sdfg/sdfg_utils.ts index 319dea01..5e3d7876 100644 --- a/src/utils/sdfg/sdfg_utils.ts +++ b/src/utils/sdfg/sdfg_utils.ts @@ -20,7 +20,7 @@ export function recursively_find_graph( graph_id: number, ns_node: SDFGNode | undefined = undefined ): { graph: DagreSDFG | undefined, node: SDFGNode | undefined } { - if (graph.node('0').sdfg.sdfg_list_id === graph_id) { + if (graph.node('0').sdfg.cfg_list_id === graph_id) { return { graph: graph, node: ns_node, @@ -80,14 +80,14 @@ export function get_uuid_graph_element(element: SDFGElement | null): string { const undefined_val = -1; if (element instanceof State) { return ( - element.sdfg.sdfg_list_id + '/' + + element.sdfg.cfg_list_id + '/' + element.id + '/' + undefined_val + '/' + undefined_val ); } else if (element instanceof SDFGNode) { return ( - element.sdfg.sdfg_list_id + '/' + + element.sdfg.cfg_list_id + '/' + element.parent_id + '/' + element.id + '/' + undefined_val @@ -97,7 +97,7 @@ export function get_uuid_graph_element(element: SDFGElement | null): string { if (element.parent_id !== null && element.parent_id !== undefined) parent_id = element.parent_id; return ( - element.sdfg.sdfg_list_id + '/' + + element.sdfg.cfg_list_id + '/' + parent_id + '/' + undefined_val + '/' + element.id @@ -380,8 +380,8 @@ export function delete_sdfg_states( e.src = mapping[e.src]; e.dst = mapping[e.dst]; }); - if (mapping[sdfg.start_state] === '-1') - sdfg.start_state = 0; + if (mapping[sdfg.start_block] === '-1') + sdfg.start_block = 0; else - sdfg.start_state = parseInt(mapping[sdfg.start_state]); + sdfg.start_block = parseInt(mapping[sdfg.start_block]); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 7b10e9af..47bf5308 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -145,13 +145,13 @@ export function get_element_uuid(element: SDFGElement): string { const undefined_val = -1; if (element instanceof State) { return ( - element.sdfg.sdfg_list_id + '/' + + element.sdfg.cfg_list_id + '/' + element.id + '/' + undefined_val + '/' + undefined_val ); } else if (element instanceof NestedSDFG) { - const sdfg_id = element.data.node.attributes.sdfg.sdfg_list_id; + const sdfg_id = element.data.node.attributes.sdfg.cfg_list_id; return ( sdfg_id + '/' + undefined_val + '/' + @@ -162,14 +162,14 @@ export function get_element_uuid(element: SDFGElement): string { // For MapExit nodes, we want to get the uuid of the corresponding // entry node instead. return ( - element.sdfg.sdfg_list_id + '/' + + element.sdfg.cfg_list_id + '/' + element.parent_id + '/' + element.data.node.scope_entry + '/' + undefined_val ); } else if (element instanceof SDFGNode) { return ( - element.sdfg.sdfg_list_id + '/' + + element.sdfg.cfg_list_id + '/' + element.parent_id + '/' + element.id + '/' + undefined_val From 4fa299eae99e6babb694462befb05f98ce45ab84 Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Mon, 29 Jan 2024 15:05:11 +0100 Subject: [PATCH 03/30] Update eslint rule --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index ff2a5b7a..6b31f5c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { "argsIgnorePattern": "^_", }, ], + "@typescript-eslint/no-unsafe-declaration-merging": "warn", "@typescript-eslint/no-var-requires": "warn", "semi": "error", "no-useless-escape": "off", From ff420dc15b6ebb26d3249a94a5abcef8825bd83a Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Wed, 31 Jan 2024 11:52:50 +0100 Subject: [PATCH 04/30] Backwards compatbility --- package.json | 2 +- src/renderer/renderer.ts | 7 +++- src/sdfv.ts | 6 +-- src/utils/sdfg/json_serializer.ts | 66 ++++++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 9f62fec5..00e4101d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@spcl/sdfv", - "version": "1.1.5", + "version": "1.1.6", "description": "A standalone viewer for SDFGs", "homepage": "https://github.com/spcl/dace-webclient", "main": "out/index.js", diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index cfb68594..858b04c2 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -18,6 +18,7 @@ import { Point2D, SDFVTooltipFunc, SimpleRect, + checkCompatSave, stringify_sdfg } from '../index'; import { SMLayouter } from '../layouter/state_machine/sm_layouter'; @@ -1365,8 +1366,10 @@ export class SDFGRenderer extends EventEmitter { public save_sdfg(): void { const name = this.sdfg.attributes.name; - const contents = 'data:text/json;charset=utf-8,' + - encodeURIComponent(stringify_sdfg(this.sdfg)); + const sdfgString = stringify_sdfg(checkCompatSave(this.sdfg)); + const contents = 'data:text/json;charset=utf-8,' + encodeURIComponent( + sdfgString + ); this.save(name + '.sdfg', contents); } diff --git a/src/sdfv.ts b/src/sdfv.ts index ab8e9844..47fdbecd 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -29,7 +29,7 @@ import { State } from './renderer/renderer_elements'; import { htmlSanitize } from './utils/sanitization'; -import { parse_sdfg, stringify_sdfg } from './utils/sdfg/json_serializer'; +import { checkCompatLoad, parse_sdfg, stringify_sdfg } from './utils/sdfg/json_serializer'; import { SDFVSettings } from './utils/sdfv_settings'; declare const vscode: any; @@ -503,7 +503,7 @@ function file_read_complete(sdfv: SDFV): void { const result_string = fr.result; const container = document.getElementById('contents'); if (result_string && container) { - const sdfg = parse_sdfg(result_string); + const sdfg = checkCompatLoad(parse_sdfg(result_string)); sdfv.get_renderer()?.destroy(); sdfv.set_renderer(new SDFGRenderer(sdfv, sdfg, container, mouse_event)); sdfv.close_menu(); @@ -632,7 +632,7 @@ function load_sdfg_from_url(sdfv: SDFV, url: string): void { request.responseType = 'text'; // Will be parsed as JSON by parse_sdfg request.onload = () => { if (request.status === 200) { - const sdfg = parse_sdfg(request.response); + const sdfg = checkCompatLoad(parse_sdfg(request.response)); sdfv.get_renderer()?.destroy(); init_sdfv(sdfg, null, false, null); } else { diff --git a/src/utils/sdfg/json_serializer.ts b/src/utils/sdfg/json_serializer.ts index f6970e4b..46ea1efd 100644 --- a/src/utils/sdfg/json_serializer.ts +++ b/src/utils/sdfg/json_serializer.ts @@ -4,11 +4,75 @@ import { Edge, JsonSDFG } from '../../index'; import { gunzipSync } from 'zlib'; import { Buffer } from 'buffer'; +const propertyReplacements_0_16_0: { [key: string]: { + replaceWith: string, + recursive: boolean, +}} = { + 'start_state': { + replaceWith: 'start_block', + recursive: false, + }, + 'sdfg_list_id': { + replaceWith: 'cfg_list_id', + recursive: true, + }, +}; + +function propertyReplace(obj: any, fromName: string, toName: string): void { + if (Object.hasOwn(obj, fromName)) { + const prop = Object.getOwnPropertyDescriptor(obj, fromName)!; + Object.defineProperty(obj, toName, prop); + delete obj[fromName]; + } +} + +function makeCompat(sdfg: any, direction: 'in' | 'out'): void { + if (sdfg.dace_version && sdfg.dace_version < '0.16.0') { + for (const k in propertyReplacements_0_16_0) { + const v = propertyReplacements_0_16_0[k]; + if (v.recursive) { + const recurse = (el: { + nodes?: any[], + edges?: any[], + attributes?: { sdfg?: any }, + }) => { + if (direction === 'in') + propertyReplace(el, k, v.replaceWith); + else + propertyReplace(el, v.replaceWith, k); + el.nodes?.forEach(recurse); + el.edges?.forEach(recurse); + if (el.attributes?.sdfg) + recurse(el.attributes.sdfg); + }; + recurse(sdfg); + } else { + if (direction === 'in') + propertyReplace(sdfg, k, v.replaceWith); + else + propertyReplace(sdfg, v.replaceWith, k); + } + } + } +} + +export function checkCompatLoad(sdfg: JsonSDFG): JsonSDFG { + makeCompat(sdfg, 'in'); + return sdfg; +} + +export function checkCompatSave(sdfg: JsonSDFG): JsonSDFG { + makeCompat(sdfg, 'out'); + return sdfg; +} + export function read_or_decompress( json: string | ArrayBuffer ): [string, boolean] { try { - return [new TextDecoder().decode(gunzipSync(Buffer.from(json as Uint8Array))), true]; + return [new TextDecoder().decode( + gunzipSync(Buffer.from(json as Uint8Array)) + ), true]; } catch { if (typeof json !== 'string') { const enc = new TextDecoder('utf-8'); From 48c8fe65bc38b13208ad4f836ed3099b19ed2a89 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Mon, 26 Feb 2024 11:02:21 +0100 Subject: [PATCH 05/30] bind viewport to graph --- src/renderer/renderer.ts | 159 ++++++++++++++++++++++++++++++++------- 1 file changed, 132 insertions(+), 27 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index b078da02..67854361 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2263,6 +2263,55 @@ export class SDFGRenderer extends EventEmitter { return true; } + // Checks if pan mouse movement is in the bounds of the graph. + // Takes the current visible_rect as input and computes if the pan mouse movement (movX, movY) is + // allowed and additionally corrects it to have a smooth view pan blocking. + // Returns: corrected movement x/y coordinates to input into this.canvas_manager?.translate() + public pan_movement_in_bounds(visible_rect: SimpleRect, movX: number, movY: number) { + + const visible_rectCenter = { + x: (visible_rect.x + (visible_rect.w / 2)), + y: (visible_rect.y + (visible_rect.h / 2)) + } + + const graphLimits = { + minX: 0, + minY: 0, + maxX: (this.graph as any).width, + maxY: (this.graph as any).height, + }; + + let outofboundsX = 0; + let outofboundsY = 0; + + if (visible_rectCenter.x < graphLimits.minX) { + outofboundsX = -1; + } + else if (visible_rectCenter.x > graphLimits.maxX) { + outofboundsX = 1; + } + if (visible_rectCenter.y < graphLimits.minY) { + outofboundsY = -1; + } + else if (visible_rectCenter.y > graphLimits.maxY) { + outofboundsY = 1; + } + + let correctedMovement = { + x: movX, + y: movY + } + + if ((outofboundsX === -1 && correctedMovement.x > 0) || (outofboundsX === 1 && correctedMovement.x < 0)) { + correctedMovement.x = 0; + } + if ((outofboundsY === -1 && correctedMovement.y > 0) || (outofboundsY === 1 && correctedMovement.y < 0)) { + correctedMovement.y = 0; + } + + return correctedMovement; + } + // TODO(later): Improve event system using event types (instanceof) instead // of passing string eventtypes. /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ @@ -2392,20 +2441,41 @@ export class SDFGRenderer extends EventEmitter { // Mark for redraw dirty = true; } else { - this.canvas_manager?.translate( - event.movementX, event.movementY - ); + + // Mouse move in panning mode + if (this.visible_rect) { + + // Check if mouse panning is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, event.movementX, event.movementY + ); - // Mark for redraw - dirty = true; + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + + // Mark for redraw + dirty = true; + } + } } else if (this.drag_start && event.buttons & 4) { // Pan the view with the middle mouse button this.dragging = true; - this.canvas_manager?.translate( - event.movementX, event.movementY - ); - dirty = true; + if (this.visible_rect) { + + // Check if mouse panning is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, event.movementX, event.movementY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + dirty = true; + } element_focus_changed = true; } else { this.drag_start = null; @@ -2418,12 +2488,21 @@ export class SDFGRenderer extends EventEmitter { // Different number of touches, ignore and reset drag_start this.drag_start = event; } else if (event.touches.length === 1) { // Move/drag - this.canvas_manager?.translate( - event.touches[0].clientX - - this.drag_start.touches[0].clientX, - event.touches[0].clientY - - this.drag_start.touches[0].clientY - ); + if (this.visible_rect) { + + const movX = event.touches[0].clientX - this.drag_start.touches[0].clientX; + const movY = event.touches[0].clientY - this.drag_start.touches[0].clientY; + + // Check if panning is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, movX, movY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + } this.drag_start = event; // Mark for redraw @@ -2448,15 +2527,28 @@ export class SDFGRenderer extends EventEmitter { ); const newCenter = [(x1 + x2) / 2.0, (y1 + y2) / 2.0]; - // First, translate according to movement of center point - this.canvas_manager?.translate( - newCenter[0] - oldCenter[0], newCenter[1] - oldCenter[1] - ); - // Then scale - this.canvas_manager?.scale( - currentDistance / initialDistance, newCenter[0], - newCenter[1] - ); + if (this.visible_rect) { + + // First, translate according to movement of center point + const movX = newCenter[0] - oldCenter[0]; + const movY = newCenter[1] - oldCenter[1]; + + // Check if movement is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, movX, movY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + + // Then scale + this.canvas_manager?.scale( + currentDistance / initialDistance, newCenter[0], + newCenter[1] + ); + } this.drag_start = event; @@ -2470,9 +2562,22 @@ export class SDFGRenderer extends EventEmitter { // 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. - this.canvas_manager?.translate(0, -event.deltaY); - dirty = true; - element_focus_changed = true; + if (this.visible_rect) { + const movX = 0; + const movY = -event.deltaY; + + // Check if scroll is in bounds (near graph) + // and restrict/correct it + const correctedMovement = this.pan_movement_in_bounds( + this.visible_rect, movX, movY + ); + + this.canvas_manager?.translate( + correctedMovement.x, correctedMovement.y + ); + dirty = true; + element_focus_changed = true; + } } else { // Get physical x,y coordinates (rather than canvas coordinates) const br = this.canvas?.getBoundingClientRect(); From 01a2142587012198c9a17df4f96fbb9041388afe Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Wed, 28 Feb 2024 11:50:52 +0100 Subject: [PATCH 06/30] bind viewport to graph bounds && comments --- package-lock.json | 4 ++-- src/renderer/renderer.ts | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c48fcfd5..b88520dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@spcl/sdfv", - "version": "1.1.5", + "version": "1.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@spcl/sdfv", - "version": "1.1.5", + "version": "1.1.6", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 7d2ead9c..b8f86153 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2267,8 +2267,9 @@ export class SDFGRenderer extends EventEmitter { } // Checks if pan mouse movement is in the bounds of the graph. - // Takes the current visible_rect as input and computes if the pan mouse movement (movX, movY) is - // allowed and additionally corrects it to have a smooth view pan blocking. + // Takes the current visible_rect as input and computes if its center is + // within the graph bounds. The pan mouse movement (movX, movY) is + // corrected accordingly to have a smooth view pan blocking. // Returns: corrected movement x/y coordinates to input into this.canvas_manager?.translate() public pan_movement_in_bounds(visible_rect: SimpleRect, movX: number, movY: number) { @@ -2284,6 +2285,8 @@ export class SDFGRenderer extends EventEmitter { maxY: (this.graph as any).height, }; + // Compute where the visible_rectCenter is out of bounds: + // outofboundsX/Y === 0 means not out of bounds let outofboundsX = 0; let outofboundsY = 0; @@ -2300,15 +2303,19 @@ export class SDFGRenderer extends EventEmitter { outofboundsY = 1; } + // Take uncorrected mouse event movement as default let correctedMovement = { x: movX, y: movY } - if ((outofboundsX === -1 && correctedMovement.x > 0) || (outofboundsX === 1 && correctedMovement.x < 0)) { + // Correct mouse movement if necessary + if ((outofboundsX === -1 && correctedMovement.x > 0) || + (outofboundsX === 1 && correctedMovement.x < 0)) { correctedMovement.x = 0; } - if ((outofboundsY === -1 && correctedMovement.y > 0) || (outofboundsY === 1 && correctedMovement.y < 0)) { + if ((outofboundsY === -1 && correctedMovement.y > 0) || + (outofboundsY === 1 && correctedMovement.y < 0)) { correctedMovement.y = 0; } From e8c05423aa63608d94995a96591505894f7b83ef Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 12 Mar 2024 15:04:12 +0100 Subject: [PATCH 07/30] first LOD optimizations --- src/renderer/canvas_manager.ts | 2 + src/renderer/renderer_elements.ts | 275 +++++++++++++++++++----------- src/sdfv.ts | 16 +- 3 files changed, 190 insertions(+), 103 deletions(-) diff --git a/src/renderer/canvas_manager.ts b/src/renderer/canvas_manager.ts index 93734fe3..b19e3c8f 100644 --- a/src/renderer/canvas_manager.ts +++ b/src/renderer/canvas_manager.ts @@ -733,6 +733,8 @@ export class CanvasManager { // (right-left)/width should be equivalent const left = this.mapPixelToCoordsX(0); const right = this.mapPixelToCoordsX(this.canvas.width); + // console.log('coordinates width: %f', (right - left)); + // console.log('canvas pixel width: %f', this.canvas.width); return (right - left) / this.canvas.width; } diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 01387404..39870e13 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -292,10 +292,14 @@ export class ControlFlowRegion extends ControlFlowBlock { if (visibleRect && visibleRect.x <= topleft.x && visibleRect.y <= topleft.y + SDFV.LINEHEIGHT && SDFVSettings.showStateNames) - ctx.fillText( - this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, - topleft.y + SDFV.LINEHEIGHT - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillText( + this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, + topleft.y + SDFV.LINEHEIGHT + ); + } // If this state is selected or hovered if ((this.selected || this.highlighted || this.hovered) && @@ -427,8 +431,12 @@ export class State extends BasicBlock { if (visible_rect && visible_rect.x <= topleft.x && visible_rect.y <= topleft.y + SDFV.LINEHEIGHT && - SDFVSettings.showStateNames) - ctx.fillText(this.label(), topleft.x, topleft.y + SDFV.LINEHEIGHT); + SDFVSettings.showStateNames) { + + if (!too_far_away_for_text(renderer, ctx)) { + ctx.fillText(this.label(), topleft.x, topleft.y + SDFV.LINEHEIGHT); + } + } // If this state is selected or hovered if ((this.selected || this.highlighted || this.hovered) && @@ -617,22 +625,25 @@ export class LoopRegion extends ControlFlowRegion { ctx.lineTo(topleft.x + this.width, initBottomLineY); ctx.stroke(); - ctx.font = LoopRegion.LOOP_STATEMENT_FONT; - const initStatement = this.attributes().init_statement?.string_data; - const initTextY = ( - (topleft.y + (LoopRegion.INIT_SPACING / 2)) + - (SDFV.LINEHEIGHT / 2) - ); - if (initStatement) { - const initTextMetrics = ctx.measureText(initStatement); - const initTextX = this.x - (initTextMetrics.width / 2); - ctx.fillText(initStatement, initTextX, initTextY); - } + if (!too_far_away_for_text(renderer, ctx)) { - ctx.font = oldFont; - ctx.fillText( - 'init', topleft.x + LoopRegion.META_LABEL_MARGIN, initTextY - ); + ctx.font = LoopRegion.LOOP_STATEMENT_FONT; + const initStatement = this.attributes().init_statement?.string_data; + const initTextY = ( + (topleft.y + (LoopRegion.INIT_SPACING / 2)) + + (SDFV.LINEHEIGHT / 2) + ); + if (initStatement) { + const initTextMetrics = ctx.measureText(initStatement); + const initTextX = this.x - (initTextMetrics.width / 2); + ctx.fillText(initStatement, initTextX, initTextY); + } + + ctx.font = oldFont; + ctx.fillText( + 'init', topleft.x + LoopRegion.META_LABEL_MARGIN, initTextY + ); + } } // Draw the condition (either on top if the loop is a regularly @@ -654,20 +665,25 @@ export class LoopRegion extends ControlFlowRegion { ctx.moveTo(topleft.x, condLineY); ctx.lineTo(topleft.x + this.width, condLineY); ctx.stroke(); - ctx.font = LoopRegion.LOOP_STATEMENT_FONT; - const condStatement = this.attributes().loop_condition?.string_data; - const condTextY = ( - (condTopY + (LoopRegion.CONDITION_SPACING / 2)) + - (SDFV.LINEHEIGHT / 2) - ); - if (condStatement) { - 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 + LoopRegion.META_LABEL_MARGIN, condTextY + + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.font = LoopRegion.LOOP_STATEMENT_FONT; + const condStatement = this.attributes().loop_condition?.string_data; + const condTextY = ( + (condTopY + (LoopRegion.CONDITION_SPACING / 2)) + + (SDFV.LINEHEIGHT / 2) ); + if (condStatement) { + 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 + LoopRegion.META_LABEL_MARGIN, condTextY + ); + } } // Draw the update statement if there is one. @@ -681,21 +697,25 @@ export class LoopRegion extends ControlFlowRegion { ctx.lineTo(topleft.x + this.width, updateTopY); ctx.stroke(); - ctx.font = LoopRegion.LOOP_STATEMENT_FONT; - const updateStatement = - this.attributes().update_statement.string_data; - const updateTextY = ( - (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 + LoopRegion.META_LABEL_MARGIN, - updateTextY - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.font = LoopRegion.LOOP_STATEMENT_FONT; + const updateStatement = + this.attributes().update_statement.string_data; + const updateTextY = ( + (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 + LoopRegion.META_LABEL_MARGIN, + updateTextY + ); + } } remainingHeight -= topSpacing; @@ -704,10 +724,14 @@ export class LoopRegion extends ControlFlowRegion { if (visibleRect && visibleRect.x <= topleft.x && visibleRect.y <= topleft.y + SDFV.LINEHEIGHT && SDFVSettings.showStateNames) - ctx.fillText( - this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, - topleft.y + topSpacing + SDFV.LINEHEIGHT - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillText( + this.label(), topleft.x + LoopRegion.META_LABEL_MARGIN, + topleft.y + topSpacing + SDFV.LINEHEIGHT + ); + } // If this state is selected or hovered if ((this.selected || this.highlighted || this.hovered) && @@ -792,17 +816,21 @@ export class SDFGNode extends SDFGElement { ctx.strokeRect(clamped.x, clamped.y, clamped.w, clamped.h); } if (this.label()) { - ctx.fillStyle = this.getCssProperty(renderer, fgstyle); - const textw = ctx.measureText(this.label()).width; - if (!visible_rect) - ctx.fillText( - this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 - ); - else if (visible_rect && visible_rect.x <= topleft.x && - visible_rect.y <= topleft.y + SDFV.LINEHEIGHT) - ctx.fillText( - this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillStyle = this.getCssProperty(renderer, fgstyle); + const textw = ctx.measureText(this.label()).width; + if (!visible_rect) + ctx.fillText( + this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 + ); + else if (visible_rect && visible_rect.x <= topleft.x && + visible_rect.y <= topleft.y + SDFV.LINEHEIGHT) + ctx.fillText( + this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 + ); + } } } @@ -1015,6 +1043,9 @@ export class Memlet extends Edge { // Straight line can be drawn ctx.lineTo(this.points[1].x, this.points[1].y); } else { + + // ctx.lineTo(this.points[this.points.length-1].x, this.points[this.points.length-1].y); + let i; for (i = 1; i < this.points.length - 2; i++) { const xm = (this.points[i].x + this.points[i + 1].x) / 2.0; @@ -1523,6 +1554,12 @@ export class AccessNode extends SDFGNode { if (this.strokeStyle(renderer) !== this.getCssProperty(renderer, '--color-default')) renderer.set_tooltip((c) => this.tooltip(c)); } + + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + const textmetrics = ctx.measureText(this.label()); ctx.fillText( this.label(), this.x - textmetrics.width / 2.0, @@ -1637,6 +1674,11 @@ export class ScopeNode extends SDFGNode { renderer, '--node-foreground-color' ); + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + drawAdaptiveText( ctx, renderer, this.far_label(renderer), this.close_label(renderer), this.x, this.y, @@ -2063,6 +2105,11 @@ export class Tasklet extends SDFGNode { renderer, '--node-foreground-color' ); + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + const ppp = canvas_manager.points_per_pixel(); if (!(ctx as any).lod || ppp < SDFV.TASKLET_LOD) { // If we are close to the tasklet, show its contents @@ -2122,17 +2169,20 @@ export class Reduce extends SDFGNode { if ((ctx as any).pdf) draw_shape(); ctx.fill(); - ctx.fillStyle = this.getCssProperty( - renderer, '--node-foreground-color' - ); - - const far_label = this.label().substring(4, this.label().indexOf(',')); - drawAdaptiveText( - ctx, renderer, far_label, - this.label(), this.x, this.y - this.height * 0.2, - this.width, this.height, - SDFV.SCOPE_LOD - ); + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillStyle = this.getCssProperty( + renderer, '--node-foreground-color' + ); + const far_label = this.label().substring(4, this.label().indexOf(',')); + drawAdaptiveText( + ctx, renderer, far_label, + this.label(), this.x, this.y - this.height * 0.2, + this.width, this.height, + SDFV.SCOPE_LOD + ); + } } public shade( @@ -2189,17 +2239,22 @@ export class NestedSDFG extends SDFGNode { this.width - 5, this.height - 5 ); ctx.fill(); - ctx.fillStyle = this.getCssProperty( - renderer, '--node-foreground-color' - ); - let label = this.data.node.attributes.label; - if (!this.data.node.attributes.sdfg) - label += ' (not loaded)'; - const textmetrics = ctx.measureText(label); - ctx.fillText( - label, this.x - textmetrics.width / 2.0, - this.y + SDFV.LINEHEIGHT / 4.0 - ); + + + if (!too_far_away_for_text(renderer, ctx)) { + + ctx.fillStyle = this.getCssProperty( + renderer, '--node-foreground-color' + ); + let label = this.data.node.attributes.label; + if (!this.data.node.attributes.sdfg) + label += ' (not loaded)'; + const textmetrics = ctx.measureText(label); + ctx.fillText( + label, this.x - textmetrics.width / 2.0, + this.y + SDFV.LINEHEIGHT / 4.0 + ); + } } else { // Draw square around nested SDFG. super.draw( @@ -2213,16 +2268,19 @@ export class NestedSDFG extends SDFGNode { drawSDFG(renderer, ctx, this.data.graph, mousepos); } else { // Expanded, but no SDFG present or loaded yet. - const errColor = this.getCssProperty( - renderer, '--node-missing-background-color' - ); - const label = 'No SDFG loaded'; - const textmetrics = ctx.measureText(label); - ctx.fillStyle = errColor; - ctx.fillText( - label, this.x - textmetrics.width / 2.0, - this.y + SDFV.LINEHEIGHT / 4.0 - ); + if (!too_far_away_for_text(renderer, ctx)) { + + const errColor = this.getCssProperty( + renderer, '--node-missing-background-color' + ); + const label = 'No SDFG loaded'; + const textmetrics = ctx.measureText(label); + ctx.fillStyle = errColor; + ctx.fillText( + label, this.x - textmetrics.width / 2.0, + this.y + SDFV.LINEHEIGHT / 4.0 + ); + } } } } @@ -2325,6 +2383,12 @@ export class LibraryNode extends SDFGNode { ctx.fillStyle = this.getCssProperty( renderer, '--node-foreground-color' ); + + // If we are far away, don't show the text + if (too_far_away_for_text(renderer, ctx)) { + return; + } + const textw = ctx.measureText(this.label()).width; ctx.fillText( this.label(), this.x - textw / 2, this.y + SDFV.LINEHEIGHT / 4 @@ -2354,6 +2418,24 @@ export class LibraryNode extends SDFGNode { ////////////////////////////////////////////////////// +// Checks if graph is zoomed out far (defined by SDFV.TEXT_LOD), using Points-per-Pixel +// Used before ctx.fillText calls to only draw text when zoomed in close enough +function too_far_away_for_text(renderer: SDFGRenderer, ctx: CanvasRenderingContext2D): boolean { + + const canvas_manager = renderer.get_canvas_manager(); + const ppp = canvas_manager?.points_per_pixel(); + if (ppp) { + if ((!(ctx as any).lod || ppp > SDFV.TEXT_LOD)) { + return true; + } + else { + return false; + } + } + + return false; +} + /** * Batched drawing of graph edges, given a specific default color. * @@ -2525,6 +2607,7 @@ export function drawStateMachine( for (const nodeId of stateMachineGraph.nodes()) { const block = stateMachineGraph.node(nodeId); + // TODO: Move this after the invisible state check below? const blockppp = Math.max(block.width, block.height) / ppp; if (lod && blockppp < SDFV.STATE_LOD) { block.simple_draw(renderer, ctx, mousePos); diff --git a/src/sdfv.ts b/src/sdfv.ts index 47fdbecd..e134497a 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -42,19 +42,21 @@ export class SDFV { public static LINEHEIGHT: number = 10; // Points-per-pixel threshold for drawing tasklet contents. - public static TASKLET_LOD: number = 0.35; + public static TASKLET_LOD: number = 0.35; // 0.35 // Points-per-pixel threshold for simple version of map nodes (label only). - public static SCOPE_LOD: number = 1.5; + public static SCOPE_LOD: number = 0.75; // 0.75 // Points-per-pixel threshold for not drawing memlets/interstate edges. - public static EDGE_LOD: number = 8; + public static EDGE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing node shapes and labels. - public static NODE_LOD: number = 60.0; + 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. - public static STATE_LOD: number = 50; + public static STATE_LOD: number = 100; // 100 public static DEFAULT_CANVAS_FONTSIZE: number = 10; - public static DEFAULT_MAX_FONTSIZE: number = 50; - public static DEFAULT_FAR_FONT_MULTIPLIER: number = 16; + public static DEFAULT_MAX_FONTSIZE: number = 20; // 50 + public static DEFAULT_FAR_FONT_MULTIPLIER: number = 16; // 16 protected renderer: SDFGRenderer | null = null; protected localViewRenderer: LViewRenderer | null = null; From 443b284dec8eb5b4ff183065a3add3c225695559 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 12 Mar 2024 15:57:40 +0100 Subject: [PATCH 08/30] minimap optimizations --- src/renderer/renderer.ts | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index b8f86153..773bb36b 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -1535,13 +1535,20 @@ export class SDFGRenderer extends EventEmitter { let targetHeight = minDimSize; const maxPercentage = 0.22; if (targetHeight > this.canvas.height * maxPercentage) - targetHeight = this.canvas.height * maxPercentage; + targetHeight = Math.floor(this.canvas.height * maxPercentage); if (targetWidth > this.canvas.width * maxPercentage) - targetWidth = this.canvas.width * maxPercentage; - this.minimap_canvas.height = targetHeight; - this.minimap_canvas.width = targetWidth; - this.minimap_canvas.style.width = targetWidth.toString() + 'px'; - this.minimap_canvas.style.height = targetHeight.toString() + 'px'; + targetWidth = Math.floor(this.canvas.width * maxPercentage); + + // Prevent forced style reflow if nothing changed + // Can save about 0.5ms of computation + if (this.minimap_canvas.height !== targetHeight) { + this.minimap_canvas.height = targetHeight; + this.minimap_canvas.style.height = targetHeight.toString() + 'px'; + } + if (this.minimap_canvas.width !== targetWidth) { + this.minimap_canvas.width = targetWidth; + this.minimap_canvas.style.width = targetWidth.toString() + 'px'; + } // Set the zoom level and translation so everything is visible. const bb = { @@ -1566,11 +1573,17 @@ export class SDFGRenderer extends EventEmitter { if (n && this.minimap_ctx) n.simple_draw(this, this.minimap_ctx, undefined); }); - this.graph.edges().forEach(x => { - const e = this.graph?.edge(x); - if (e && this.minimap_ctx) - e.draw(this, this.minimap_ctx, undefined); - }); + + // Don't draw Interstate edges in the minimap: + // Small optimization thats can save ~1ms in computation + // The performance problem comes from the edges and their + // labels which are also drawn. + + // this.graph.edges().forEach(x => { + // const e = this.graph?.edge(x); + // if (e && this.minimap_ctx) + // e.draw(this, this.minimap_ctx, undefined); + // }); // Draw the viewport. if (this.visible_rect) { From cf6ded5d5d53c6b34c5841e7636bf367ddff7a3f Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 12 Mar 2024 16:20:28 +0100 Subject: [PATCH 09/30] quickfix simple_draw after invisible check --- src/renderer/renderer_elements.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 39870e13..e4fac997 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2607,7 +2607,13 @@ export function drawStateMachine( for (const nodeId of stateMachineGraph.nodes()) { const block = stateMachineGraph.node(nodeId); - // TODO: Move this after the invisible state check below? + // Skip invisible states. + if (lod && visibleRect && !block.intersect( + visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h + )) { + continue; + } + const blockppp = Math.max(block.width, block.height) / ppp; if (lod && blockppp < SDFV.STATE_LOD) { block.simple_draw(renderer, ctx, mousePos); @@ -2615,13 +2621,6 @@ export function drawStateMachine( continue; } - // Skip invisible states. - if (lod && visibleRect && !block.intersect( - visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h - )) { - continue; - } - block.draw(renderer, ctx, mousePos); block.debug_draw(renderer, ctx); From cc73e54b6a8cf7d1474c0dc89a678d0e61b536d4 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Wed, 13 Mar 2024 14:11:04 +0100 Subject: [PATCH 10/30] added connector LOD --- src/renderer/renderer_elements.ts | 45 +++++++++++++++++-------------- src/sdfv.ts | 2 ++ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index e4fac997..19f945f8 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2560,28 +2560,33 @@ export function drawStateContents( node.draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); - node.in_connectors.forEach((c: Connector) => { - let edge: Edge | null = null; - stateGraph.inEdges(nodeId)?.forEach((e) => { - const eobj = stateGraph.edge(e); - if (eobj.dst_connector == c.data.name) - edge = eobj as any; - }); - c.draw(renderer, ctx, mousePos, edge); - c.debug_draw(renderer, ctx); - }); - node.out_connectors.forEach((c: Connector) => { - let edge: Edge | null = null; - stateGraph.outEdges(nodeId)?.forEach((e) => { - const eobj = stateGraph.edge(e); - if (eobj.src_connector == c.data.name) - edge = eobj as any; - }); + // Only draw connectors when close enough to see them + if (!lod || ppp < SDFV.CONNECTOR_LOD) { - c.draw(renderer, ctx, mousePos, edge); - c.debug_draw(renderer, ctx); - }); + node.in_connectors.forEach((c: Connector) => { + let edge: Edge | null = null; + stateGraph.inEdges(nodeId)?.forEach((e) => { + const eobj = stateGraph.edge(e); + if (eobj.dst_connector == c.data.name) + edge = eobj as any; + }); + + c.draw(renderer, ctx, mousePos, edge); + c.debug_draw(renderer, ctx); + }); + node.out_connectors.forEach((c: Connector) => { + let edge: Edge | null = null; + stateGraph.outEdges(nodeId)?.forEach((e) => { + const eobj = stateGraph.edge(e); + if (eobj.src_connector == c.data.name) + edge = eobj as any; + }); + + c.draw(renderer, ctx, mousePos, edge); + c.debug_draw(renderer, ctx); + }); + } } if (lod && ppp > SDFV.EDGE_LOD) diff --git a/src/sdfv.ts b/src/sdfv.ts index e134497a..7bbb4ea6 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -47,6 +47,8 @@ export class SDFV { public static SCOPE_LOD: number = 0.75; // 0.75 // Points-per-pixel threshold for not drawing memlets/interstate edges. public static EDGE_LOD: number = 5.0; // 5.0 + // Points-per-pixel threshold for not drawing connectors + public static CONNECTOR_LOD = 2.0; // 5.0 // Points-per-pixel threshold for not drawing node shapes and labels. public static NODE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing node text From f330b934f06cd4190c35fd651094bc3ccd2c2e93 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Wed, 13 Mar 2024 16:36:36 +0100 Subject: [PATCH 11/30] first mousehandler optimizations (only highlight when close) --- src/renderer/renderer.ts | 188 +++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 86 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 773bb36b..516b1626 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2670,98 +2670,113 @@ export class SDFGRenderer extends EventEmitter { this.tooltip = null; - // De-highlight all elements. - this.doForVisibleElements( - (type: any, e: any, obj: any) => { - obj.hovered = false; - obj.highlighted = false; - if (obj instanceof Tasklet) { - for (const t of obj.inputTokens) - t.highlighted = false; - for (const t of obj.outputTokens) - t.highlighted = false; - } - } - ); - // Mark hovered and highlighted elements. - this.doForVisibleElements( - (type: any, e: any, obj: any) => { - const intersected = obj.intersect( - this.mousepos!.x, this.mousepos!.y, 0, 0 - ); - - // Highlight all edges of the memlet tree - if (intersected && obj instanceof Edge && - obj.parent_id !== null) { - const tree = this.get_nested_memlet_tree(obj); - tree.forEach(te => { - if (te !== obj && te !== undefined) { - te.highlighted = true; + // Only do highlighting re-computation if view is close enough to actually see the + // highlights. Done by points-per-pixel metric using SDFV.NODE_LOD as the threshold. + // Hence, the highlights only update/become visible if there are nodes visible to hover over. + // This creatly reduces CPU utilization when moving/hovering the mouse over large graphs. + if (this.canvas_manager) { + const ppp = this.canvas_manager.points_per_pixel(); + if (ppp < SDFV.NODE_LOD) { + + // De-highlight all elements. + this.doForVisibleElements( + (type: any, e: any, obj: any) => { + obj.hovered = false; + obj.highlighted = false; + if (obj instanceof Tasklet) { + for (const t of obj.inputTokens) + t.highlighted = false; + for (const t of obj.outputTokens) + t.highlighted = false; } - }); - } - - // Highlight all access nodes with the same name in the same - // nested sdfg - if (intersected && obj instanceof AccessNode) { - traverseSDFGScopes( - this.cfg_list[obj.sdfg.cfg_list_id], - (node: any) => { - // If node is a state, then visit sub-scope - if (node instanceof State) - return true; - if (node instanceof AccessNode && - node.data.node.label === obj.data.node.label) - node.highlighted = true; - // No need to visit sub-scope - return false; - } - ); - } - - if (intersected && obj instanceof Connector) { - // Highlight all access nodes with the same name as the - // hovered connector in the nested sdfg - if (e.graph) { - const nested_graph = - e.graph.node(obj.parent_id).data.graph; - if (nested_graph) { - traverseSDFGScopes(nested_graph, (node: any) => { - // If node is a state, then visit sub-scope - if (node instanceof State) { - return true; - } - if (node instanceof AccessNode && - node.data.node.label === obj.label()) { - node.highlighted = true; + } + ); + // Mark hovered and highlighted elements. + this.doForVisibleElements( + (type: any, e: any, obj: any) => { + const intersected = obj.intersect( + this.mousepos!.x, this.mousepos!.y, 0, 0 + ); + + // Highlight all edges of the memlet tree + if (intersected && obj instanceof Edge && + obj.parent_id !== null) { + const tree = this.get_nested_memlet_tree(obj); + tree.forEach(te => { + if (te !== obj && te !== undefined) { + te.highlighted = true; } - // No need to visit sub-scope - return false; }); } - } - - // Similarly, highlight any identifiers in a connector's - // tasklet, if applicable. - if (obj.linkedElem && obj.linkedElem instanceof Tasklet) { - if (obj.connectorType === 'in') { - for (const token of obj.linkedElem.inputTokens) { - if (token.token === obj.data.name) - token.highlighted = true; + + // Highlight all access nodes with the same name in the same + // nested sdfg + if (intersected && obj instanceof AccessNode) { + traverseSDFGScopes( + this.cfg_list[obj.sdfg.cfg_list_id], + (node: any) => { + // If node is a state, then visit sub-scope + if (node instanceof State) + return true; + if (node instanceof AccessNode && + node.data.node.label === obj.data.node.label) + node.highlighted = true; + // No need to visit sub-scope + return false; + } + ); + } + + if (intersected && obj instanceof Connector) { + // Highlight all access nodes with the same name as the + // hovered connector in the nested sdfg + if (e.graph) { + const nested_graph = + e.graph.node(obj.parent_id).data.graph; + if (nested_graph) { + traverseSDFGScopes(nested_graph, (node: any) => { + // If node is a state, then visit sub-scope + if (node instanceof State) { + return true; + } + if (node instanceof AccessNode && + node.data.node.label === obj.label()) { + node.highlighted = true; + } + // No need to visit sub-scope + return false; + }); + } } - } else { - for (const token of obj.linkedElem.outputTokens) { - if (token.token === obj.data.name) - token.highlighted = true; + + // Similarly, highlight any identifiers in a connector's + // tasklet, if applicable. + if (obj.linkedElem && obj.linkedElem instanceof Tasklet) { + if (obj.connectorType === 'in') { + for (const token of obj.linkedElem.inputTokens) { + if (token.token === obj.data.name) + token.highlighted = true; + } + } else { + for (const token of obj.linkedElem.outputTokens) { + if (token.token === obj.data.name) + token.highlighted = true; + } + } } } + + if (intersected) + obj.hovered = true; } - } + ); + + // TODO: only redraw when highlighting changed + dirty = true; - if (intersected) - obj.hovered = true; } - ); + } + // If adding an edge, mark/highlight the first/from element, if it has // already been selected. @@ -2772,10 +2787,11 @@ export class SDFGRenderer extends EventEmitter { this.add_edge_start_conn.highlighted = true; } - if (evtype === 'mousemove') { - // TODO: Draw only if elements have changed - dirty = true; - } + // TODO: check everything works if this is commented out + // if (evtype === 'mousemove') { + // // TODO: Draw only if elements have changed + // dirty = true; + // } if (evtype === 'dblclick') { const sdfg = (foreground_elem ? foreground_elem.sdfg : null); From cce8ee01c84f411b533c2cb8f6c948bc109a4f9f Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Thu, 14 Mar 2024 15:23:47 +0100 Subject: [PATCH 12/30] mousehandler highlighting optimization (only redraw on change) --- src/renderer/renderer.ts | 87 +++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 516b1626..d4d783c2 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -2678,19 +2678,7 @@ export class SDFGRenderer extends EventEmitter { const ppp = this.canvas_manager.points_per_pixel(); if (ppp < SDFV.NODE_LOD) { - // De-highlight all elements. - this.doForVisibleElements( - (type: any, e: any, obj: any) => { - obj.hovered = false; - obj.highlighted = false; - if (obj instanceof Tasklet) { - for (const t of obj.inputTokens) - t.highlighted = false; - for (const t of obj.outputTokens) - t.highlighted = false; - } - } - ); + let highlighting_changed = false; // Mark hovered and highlighted elements. this.doForVisibleElements( (type: any, e: any, obj: any) => { @@ -2699,19 +2687,26 @@ export class SDFGRenderer extends EventEmitter { ); // Highlight all edges of the memlet tree - if (intersected && obj instanceof Edge && - obj.parent_id !== null) { + if (obj instanceof Edge && obj.parent_id !== null) { const tree = this.get_nested_memlet_tree(obj); tree.forEach(te => { if (te !== obj && te !== undefined) { - te.highlighted = true; + if (intersected && te.highlighted === false) { + + te.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && te.highlighted === true) { + te.highlighted = false; + highlighting_changed = true; + } } }); } // Highlight all access nodes with the same name in the same // nested sdfg - if (intersected && obj instanceof AccessNode) { + if (obj instanceof AccessNode) { traverseSDFGScopes( this.cfg_list[obj.sdfg.cfg_list_id], (node: any) => { @@ -2720,14 +2715,21 @@ export class SDFGRenderer extends EventEmitter { return true; if (node instanceof AccessNode && node.data.node.label === obj.data.node.label) - node.highlighted = true; + if (intersected && node.highlighted === false) { + node.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && node.highlighted === true) { + node.highlighted = false; + highlighting_changed = true; + } // No need to visit sub-scope return false; } ); } - if (intersected && obj instanceof Connector) { + if (obj instanceof Connector) { // Highlight all access nodes with the same name as the // hovered connector in the nested sdfg if (e.graph) { @@ -2741,7 +2743,14 @@ export class SDFGRenderer extends EventEmitter { } if (node instanceof AccessNode && node.data.node.label === obj.label()) { - node.highlighted = true; + if (intersected && node.highlighted === false) { + node.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && node.highlighted === true) { + node.highlighted = false; + highlighting_changed = true; + } } // No need to visit sub-scope return false; @@ -2755,29 +2764,49 @@ export class SDFGRenderer extends EventEmitter { if (obj.connectorType === 'in') { for (const token of obj.linkedElem.inputTokens) { if (token.token === obj.data.name) - token.highlighted = true; + if (intersected && token.highlighted === false) { + token.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && token.highlighted === true) { + token.highlighted = false; + highlighting_changed = true; + } } } else { for (const token of obj.linkedElem.outputTokens) { if (token.token === obj.data.name) - token.highlighted = true; + if (intersected && token.highlighted === false) { + token.highlighted = true; + highlighting_changed = true; + } + else if (!intersected && token.highlighted === true) { + token.highlighted = false; + highlighting_changed = true; + } } } } } - if (intersected) + if (intersected && obj.hovered === false) { obj.hovered = true; + highlighting_changed = true; + } + else if (!intersected && obj.hovered === true) { + obj.hovered = false; + highlighting_changed = true; + } } ); - // TODO: only redraw when highlighting changed - dirty = true; + if (highlighting_changed) { + dirty = true; + } } } - // If adding an edge, mark/highlight the first/from element, if it has // already been selected. if (this.mouse_mode === 'add' && this.add_type === 'Edge') { @@ -2787,12 +2816,6 @@ export class SDFGRenderer extends EventEmitter { this.add_edge_start_conn.highlighted = true; } - // TODO: check everything works if this is commented out - // if (evtype === 'mousemove') { - // // TODO: Draw only if elements have changed - // dirty = true; - // } - if (evtype === 'dblclick') { const sdfg = (foreground_elem ? foreground_elem.sdfg : null); let sdfg_elem = null; From e3de51acad2a7acf3287285627c596dff301102b Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Thu, 14 Mar 2024 17:28:37 +0100 Subject: [PATCH 13/30] arrowhead LOD --- src/renderer/renderer_elements.ts | 16 +++++++++++----- src/sdfv.ts | 6 ++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 19f945f8..952884c2 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2507,11 +2507,17 @@ function batchedDrawEdges( ctx.fillStyle = ctx.strokeStyle = renderer.getCssProperty(color); ctx.stroke(); - arrowEdges.forEach(e => { - e.drawArrow( - ctx, e.points[e.points.length - 2], e.points[e.points.length - 1], 3 - ); - }); + // Only draw Arrowheads when close enough to see them + const canvas_manager = renderer.get_canvas_manager(); + const ppp = canvas_manager?.points_per_pixel(); + if (ppp && ppp < SDFV.ARROW_LOD) { + + arrowEdges.forEach(e => { + e.drawArrow( + ctx, e.points[e.points.length - 2], e.points[e.points.length - 1], 3 + ); + }); + } labelEdges.forEach(e => { (e as InterstateEdge).drawLabel(renderer, ctx); diff --git a/src/sdfv.ts b/src/sdfv.ts index 7bbb4ea6..e05bd386 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -47,11 +47,13 @@ export class SDFV { public static SCOPE_LOD: number = 0.75; // 0.75 // Points-per-pixel threshold for not drawing memlets/interstate edges. public static EDGE_LOD: number = 5.0; // 5.0 - // Points-per-pixel threshold for not drawing connectors + // Points-per-pixel threshold for not drawing Arrowheads of memlets/interstate edges. + public static ARROW_LOD: number = 2.0; // 5.0 + // Points-per-pixel threshold for not drawing connectors. public static CONNECTOR_LOD = 2.0; // 5.0 // Points-per-pixel threshold for not drawing node shapes and labels. public static NODE_LOD: number = 5.0; // 5.0 - // Points-per-pixel threshold for not drawing node text + // 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. public static STATE_LOD: number = 100; // 100 From 399772605dc5b8149ebbe21a23f01b4e66010814 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Fri, 15 Mar 2024 15:22:51 +0100 Subject: [PATCH 14/30] fixed connectors rendered when invisible --- src/renderer/renderer.ts | 6 +++++- src/renderer/renderer_elements.ts | 27 +++++++++++++++++++++++++++ src/sdfv.ts | 4 ++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index d4d783c2..3132b9d1 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -150,6 +150,7 @@ export class SDFGRenderer extends EventEmitter { protected bgcolor: string | null = null; protected visible_rect: SimpleRect | null = null; protected static cssProps: { [key: string]: string } = {}; + public static rendered_elements_count: number = 0; // Toolbar related fields. protected toolbar: JQuery | null = null; @@ -3140,8 +3141,11 @@ export class SDFGRenderer extends EventEmitter { dirty = dirty || ol_manager_dirty; } - if (dirty) + if (dirty) { + this.draw_async(); + // console.log(SDFGRenderer.rendered_elements_count) + } if (element_focus_changed || selection_changed) this.emit('selection_changed', multi_selection_changed); diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 952884c2..94e3e125 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -2502,6 +2502,7 @@ function batchedDrawEdges( labelEdges.push(edge); edge.create_arrow_line(ctx); + // SDFGRenderer.rendered_elements_count++; }); ctx.setLineDash([1, 0]); ctx.fillStyle = ctx.strokeStyle = renderer.getCssProperty(color); @@ -2516,15 +2517,18 @@ function batchedDrawEdges( e.drawArrow( ctx, e.points[e.points.length - 2], e.points[e.points.length - 1], 3 ); + // SDFGRenderer.rendered_elements_count++; }); } labelEdges.forEach(e => { (e as InterstateEdge).drawLabel(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; }); deferredEdges.forEach(e => { e.draw(renderer, ctx, mousepos); + // SDFGRenderer.rendered_elements_count++; }); if (renderer.debug_draw) { @@ -2554,23 +2558,35 @@ export function drawStateContents( ) < SDFV.STATE_LOD) { node.simple_draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; continue; } } else { if (lod && ppp > SDFV.NODE_LOD) { node.simple_draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; continue; } } node.draw(renderer, ctx, mousePos); node.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; // Only draw connectors when close enough to see them if (!lod || ppp < SDFV.CONNECTOR_LOD) { node.in_connectors.forEach((c: Connector) => { + + // Only draw connectors if actually visible. This is needed for large + // nodes in the background like NestedSDFGs, that are visible, but their + // connectors are actually not. + if (visibleRect && !c.intersect( + visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h)) { + return; + } + let edge: Edge | null = null; stateGraph.inEdges(nodeId)?.forEach((e) => { const eobj = stateGraph.edge(e); @@ -2580,8 +2596,15 @@ export function drawStateContents( c.draw(renderer, ctx, mousePos, edge); c.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; }); node.out_connectors.forEach((c: Connector) => { + + if (visibleRect && !c.intersect( + visibleRect.x, visibleRect.y, visibleRect.w, visibleRect.h)) { + return; + } + let edge: Edge | null = null; stateGraph.outEdges(nodeId)?.forEach((e) => { const eobj = stateGraph.edge(e); @@ -2591,6 +2614,7 @@ export function drawStateContents( c.draw(renderer, ctx, mousePos, edge); c.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; }); } } @@ -2629,11 +2653,13 @@ export function drawStateMachine( if (lod && blockppp < SDFV.STATE_LOD) { block.simple_draw(renderer, ctx, mousePos); block.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; continue; } block.draw(renderer, ctx, mousePos); block.debug_draw(renderer, ctx); + // SDFGRenderer.rendered_elements_count++; const ng = block.data.graph; if (!block.attributes().is_collapsed && ng) { @@ -2661,6 +2687,7 @@ export function drawSDFG( const ppp = cManager.points_per_pixel(); const visibleRect = renderer.get_visible_rect() ?? undefined; + SDFGRenderer.rendered_elements_count = 0; drawStateMachine( g, ctx, renderer, ppp, (ctx as any).lod, visibleRect, mousePos ); diff --git a/src/sdfv.ts b/src/sdfv.ts index e05bd386..eed5f25e 100644 --- a/src/sdfv.ts +++ b/src/sdfv.ts @@ -48,9 +48,9 @@ export class SDFV { // Points-per-pixel threshold for not drawing memlets/interstate edges. public static EDGE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing Arrowheads of memlets/interstate edges. - public static ARROW_LOD: number = 2.0; // 5.0 + public static ARROW_LOD: number = 2.0; // 2.0 // Points-per-pixel threshold for not drawing connectors. - public static CONNECTOR_LOD = 2.0; // 5.0 + public static CONNECTOR_LOD = 2.0; // 2.0 // Points-per-pixel threshold for not drawing node shapes and labels. public static NODE_LOD: number = 5.0; // 5.0 // Points-per-pixel threshold for not drawing node text. From 9c134bea61516d5cf11a81acf2c2d521820a7beb Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Mon, 18 Mar 2024 16:43:39 +0100 Subject: [PATCH 15/30] fixed edges rendered even if not visible --- src/renderer/renderer_elements.ts | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/renderer/renderer_elements.ts b/src/renderer/renderer_elements.ts index 94e3e125..6a2aa4a9 100644 --- a/src/renderer/renderer_elements.ts +++ b/src/renderer/renderer_elements.ts @@ -1015,8 +1015,9 @@ export abstract class Edge extends SDFGElement { x: number, y: number, w: number = 0, h: number = 0 ): boolean { // First, check bounding box - if (!super.intersect(x, y, w, h)) + if (!super.intersect(x, y, w, h)) { return false; + } // Then (if point), check distance from line if (w === 0 || h === 0) { @@ -1029,7 +1030,33 @@ export abstract class Edge extends SDFGElement { } return false; } - return true; + else { + // Its a rectangle. + // Check if the any of the rectangles, spanned by pairs of points of the line, + // intersect the input rectangle. + // This is needed for long Interstate edges that have a huge bounding box and + // intersect almost always with the viewport even if they are not visible. + // This is only an approximation to detect if a line is in the viewport and + // could be made more accurate at the cost of more computation. + for (let i = 0; i < this.points.length - 1; i++) { + const linepoint_0 = this.points[i]; + const linepoint_1 = this.points[i + 1]; + // Rectangle spanned by the two line points + const r = { + x: Math.min(linepoint_0.x, linepoint_1.x), + y: Math.min(linepoint_0.y, linepoint_1.y), + w: Math.abs(linepoint_1.x - linepoint_0.x), + h: Math.abs(linepoint_1.y - linepoint_0.y) + } + + // Check if the two rectangles intersect + if (r.x + r.w >= x && r.x <= x+w && + r.y + r.h >= y && r.y <= y+h) { + return true; + } + } + return false; + } } } @@ -2633,11 +2660,13 @@ export function drawStateMachine( renderer: SDFGRenderer, ppp: number, lod?: boolean, visibleRect?: SimpleRect, mousePos?: Point2D ): void { - if (!lod || ppp < SDFV.EDGE_LOD) + if (!lod || ppp < SDFV.EDGE_LOD) { + batchedDrawEdges( renderer, stateMachineGraph, ctx, visibleRect, mousePos, '--interstate-edge-color', SDFVSettings.alwaysOnISEdgeLabels ); + } for (const nodeId of stateMachineGraph.nodes()) { const block = stateMachineGraph.node(nodeId); From c5ba20aeccb2ec766b9854cd3ba2dd03d6b46ae1 Mon Sep 17 00:00:00 2001 From: LeandroTreu Date: Tue, 19 Mar 2024 19:33:55 +0100 Subject: [PATCH 16/30] added loading animation for relayout tasks --- index.html | 4 ++ scss/sdfv.scss | 14 ++++++ src/renderer/canvas_manager.ts | 5 +-- src/renderer/renderer.ts | 81 +++++++++++++++++++++++++++++----- src/sdfv.ts | 26 ++++++++--- 5 files changed, 112 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index 42009f02..e93e573a 100644 --- a/index.html +++ b/index.html @@ -101,6 +101,10 @@