[turbolizer] Turboshaft view initial commit
General: - Graph view refactoring Turboshaft: - Blocks representation - Inline nodes representation - Minimum required turboshaft toolbox actions - Layout caching Bug: v8:7327 Change-Id: I2ac07965ac775c68c522cfc9367b7ce0ff18672a Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3726287 Reviewed-by: Nico Hartmann <nicohartmann@chromium.org> Commit-Queue: Danylo Boiko <danielboyko02@gmail.com> Cr-Commit-Position: refs/heads/main@{#81553}
This commit is contained in:
parent
6639962a32
commit
f61d1afec6
44
tools/turbolizer/css/turboshaft.css
Normal file
44
tools/turbolizer/css/turboshaft.css
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
g.turboshaft-block rect {
|
||||||
|
stroke-dasharray: 20;
|
||||||
|
stroke-width: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.turboshaft-block:hover rect {
|
||||||
|
stroke-dasharray: 0;
|
||||||
|
stroke-width: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.block rect {
|
||||||
|
fill: #ecf3fe;
|
||||||
|
stroke: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.merge rect {
|
||||||
|
fill: #e9fcee;
|
||||||
|
stroke: #2bde5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.loop rect {
|
||||||
|
fill: #fdecea;
|
||||||
|
stroke: #e94235;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-label tspan {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block .block-label tspan {
|
||||||
|
fill: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge .block-label tspan {
|
||||||
|
fill: #34a853;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loop .block-label tspan {
|
||||||
|
fill: #ea4335;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-node-properties tspan {
|
||||||
|
fill: #9227b0;
|
||||||
|
}
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B |
BIN
tools/turbolizer/img/toolbox/toggle-properties-icon.png
Normal file
BIN
tools/turbolizer/img/toolbox/toggle-properties-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 499 B |
@ -10,6 +10,7 @@ code is governed by a BSD-style license that can be found in the LICENSE file.
|
|||||||
<link rel="stylesheet" href="css/turbo-visualizer.css">
|
<link rel="stylesheet" href="css/turbo-visualizer.css">
|
||||||
<link rel="stylesheet" href="css/turbo-visualizer-ranges.css">
|
<link rel="stylesheet" href="css/turbo-visualizer-ranges.css">
|
||||||
<link rel="stylesheet" href="css/tabs.css">
|
<link rel="stylesheet" href="css/tabs.css">
|
||||||
|
<link rel="stylesheet" href="css/turboshaft.css">
|
||||||
<link rel="icon" href="turbolizer.png">
|
<link rel="icon" href="turbolizer.png">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
import { GraphNode } from "./phases/graph-phase/graph-node";
|
import { GraphNode } from "./phases/graph-phase/graph-node";
|
||||||
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
|
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
|
||||||
|
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||||||
|
|
||||||
export abstract class Edge<NodeType extends GraphNode | TurboshaftGraphNode> {
|
export abstract class Edge<NodeType extends GraphNode | TurboshaftGraphNode
|
||||||
|
| TurboshaftGraphBlock> {
|
||||||
target: NodeType;
|
target: NodeType;
|
||||||
source: NodeType;
|
source: NodeType;
|
||||||
backEdgeNumber: number;
|
backEdgeNumber: number;
|
||||||
|
@ -36,7 +36,7 @@ export class GraphLayout {
|
|||||||
this.graph.graphPhase.rendered = true;
|
this.graph.graphPhase.rendered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public fullRebuild(showTypes: boolean): void {
|
private fullRebuild(showTypes: boolean): void {
|
||||||
this.startTime = performance.now();
|
this.startTime = performance.now();
|
||||||
this.maxRank = 0;
|
this.maxRank = 0;
|
||||||
this.visitOrderWithinRank = 0;
|
this.visitOrderWithinRank = 0;
|
||||||
@ -56,7 +56,7 @@ export class GraphLayout {
|
|||||||
this.graph.graphPhase.stateType = GraphStateType.Cached;
|
this.graph.graphPhase.stateType = GraphStateType.Cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
public cachedRebuild(): void {
|
private cachedRebuild(): void {
|
||||||
this.calculateBackEdgeNumbers();
|
this.calculateBackEdgeNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ export class GraphLayout {
|
|||||||
const rankSets = new Array<Array<GraphNode>>();
|
const rankSets = new Array<Array<GraphNode>>();
|
||||||
for (const node of this.graph.nodes()) {
|
for (const node of this.graph.nodes()) {
|
||||||
node.y = node.rank * (C.DEFAULT_NODE_ROW_SEPARATION +
|
node.y = node.rank * (C.DEFAULT_NODE_ROW_SEPARATION +
|
||||||
node.getNodeHeight(showTypes) + 2 * C.DEFAULT_NODE_BUBBLE_RADIUS);
|
node.getHeight(showTypes) + 2 * C.DEFAULT_NODE_BUBBLE_RADIUS);
|
||||||
if (node.visible) {
|
if (node.visible) {
|
||||||
if (!rankSets[node.rank]) {
|
if (!rankSets[node.rank]) {
|
||||||
rankSets[node.rank] = new Array<GraphNode>(node);
|
rankSets[node.rank] = new Array<GraphNode>(node);
|
||||||
@ -204,8 +204,7 @@ export class GraphLayout {
|
|||||||
for (const node of rankSet) {
|
for (const node of rankSet) {
|
||||||
if (node.visible) {
|
if (node.visible) {
|
||||||
node.x = this.graphOccupation.occupyNode(node);
|
node.x = this.graphOccupation.occupyNode(node);
|
||||||
const nodeTotalWidth = node.getTotalNodeWidth();
|
this.trace(`Node ${node.id} is placed between [${node.x}, ${node.x + node.getWidth()})`);
|
||||||
this.trace(`Node ${node.id} is placed between [${node.x}, ${node.x + nodeTotalWidth})`);
|
|
||||||
const staggeredFlooredI = Math.floor(placedCount++ % 3);
|
const staggeredFlooredI = Math.floor(placedCount++ % 3);
|
||||||
const delta = C.MINIMUM_EDGE_SEPARATION * staggeredFlooredI;
|
const delta = C.MINIMUM_EDGE_SEPARATION * staggeredFlooredI;
|
||||||
node.outputApproach += delta;
|
node.outputApproach += delta;
|
||||||
@ -288,7 +287,7 @@ class GraphOccupation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public occupyNode(node: GraphNode): number {
|
public occupyNode(node: GraphNode): number {
|
||||||
const width = node.getTotalNodeWidth();
|
const width = node.getWidth();
|
||||||
const margin = C.MINIMUM_EDGE_SEPARATION;
|
const margin = C.MINIMUM_EDGE_SEPARATION;
|
||||||
const paddedWidth = width + 2 * margin;
|
const paddedWidth = width + 2 * margin;
|
||||||
const [direction, position] = this.getPlacementHint(node);
|
const [direction, position] = this.getPlacementHint(node);
|
||||||
|
@ -6,28 +6,15 @@ import * as C from "./common/constants";
|
|||||||
import { GraphPhase, GraphStateType } from "./phases/graph-phase/graph-phase";
|
import { GraphPhase, GraphStateType } from "./phases/graph-phase/graph-phase";
|
||||||
import { GraphEdge } from "./phases/graph-phase/graph-edge";
|
import { GraphEdge } from "./phases/graph-phase/graph-edge";
|
||||||
import { GraphNode } from "./phases/graph-phase/graph-node";
|
import { GraphNode } from "./phases/graph-phase/graph-node";
|
||||||
|
import { MovableContainer } from "./movable-container";
|
||||||
|
|
||||||
export class Graph {
|
export class Graph extends MovableContainer<GraphPhase> {
|
||||||
graphPhase: GraphPhase;
|
|
||||||
nodeMap: Array<GraphNode>;
|
nodeMap: Array<GraphNode>;
|
||||||
minGraphX: number;
|
|
||||||
maxGraphX: number;
|
|
||||||
minGraphY: number;
|
|
||||||
maxGraphY: number;
|
|
||||||
maxGraphNodeX: number;
|
|
||||||
maxBackEdgeNumber: number;
|
maxBackEdgeNumber: number;
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
|
|
||||||
constructor(graphPhase: GraphPhase) {
|
constructor(graphPhase: GraphPhase) {
|
||||||
this.graphPhase = graphPhase;
|
super(graphPhase);
|
||||||
this.nodeMap = graphPhase.nodeIdToNodeMap;
|
this.nodeMap = graphPhase.nodeIdToNodeMap;
|
||||||
this.minGraphX = 0;
|
|
||||||
this.maxGraphX = 1;
|
|
||||||
this.minGraphY = 0;
|
|
||||||
this.maxGraphY = 1;
|
|
||||||
this.width = 1;
|
|
||||||
this.height = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public *nodes(func = (n: GraphNode) => true) {
|
public *nodes(func = (n: GraphNode) => true) {
|
||||||
@ -65,12 +52,11 @@ export class Graph {
|
|||||||
if (!node.visible) continue;
|
if (!node.visible) continue;
|
||||||
|
|
||||||
this.minGraphX = Math.min(this.minGraphX, node.x);
|
this.minGraphX = Math.min(this.minGraphX, node.x);
|
||||||
this.maxGraphNodeX = Math.max(this.maxGraphNodeX,
|
this.maxGraphNodeX = Math.max(this.maxGraphNodeX, node.x + node.getWidth());
|
||||||
node.x + node.getTotalNodeWidth());
|
|
||||||
|
|
||||||
this.minGraphY = Math.min(this.minGraphY, node.y - C.NODE_INPUT_WIDTH);
|
this.minGraphY = Math.min(this.minGraphY, node.y - C.NODE_INPUT_WIDTH);
|
||||||
this.maxGraphY = Math.max(this.maxGraphY,
|
this.maxGraphY = Math.max(this.maxGraphY, node.y + node.getHeight(showTypes)
|
||||||
node.y + node.getNodeHeight(showTypes) + C.NODE_INPUT_WIDTH);
|
+ C.NODE_INPUT_WIDTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.maxGraphX = this.maxGraphNodeX + this.maxBackEdgeNumber
|
this.maxGraphX = this.maxGraphNodeX + this.maxBackEdgeNumber
|
||||||
@ -97,8 +83,4 @@ export class Graph {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isRendered(): boolean {
|
|
||||||
return this.graphPhase.rendered;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { GraphPhase } from "./phases/graph-phase/graph-phase";
|
|||||||
import { GraphNode } from "./phases/graph-phase/graph-node";
|
import { GraphNode } from "./phases/graph-phase/graph-node";
|
||||||
import { storageGetItem, storageSetItem } from "./common/util";
|
import { storageGetItem, storageSetItem } from "./common/util";
|
||||||
import { PhaseType } from "./phases/phase";
|
import { PhaseType } from "./phases/phase";
|
||||||
|
import { TurboshaftGraphView } from "./views/turboshaft-graph-view";
|
||||||
|
|
||||||
const toolboxHTML = `
|
const toolboxHTML = `
|
||||||
<div class="graph-toolbox">
|
<div class="graph-toolbox">
|
||||||
@ -28,6 +29,7 @@ export class GraphMultiView extends View {
|
|||||||
sourceResolver: SourceResolver;
|
sourceResolver: SourceResolver;
|
||||||
selectionBroker: SelectionBroker;
|
selectionBroker: SelectionBroker;
|
||||||
graph: GraphView;
|
graph: GraphView;
|
||||||
|
turboshaftGraph: TurboshaftGraphView;
|
||||||
schedule: ScheduleView;
|
schedule: ScheduleView;
|
||||||
sequence: SequenceView;
|
sequence: SequenceView;
|
||||||
selectMenu: HTMLSelectElement;
|
selectMenu: HTMLSelectElement;
|
||||||
@ -59,6 +61,8 @@ export class GraphMultiView extends View {
|
|||||||
searchInput.setAttribute("value", storageGetItem("lastSearch", "", false));
|
searchInput.setAttribute("value", storageGetItem("lastSearch", "", false));
|
||||||
this.graph = new GraphView(this.divNode, selectionBroker, view.displayPhaseByName.bind(this),
|
this.graph = new GraphView(this.divNode, selectionBroker, view.displayPhaseByName.bind(this),
|
||||||
toolbox.querySelector(".graph-toolbox"));
|
toolbox.querySelector(".graph-toolbox"));
|
||||||
|
this.turboshaftGraph = new TurboshaftGraphView(this.divNode, selectionBroker,
|
||||||
|
view.displayPhaseByName.bind(this), toolbox.querySelector(".graph-toolbox"));
|
||||||
this.schedule = new ScheduleView(this.divNode, selectionBroker);
|
this.schedule = new ScheduleView(this.divNode, selectionBroker);
|
||||||
this.sequence = new SequenceView(this.divNode, selectionBroker);
|
this.sequence = new SequenceView(this.divNode, selectionBroker);
|
||||||
this.selectMenu = toolbox.querySelector("#phase-select") as HTMLSelectElement;
|
this.selectMenu = toolbox.querySelector("#phase-select") as HTMLSelectElement;
|
||||||
@ -96,6 +100,8 @@ export class GraphMultiView extends View {
|
|||||||
private displayPhase(phase: GenericPhase, selection?: Map<string, GraphNode>): void {
|
private displayPhase(phase: GenericPhase, selection?: Map<string, GraphNode>): void {
|
||||||
if (phase.type == PhaseType.Graph) {
|
if (phase.type == PhaseType.Graph) {
|
||||||
this.displayPhaseView(this.graph, phase, selection);
|
this.displayPhaseView(this.graph, phase, selection);
|
||||||
|
} else if (phase.type == PhaseType.TurboshaftGraph) {
|
||||||
|
this.displayPhaseView(this.turboshaftGraph, phase, selection);
|
||||||
} else if (phase.type == PhaseType.Schedule) {
|
} else if (phase.type == PhaseType.Schedule) {
|
||||||
this.displayPhaseView(this.schedule, phase, selection);
|
this.displayPhaseView(this.schedule, phase, selection);
|
||||||
} else if (phase.type == PhaseType.Sequence) {
|
} else if (phase.type == PhaseType.Sequence) {
|
||||||
@ -120,7 +126,7 @@ export class GraphMultiView extends View {
|
|||||||
let nextPhaseIndex = this.selectMenu.selectedIndex + 1;
|
let nextPhaseIndex = this.selectMenu.selectedIndex + 1;
|
||||||
while (nextPhaseIndex < this.sourceResolver.phases.length) {
|
while (nextPhaseIndex < this.sourceResolver.phases.length) {
|
||||||
const nextPhase = this.sourceResolver.getPhase(nextPhaseIndex);
|
const nextPhase = this.sourceResolver.getPhase(nextPhaseIndex);
|
||||||
if (nextPhase.type == PhaseType.Graph) {
|
if (nextPhase.isGraph()) {
|
||||||
this.selectMenu.selectedIndex = nextPhaseIndex;
|
this.selectMenu.selectedIndex = nextPhaseIndex;
|
||||||
storageSetItem("lastSelectedPhase", nextPhaseIndex);
|
storageSetItem("lastSelectedPhase", nextPhaseIndex);
|
||||||
this.displayPhase(nextPhase);
|
this.displayPhase(nextPhase);
|
||||||
@ -134,7 +140,7 @@ export class GraphMultiView extends View {
|
|||||||
let previousPhaseIndex = this.selectMenu.selectedIndex - 1;
|
let previousPhaseIndex = this.selectMenu.selectedIndex - 1;
|
||||||
while (previousPhaseIndex >= 0) {
|
while (previousPhaseIndex >= 0) {
|
||||||
const previousPhase = this.sourceResolver.getPhase(previousPhaseIndex);
|
const previousPhase = this.sourceResolver.getPhase(previousPhaseIndex);
|
||||||
if (previousPhase.type === PhaseType.Graph) {
|
if (previousPhase.isGraph()) {
|
||||||
this.selectMenu.selectedIndex = previousPhaseIndex;
|
this.selectMenu.selectedIndex = previousPhaseIndex;
|
||||||
storageSetItem("lastSelectedPhase", previousPhaseIndex);
|
storageSetItem("lastSelectedPhase", previousPhaseIndex);
|
||||||
this.displayPhase(previousPhase);
|
this.displayPhase(previousPhase);
|
||||||
|
34
tools/turbolizer/src/movable-container.ts
Normal file
34
tools/turbolizer/src/movable-container.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import { GraphPhase } from "./phases/graph-phase/graph-phase";
|
||||||
|
import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
|
||||||
|
|
||||||
|
export abstract class MovableContainer<GraphPhaseType extends GraphPhase | TurboshaftGraphPhase> {
|
||||||
|
graphPhase: GraphPhaseType;
|
||||||
|
minGraphX: number;
|
||||||
|
maxGraphX: number;
|
||||||
|
maxGraphNodeX: number;
|
||||||
|
minGraphY: number;
|
||||||
|
maxGraphY: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
|
||||||
|
public abstract redetermineGraphBoundingBox(extendHeight: boolean):
|
||||||
|
[[number, number], [number, number]];
|
||||||
|
|
||||||
|
constructor(graphPhase: GraphPhaseType) {
|
||||||
|
this.graphPhase = graphPhase;
|
||||||
|
this.minGraphX = 0;
|
||||||
|
this.maxGraphX = 1;
|
||||||
|
this.minGraphY = 0;
|
||||||
|
this.maxGraphY = 1;
|
||||||
|
this.width = 1;
|
||||||
|
this.height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRendered(): boolean {
|
||||||
|
return this.graphPhase.rendered;
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,15 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import * as C from "./common/constants";
|
||||||
import { measureText } from "./common/util";
|
import { measureText } from "./common/util";
|
||||||
import { GraphEdge } from "./phases/graph-phase/graph-edge";
|
import { GraphEdge } from "./phases/graph-phase/graph-edge";
|
||||||
import { TurboshaftGraphEdge } from "./phases/turboshaft-graph-phase/turboshaft-graph-edge";
|
import { TurboshaftGraphEdge } from "./phases/turboshaft-graph-phase/turboshaft-graph-edge";
|
||||||
|
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
|
||||||
|
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||||||
|
|
||||||
export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge> {
|
export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge<TurboshaftGraphNode
|
||||||
|
| TurboshaftGraphBlock>> {
|
||||||
id: number;
|
id: number;
|
||||||
displayLabel: string;
|
displayLabel: string;
|
||||||
inputs: Array<EdgeType>;
|
inputs: Array<EdgeType>;
|
||||||
@ -15,7 +19,9 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge> {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
labelBox: { width: number, height: number };
|
labelBox: { width: number, height: number };
|
||||||
visitOrderWithinRank: number;
|
|
||||||
|
public abstract getHeight(extendHeight: boolean): number;
|
||||||
|
public abstract getWidth(): number;
|
||||||
|
|
||||||
constructor(id: number, displayLabel?: string) {
|
constructor(id: number, displayLabel?: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -26,20 +32,22 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge> {
|
|||||||
this.x = 0;
|
this.x = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
this.labelBox = measureText(this.displayLabel);
|
this.labelBox = measureText(this.displayLabel);
|
||||||
this.visitOrderWithinRank = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public areAnyOutputsVisible(): number {
|
public areAnyOutputsVisible(): OutputVisibilityType {
|
||||||
// TODO (danylo boiko) Move 0, 1, 2 logic to enum
|
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
for (const edge of this.outputs) {
|
for (const edge of this.outputs) {
|
||||||
if (edge.isVisible()) {
|
if (edge.isVisible()) {
|
||||||
++visibleCount;
|
++visibleCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.outputs.length === visibleCount) return 2;
|
if (this.outputs.length == visibleCount) {
|
||||||
if (visibleCount !== 0) return 1;
|
return OutputVisibilityType.AllNodesVisible;
|
||||||
return 0;
|
}
|
||||||
|
if (visibleCount != 0) {
|
||||||
|
return OutputVisibilityType.SomeNodesVisible;
|
||||||
|
}
|
||||||
|
return OutputVisibilityType.NoVisibleNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setOutputVisibility(visibility: boolean): boolean {
|
public setOutputVisibility(visibility: boolean): boolean {
|
||||||
@ -64,6 +72,15 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getInputX(index: number): number {
|
||||||
|
return this.getWidth() - (C.NODE_INPUT_WIDTH / 2) +
|
||||||
|
(index - this.inputs.length + 1) * C.NODE_INPUT_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOutputX(): number {
|
||||||
|
return this.getWidth() - (C.NODE_INPUT_WIDTH / 2);
|
||||||
|
}
|
||||||
|
|
||||||
public identifier(): string {
|
public identifier(): string {
|
||||||
return `${this.id}`;
|
return `${this.id}`;
|
||||||
}
|
}
|
||||||
@ -72,3 +89,9 @@ export abstract class Node<EdgeType extends GraphEdge | TurboshaftGraphEdge> {
|
|||||||
return `N${this.id}`;
|
return `N${this.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OutputVisibilityType {
|
||||||
|
NoVisibleNodes,
|
||||||
|
SomeNodesVisible,
|
||||||
|
AllNodesVisible
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ export class GraphEdge extends Edge<GraphNode> {
|
|||||||
}
|
}
|
||||||
const inputOffset = C.MINIMUM_EDGE_SEPARATION * (index + 1);
|
const inputOffset = C.MINIMUM_EDGE_SEPARATION * (index + 1);
|
||||||
return target.x < source.x
|
return target.x < source.x
|
||||||
? target.x + target.getTotalNodeWidth() + inputOffset
|
? target.x + target.getWidth() + inputOffset
|
||||||
: target.x - inputOffset;
|
: target.x - inputOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ export class GraphEdge extends Edge<GraphNode> {
|
|||||||
const arrowheadHeight = 7;
|
const arrowheadHeight = 7;
|
||||||
const inputY = target.y - 2 * C.DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
|
const inputY = target.y - 2 * C.DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
|
||||||
const outputX = source.x + source.getOutputX();
|
const outputX = source.x + source.getOutputX();
|
||||||
const outputY = source.y + source.getNodeHeight(showTypes) + C.DEFAULT_NODE_BUBBLE_RADIUS;
|
const outputY = source.y + source.getHeight(showTypes) + C.DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
let inputApproach = target.getInputApproach(this.index);
|
let inputApproach = target.getInputApproach(this.index);
|
||||||
const outputApproach = source.getOutputApproach(showTypes);
|
const outputApproach = source.getOutputApproach(showTypes);
|
||||||
const horizontalPos = this.getInputHorizontalPosition(graph, showTypes);
|
const horizontalPos = this.getInputHorizontalPosition(graph, showTypes);
|
||||||
|
@ -15,6 +15,7 @@ export class GraphNode extends Node<GraphEdge> {
|
|||||||
cfg: boolean;
|
cfg: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
normalHeight: number;
|
normalHeight: number;
|
||||||
|
visitOrderWithinRank: number;
|
||||||
|
|
||||||
constructor(nodeLabel: NodeLabel) {
|
constructor(nodeLabel: NodeLabel) {
|
||||||
super(nodeLabel.id, nodeLabel.getDisplayLabel());
|
super(nodeLabel.id, nodeLabel.getDisplayLabel());
|
||||||
@ -28,6 +29,18 @@ export class GraphNode extends Node<GraphEdge> {
|
|||||||
this.width = alignUp(innerWidth + C.NODE_INPUT_WIDTH * 2, C.NODE_INPUT_WIDTH);
|
this.width = alignUp(innerWidth + C.NODE_INPUT_WIDTH * 2, C.NODE_INPUT_WIDTH);
|
||||||
const innerHeight = Math.max(this.labelBox.height, typeBox.height);
|
const innerHeight = Math.max(this.labelBox.height, typeBox.height);
|
||||||
this.normalHeight = innerHeight + 20;
|
this.normalHeight = innerHeight + 20;
|
||||||
|
this.visitOrderWithinRank = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeight(showTypes: boolean): number {
|
||||||
|
if (showTypes) {
|
||||||
|
return this.normalHeight + this.labelBox.height;
|
||||||
|
}
|
||||||
|
return this.normalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWidth(): number {
|
||||||
|
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, this.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isControl(): boolean {
|
public isControl(): boolean {
|
||||||
@ -68,10 +81,6 @@ export class GraphNode extends Node<GraphEdge> {
|
|||||||
this.isJavaScript() || this.isSimplified());
|
this.isJavaScript() || this.isSimplified());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTotalNodeWidth(): number {
|
|
||||||
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, this.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
return this.nodeLabel.getTitle();
|
return this.nodeLabel.getTitle();
|
||||||
}
|
}
|
||||||
@ -105,27 +114,11 @@ export class GraphNode extends Node<GraphEdge> {
|
|||||||
(index % 4) * C.MINIMUM_EDGE_SEPARATION - C.DEFAULT_NODE_BUBBLE_RADIUS;
|
(index % 4) * C.MINIMUM_EDGE_SEPARATION - C.DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNodeHeight(showTypes: boolean): number {
|
|
||||||
if (showTypes) {
|
|
||||||
return this.normalHeight + this.labelBox.height;
|
|
||||||
}
|
|
||||||
return this.normalHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getOutputApproach(showTypes: boolean): number {
|
public getOutputApproach(showTypes: boolean): number {
|
||||||
return this.y + this.outputApproach + this.getNodeHeight(showTypes) +
|
return this.y + this.outputApproach + this.getHeight(showTypes) +
|
||||||
+ C.DEFAULT_NODE_BUBBLE_RADIUS;
|
+ C.DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getInputX(index: number): number {
|
|
||||||
return this.getTotalNodeWidth() - (C.NODE_INPUT_WIDTH / 2) +
|
|
||||||
(index - this.inputs.length + 1) * C.NODE_INPUT_WIDTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getOutputX(): number {
|
|
||||||
return this.getTotalNodeWidth() - (C.NODE_INPUT_WIDTH / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public hasBackEdges(): boolean {
|
public hasBackEdges(): boolean {
|
||||||
return (this.nodeLabel.opcode === "Loop") ||
|
return (this.nodeLabel.opcode === "Loop") ||
|
||||||
((this.nodeLabel.opcode === "Phi" || this.nodeLabel.opcode === "EffectPhi" ||
|
((this.nodeLabel.opcode === "Phi" || this.nodeLabel.opcode === "EffectPhi" ||
|
||||||
|
@ -10,6 +10,11 @@ export abstract class Phase {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isGraph(): boolean {
|
||||||
|
return this.type == PhaseType.Graph ||
|
||||||
|
this.type == PhaseType.TurboshaftGraph;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PhaseType {
|
export enum PhaseType {
|
||||||
@ -20,3 +25,8 @@ export enum PhaseType {
|
|||||||
Sequence = "sequence",
|
Sequence = "sequence",
|
||||||
Schedule = "schedule"
|
Schedule = "schedule"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum GraphStateType {
|
||||||
|
NeedToFullRebuild,
|
||||||
|
Cached
|
||||||
|
}
|
||||||
|
@ -3,21 +3,38 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
||||||
|
import { Node } from "../../node";
|
||||||
|
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
||||||
|
|
||||||
export class TurboshaftGraphBlock {
|
export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGraphBlock>> {
|
||||||
id: string;
|
|
||||||
type: TurboshaftGraphBlockType;
|
type: TurboshaftGraphBlockType;
|
||||||
deferred: boolean;
|
deferred: boolean;
|
||||||
predecessors: Array<string>;
|
predecessors: Array<string>;
|
||||||
nodes: Array<TurboshaftGraphNode>;
|
nodes: Array<TurboshaftGraphNode>;
|
||||||
|
|
||||||
constructor(id: string, type: TurboshaftGraphBlockType, deferred: boolean,
|
constructor(id: number, type: TurboshaftGraphBlockType, deferred: boolean,
|
||||||
predecessors: Array<string>) {
|
predecessors: Array<string>) {
|
||||||
this.id = id;
|
super(id, `${type} ${id}${deferred ? " (deferred)" : ""}`);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.deferred = deferred;
|
this.deferred = deferred;
|
||||||
this.predecessors = predecessors ?? new Array<string>();
|
this.predecessors = predecessors ?? new Array<string>();
|
||||||
this.nodes = new Array<TurboshaftGraphNode>();
|
this.nodes = new Array<TurboshaftGraphNode>();
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeight(showProperties: boolean): number {
|
||||||
|
return this.nodes.reduce<number>((accumulator: number, node: TurboshaftGraphNode) => {
|
||||||
|
return accumulator + node.getHeight(showProperties);
|
||||||
|
}, this.labelBox.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWidth(): number {
|
||||||
|
const maxWidth = Math.max(...this.nodes.map((node: TurboshaftGraphNode) => node.getWidth()));
|
||||||
|
return Math.max(maxWidth, this.labelBox.width) + 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return `B${this.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,18 @@
|
|||||||
|
|
||||||
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
||||||
import { Edge } from "../../edge";
|
import { Edge } from "../../edge";
|
||||||
|
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
||||||
|
|
||||||
export class TurboshaftGraphEdge extends Edge<TurboshaftGraphNode> {
|
export class TurboshaftGraphEdge<Type extends TurboshaftGraphNode | TurboshaftGraphBlock> extends
|
||||||
constructor(target: TurboshaftGraphNode, source: TurboshaftGraphNode) {
|
Edge<Type> {
|
||||||
|
|
||||||
|
constructor(target: Type, source: Type) {
|
||||||
super(target, source);
|
super(target, source);
|
||||||
|
this.visible = target.visible && source.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(idx?: number): string {
|
||||||
|
if (idx !== null) return `${this.source.id},${idx},${this.target.id}`;
|
||||||
|
return `${this.source.id},${this.target.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,78 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import * as C from "../../common/constants";
|
||||||
|
import { measureText } from "../../common/util";
|
||||||
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
||||||
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
||||||
import { Node } from "../../node";
|
import { Node } from "../../node";
|
||||||
|
|
||||||
export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge> {
|
export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGraphNode>> {
|
||||||
title: string;
|
title: string;
|
||||||
block: TurboshaftGraphBlock;
|
block: TurboshaftGraphBlock;
|
||||||
|
opPropertiesType: OpPropertiesType;
|
||||||
properties: string;
|
properties: string;
|
||||||
|
|
||||||
constructor(id: number, title: string, block: TurboshaftGraphBlock, properties: string) {
|
constructor(id: number, title: string, block: TurboshaftGraphBlock,
|
||||||
super(id);
|
opPropertiesType: OpPropertiesType, properties: string) {
|
||||||
|
super(id, `${id} ${title}`);
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.block = block;
|
this.block = block;
|
||||||
|
this.opPropertiesType = opPropertiesType;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeight(showProperties: boolean): number {
|
||||||
|
if (this.properties && showProperties) {
|
||||||
|
return this.labelBox.height * 2;
|
||||||
|
}
|
||||||
|
return this.labelBox.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWidth(): number {
|
||||||
|
const measure = measureText(
|
||||||
|
`${this.getInlineLabel()}[${this.getPropertiesTypeAbbreviation()}]`
|
||||||
|
);
|
||||||
|
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, measure.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getInlineLabel(): string {
|
||||||
|
if (this.inputs.length == 0) return `${this.id} ${this.title}`;
|
||||||
|
return `${this.id} ${this.title}(${this.inputs.map(i => i.source.id).join(",")})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getReadableProperties(blockWidth: number): string {
|
||||||
|
const propertiesWidth = measureText(this.properties).width;
|
||||||
|
if (blockWidth > propertiesWidth) return this.properties;
|
||||||
|
const widthOfOneSymbol = Math.floor(propertiesWidth / this.properties.length);
|
||||||
|
const lengthOfReadableProperties = Math.floor(blockWidth / widthOfOneSymbol);
|
||||||
|
return `${this.properties.slice(0, lengthOfReadableProperties - 3)}..`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPropertiesTypeAbbreviation(): string {
|
||||||
|
switch (this.opPropertiesType) {
|
||||||
|
case OpPropertiesType.Pure:
|
||||||
|
return "P";
|
||||||
|
case OpPropertiesType.Reading:
|
||||||
|
return "R";
|
||||||
|
case OpPropertiesType.Writing:
|
||||||
|
return "W";
|
||||||
|
case OpPropertiesType.CanDeopt:
|
||||||
|
return "CD";
|
||||||
|
case OpPropertiesType.AnySideEffects:
|
||||||
|
return "ASE";
|
||||||
|
case OpPropertiesType.BlockTerminator:
|
||||||
|
return "BT";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OpPropertiesType {
|
||||||
|
Pure = "Pure",
|
||||||
|
Reading = "Reading",
|
||||||
|
Writing = "Writing",
|
||||||
|
CanDeopt = "CanDeopt",
|
||||||
|
AnySideEffects = "AnySideEffects",
|
||||||
|
BlockTerminator = "BlockTerminator"
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import { Phase, PhaseType } from "../phase";
|
import { GraphStateType, Phase, PhaseType } from "../phase";
|
||||||
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
|
||||||
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
|
||||||
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
||||||
@ -10,8 +10,12 @@ import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
|
|||||||
export class TurboshaftGraphPhase extends Phase {
|
export class TurboshaftGraphPhase extends Phase {
|
||||||
highestBlockId: number;
|
highestBlockId: number;
|
||||||
data: TurboshaftGraphData;
|
data: TurboshaftGraphData;
|
||||||
|
stateType: GraphStateType;
|
||||||
|
layoutType: TurboshaftLayoutType;
|
||||||
nodeIdToNodeMap: Array<TurboshaftGraphNode>;
|
nodeIdToNodeMap: Array<TurboshaftGraphNode>;
|
||||||
blockIdToBlockMap: Array<TurboshaftGraphBlock>;
|
blockIdToBlockMap: Array<TurboshaftGraphBlock>;
|
||||||
|
rendered: boolean;
|
||||||
|
transform: { x: number, y: number, scale: number };
|
||||||
|
|
||||||
constructor(name: string, highestBlockId: number, data?: TurboshaftGraphData,
|
constructor(name: string, highestBlockId: number, data?: TurboshaftGraphData,
|
||||||
nodeIdToNodeMap?: Array<TurboshaftGraphNode>,
|
nodeIdToNodeMap?: Array<TurboshaftGraphNode>,
|
||||||
@ -19,8 +23,11 @@ export class TurboshaftGraphPhase extends Phase {
|
|||||||
super(name, PhaseType.TurboshaftGraph);
|
super(name, PhaseType.TurboshaftGraph);
|
||||||
this.highestBlockId = highestBlockId;
|
this.highestBlockId = highestBlockId;
|
||||||
this.data = data ?? new TurboshaftGraphData();
|
this.data = data ?? new TurboshaftGraphData();
|
||||||
|
this.stateType = GraphStateType.NeedToFullRebuild;
|
||||||
|
this.layoutType = TurboshaftLayoutType.Inline;
|
||||||
this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<TurboshaftGraphNode>();
|
this.nodeIdToNodeMap = nodeIdToNodeMap ?? new Array<TurboshaftGraphNode>();
|
||||||
this.blockIdToBlockMap = blockIdToBlockMap ?? new Array<TurboshaftGraphBlock>();
|
this.blockIdToBlockMap = blockIdToBlockMap ?? new Array<TurboshaftGraphBlock>();
|
||||||
|
this.rendered = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseDataFromJSON(dataJson): void {
|
public parseDataFromJSON(dataJson): void {
|
||||||
@ -32,18 +39,29 @@ export class TurboshaftGraphPhase extends Phase {
|
|||||||
|
|
||||||
private parseBlocksFromJSON(blocksJson): void {
|
private parseBlocksFromJSON(blocksJson): void {
|
||||||
for (const blockJson of blocksJson) {
|
for (const blockJson of blocksJson) {
|
||||||
const block = new TurboshaftGraphBlock(blockJson.id, blockJson.type,
|
// TODO (danylo boiko) Change type of block id in JSON output
|
||||||
|
const numId = Number(blockJson.id.substring(1));
|
||||||
|
const block = new TurboshaftGraphBlock(numId, blockJson.type,
|
||||||
blockJson.deferred, blockJson.predecessors);
|
blockJson.deferred, blockJson.predecessors);
|
||||||
this.data.blocks.push(block);
|
this.data.blocks.push(block);
|
||||||
this.blockIdToBlockMap[block.id] = block;
|
this.blockIdToBlockMap[block.id] = block;
|
||||||
}
|
}
|
||||||
|
for (const block of this.blockIdToBlockMap) {
|
||||||
|
for (const predecessor of block.predecessors) {
|
||||||
|
const source = this.blockIdToBlockMap[Number(predecessor.substring(1))];
|
||||||
|
const edge = new TurboshaftGraphEdge(block, source);
|
||||||
|
block.inputs.push(edge);
|
||||||
|
source.outputs.push(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseNodesFromJSON(nodesJson): void {
|
private parseNodesFromJSON(nodesJson): void {
|
||||||
for (const nodeJson of nodesJson) {
|
for (const nodeJson of nodesJson) {
|
||||||
const block = this.blockIdToBlockMap[nodeJson.block_id];
|
const numId = Number(nodeJson.block_id.substring(1));
|
||||||
|
const block = this.blockIdToBlockMap[numId];
|
||||||
const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title,
|
const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title,
|
||||||
block, nodeJson.properties);
|
block, nodeJson.op_properties_type, nodeJson.properties);
|
||||||
block.nodes.push(node);
|
block.nodes.push(node);
|
||||||
this.data.nodes.push(node);
|
this.data.nodes.push(node);
|
||||||
this.nodeIdToNodeMap[node.id] = node;
|
this.nodeIdToNodeMap[node.id] = node;
|
||||||
@ -64,13 +82,19 @@ export class TurboshaftGraphPhase extends Phase {
|
|||||||
|
|
||||||
export class TurboshaftGraphData {
|
export class TurboshaftGraphData {
|
||||||
nodes: Array<TurboshaftGraphNode>;
|
nodes: Array<TurboshaftGraphNode>;
|
||||||
edges: Array<TurboshaftGraphEdge>;
|
edges: Array<TurboshaftGraphEdge<TurboshaftGraphNode>>;
|
||||||
blocks: Array<TurboshaftGraphBlock>;
|
blocks: Array<TurboshaftGraphBlock>;
|
||||||
|
|
||||||
constructor(nodes?: Array<TurboshaftGraphNode>, edges?: Array<TurboshaftGraphEdge>,
|
constructor(nodes?: Array<TurboshaftGraphNode>,
|
||||||
|
edges?: Array<TurboshaftGraphEdge<TurboshaftGraphNode>>,
|
||||||
blocks?: Array<TurboshaftGraphBlock>) {
|
blocks?: Array<TurboshaftGraphBlock>) {
|
||||||
this.nodes = nodes ?? new Array<TurboshaftGraphNode>();
|
this.nodes = nodes ?? new Array<TurboshaftGraphNode>();
|
||||||
this.edges = edges ?? new Array<TurboshaftGraphEdge>();
|
this.edges = edges ?? new Array<TurboshaftGraphEdge<TurboshaftGraphNode>>();
|
||||||
this.blocks = blocks ?? new Array<TurboshaftGraphBlock>();
|
this.blocks = blocks ?? new Array<TurboshaftGraphBlock>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TurboshaftLayoutType {
|
||||||
|
Inline,
|
||||||
|
Nodes
|
||||||
|
}
|
||||||
|
40
tools/turbolizer/src/turboshaft-graph-layout.ts
Normal file
40
tools/turbolizer/src/turboshaft-graph-layout.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { TurboshaftGraph } from "./turboshaft-graph";
|
||||||
|
import { GraphStateType } from "./phases/phase";
|
||||||
|
import { TurboshaftLayoutType } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
|
||||||
|
|
||||||
|
export class TurboshaftGraphLayout {
|
||||||
|
graph: TurboshaftGraph;
|
||||||
|
|
||||||
|
constructor(graph: TurboshaftGraph) {
|
||||||
|
this.graph = graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public rebuild(showProperties: boolean): void {
|
||||||
|
if (this.graph.graphPhase.stateType == GraphStateType.NeedToFullRebuild) {
|
||||||
|
switch (this.graph.graphPhase.layoutType) {
|
||||||
|
case TurboshaftLayoutType.Inline:
|
||||||
|
this.inlineRebuild(showProperties);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw "Unsupported graph layout type";
|
||||||
|
}
|
||||||
|
this.graph.graphPhase.stateType = GraphStateType.Cached;
|
||||||
|
}
|
||||||
|
this.graph.graphPhase.rendered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private inlineRebuild(showProperties): void {
|
||||||
|
// Useless logic to simulate blocks coordinates (will be replaced in the future)
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
for (const block of this.graph.blockMap) {
|
||||||
|
block.x = x;
|
||||||
|
block.y = y;
|
||||||
|
y += block.getHeight(showProperties) + 50;
|
||||||
|
if (y > 1800) {
|
||||||
|
x += block.getWidth() * 1.5;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
tools/turbolizer/src/turboshaft-graph.ts
Normal file
74
tools/turbolizer/src/turboshaft-graph.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import * as C from "./common/constants";
|
||||||
|
import { MovableContainer } from "./movable-container";
|
||||||
|
import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
|
||||||
|
import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";
|
||||||
|
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||||||
|
import { TurboshaftGraphEdge } from "./phases/turboshaft-graph-phase/turboshaft-graph-edge";
|
||||||
|
|
||||||
|
export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
|
||||||
|
blockMap: Array<TurboshaftGraphBlock>;
|
||||||
|
nodeMap: Array<TurboshaftGraphNode>;
|
||||||
|
|
||||||
|
constructor(graphPhase: TurboshaftGraphPhase) {
|
||||||
|
super(graphPhase);
|
||||||
|
this.blockMap = graphPhase.blockIdToBlockMap;
|
||||||
|
this.nodeMap = graphPhase.nodeIdToNodeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public *blocks(func = (b: TurboshaftGraphBlock) => true) {
|
||||||
|
for (const block of this.blockMap) {
|
||||||
|
if (!block || !func(block)) continue;
|
||||||
|
yield block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public *nodes(func = (n: TurboshaftGraphNode) => true) {
|
||||||
|
for (const node of this.nodeMap) {
|
||||||
|
if (!node || !func(node)) continue;
|
||||||
|
yield node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public *blocksEdges(func = (e: TurboshaftGraphEdge<TurboshaftGraphBlock>) => true) {
|
||||||
|
for (const block of this.blockMap) {
|
||||||
|
if (!block) continue;
|
||||||
|
for (const edge of block.inputs) {
|
||||||
|
if (!edge || func(edge)) continue;
|
||||||
|
yield edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public redetermineGraphBoundingBox(showProperties: boolean):
|
||||||
|
[[number, number], [number, number]] {
|
||||||
|
this.minGraphX = 0;
|
||||||
|
this.maxGraphNodeX = 1;
|
||||||
|
this.minGraphY = 0;
|
||||||
|
this.maxGraphY = 1;
|
||||||
|
|
||||||
|
for (const block of this.blocks()) {
|
||||||
|
if (!block.visible) continue;
|
||||||
|
|
||||||
|
this.minGraphX = Math.min(this.minGraphX, block.x);
|
||||||
|
this.maxGraphNodeX = Math.max(this.maxGraphNodeX, block.x + block.getWidth());
|
||||||
|
|
||||||
|
this.minGraphY = Math.min(this.minGraphY, block.y - C.NODE_INPUT_WIDTH);
|
||||||
|
this.maxGraphY = Math.max(this.maxGraphY, block.y + block.getHeight(showProperties)
|
||||||
|
+ C.NODE_INPUT_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maxGraphX = this.maxGraphNodeX + 3 * C.MINIMUM_EDGE_SEPARATION;
|
||||||
|
|
||||||
|
this.width = this.maxGraphX - this.minGraphX;
|
||||||
|
this.height = this.maxGraphY - this.minGraphY;
|
||||||
|
|
||||||
|
return [
|
||||||
|
[this.minGraphX - this.width / 2, this.minGraphY - this.height / 2],
|
||||||
|
[this.maxGraphX + this.width / 2, this.maxGraphY + this.height / 2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
305
tools/turbolizer/src/views/movable-view.ts
Normal file
305
tools/turbolizer/src/views/movable-view.ts
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import * as C from "../common/constants";
|
||||||
|
import * as d3 from "d3";
|
||||||
|
import { storageGetItem, storageSetItem } from "../common/util";
|
||||||
|
import { PhaseView } from "./view";
|
||||||
|
import { SelectionBroker } from "../selection/selection-broker";
|
||||||
|
import { SelectionMap } from "../selection/selection";
|
||||||
|
import { ClearableHandler, NodeSelectionHandler } from "../selection/selection-handler";
|
||||||
|
import { GraphStateType } from "../phases/graph-phase/graph-phase";
|
||||||
|
import { Edge } from "../edge";
|
||||||
|
import { Node } from "../node";
|
||||||
|
import { TurboshaftGraph } from "../turboshaft-graph";
|
||||||
|
import { Graph } from "../graph";
|
||||||
|
|
||||||
|
export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> extends PhaseView {
|
||||||
|
phaseName: string;
|
||||||
|
graph: GraphType;
|
||||||
|
showPhaseByName: (name: string, selection: Set<any>) => void;
|
||||||
|
broker: SelectionBroker;
|
||||||
|
toolbox: HTMLElement;
|
||||||
|
state: MovableViewState;
|
||||||
|
nodesSelectionHandler: NodeSelectionHandler & ClearableHandler;
|
||||||
|
divElement: d3.Selection<any, any, any, any>;
|
||||||
|
graphElement: d3.Selection<any, any, any, any>;
|
||||||
|
svg: d3.Selection<any, any, any, any>;
|
||||||
|
panZoom: d3.ZoomBehavior<SVGElement, any>;
|
||||||
|
|
||||||
|
public abstract updateGraphVisibility(): void;
|
||||||
|
public abstract svgKeyDown(): void;
|
||||||
|
|
||||||
|
constructor(idOrContainer: string | HTMLElement, broker: SelectionBroker,
|
||||||
|
showPhaseByName: (name: string) => void, toolbox: HTMLElement) {
|
||||||
|
super(idOrContainer);
|
||||||
|
this.broker = broker;
|
||||||
|
this.showPhaseByName = showPhaseByName;
|
||||||
|
this.toolbox = toolbox;
|
||||||
|
this.state = new MovableViewState();
|
||||||
|
this.divElement = d3.select(this.divNode);
|
||||||
|
|
||||||
|
// Listen for key events. Note that the focus handler seems
|
||||||
|
// to be important even if it does nothing.
|
||||||
|
this.svg = this.divElement.append("svg")
|
||||||
|
.attr("version", "2.0")
|
||||||
|
.attr("width", "100%")
|
||||||
|
.attr("height", "100%")
|
||||||
|
.on("focus", () => { })
|
||||||
|
.on("keydown", () => this.svgKeyDown());
|
||||||
|
|
||||||
|
this.svg.append("svg:defs")
|
||||||
|
.append("svg:marker")
|
||||||
|
.attr("id", "end-arrow")
|
||||||
|
.attr("viewBox", "0 -4 8 8")
|
||||||
|
.attr("refX", 2)
|
||||||
|
.attr("markerWidth", 2.5)
|
||||||
|
.attr("markerHeight", 2.5)
|
||||||
|
.attr("orient", "auto")
|
||||||
|
.append("svg:path")
|
||||||
|
.attr("d", "M0,-4L8,0L0,4");
|
||||||
|
|
||||||
|
this.graphElement = this.svg.append("g");
|
||||||
|
|
||||||
|
this.panZoom = d3.zoom<SVGElement, any>()
|
||||||
|
.scaleExtent([0.2, 40])
|
||||||
|
.on("zoom", () => {
|
||||||
|
if (d3.event.shiftKey) return false;
|
||||||
|
this.graphElement.attr("transform", d3.event.transform);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.on("start", () => {
|
||||||
|
if (d3.event.shiftKey) return;
|
||||||
|
d3.select("body").style("cursor", "move");
|
||||||
|
})
|
||||||
|
.on("end", () => d3.select("body").style("cursor", "auto"));
|
||||||
|
|
||||||
|
this.svg.call(this.panZoom).on("dblclick.zoom", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createViewElement(): HTMLDivElement {
|
||||||
|
const pane = document.createElement("div");
|
||||||
|
pane.setAttribute("id", C.GRAPH_PANE_ID);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public detachSelection(): Map<string, any> {
|
||||||
|
return this.state.selection.detachSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onresize() {
|
||||||
|
const trans = d3.zoomTransform(this.svg.node());
|
||||||
|
const ctrans = this.panZoom.constrain()(trans, this.getSvgExtent(),
|
||||||
|
this.panZoom.translateExtent());
|
||||||
|
this.panZoom.transform(this.svg, ctrans);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hide(): void {
|
||||||
|
if (this.state.cacheLayout) {
|
||||||
|
const matrix = this.graphElement.node().transform.baseVal.consolidate().matrix;
|
||||||
|
this.graph.graphPhase.transform = { scale: matrix.a, x: matrix.e, y: matrix.f };
|
||||||
|
} else {
|
||||||
|
this.graph.graphPhase.transform = null;
|
||||||
|
}
|
||||||
|
super.hide();
|
||||||
|
this.deleteContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected focusOnSvg(): void {
|
||||||
|
const svg = document.getElementById(C.GRAPH_PANE_ID).childNodes[0] as HTMLElement;
|
||||||
|
svg.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateGraphStateType(stateType: GraphStateType): void {
|
||||||
|
this.graph.graphPhase.stateType = stateType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected viewGraphRegion(minX: number, minY: number,
|
||||||
|
maxX: number, maxY: number): void {
|
||||||
|
const [width, height] = this.getSvgViewDimensions();
|
||||||
|
const dx = maxX - minX;
|
||||||
|
const dy = maxY - minY;
|
||||||
|
const x = (minX + maxX) / 2;
|
||||||
|
const y = (minY + maxY) / 2;
|
||||||
|
const scale = Math.min(width / dx, height / dy) * 0.9;
|
||||||
|
this.svg
|
||||||
|
.transition().duration(120).call(this.panZoom.scaleTo, scale)
|
||||||
|
.transition().duration(120).call(this.panZoom.translateTo, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addImgInput(id: string, title: string, onClick): void {
|
||||||
|
const input = this.createImgInput(id, title, onClick);
|
||||||
|
this.toolbox.appendChild(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addToggleImgInput(id: string, title: string, initState: boolean, onClick): void {
|
||||||
|
const input = this.createImgToggleInput(id, title, initState, onClick);
|
||||||
|
this.toolbox.appendChild(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected minScale(graphWidth: number, graphHeight: number): number {
|
||||||
|
const [clientWith, clientHeight] = this.getSvgViewDimensions();
|
||||||
|
const minXScale = clientWith / (2 * graphWidth);
|
||||||
|
const minYScale = clientHeight / (2 * graphHeight);
|
||||||
|
const minScale = Math.min(minXScale, minYScale);
|
||||||
|
this.panZoom.scaleExtent([minScale, 40]);
|
||||||
|
return minScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getNodeFrontier<NodeType extends Node<any>, EdgeType extends Edge<any>>(
|
||||||
|
nodes: Iterable<NodeType>, inEdges: boolean,
|
||||||
|
edgeFilter: (edge: EdgeType, idx: number) => boolean): Set<NodeType> {
|
||||||
|
const frontier = new Set<NodeType>();
|
||||||
|
let newState = true;
|
||||||
|
const edgeFrontier = this.getEdgeFrontier<EdgeType>(nodes, inEdges, edgeFilter);
|
||||||
|
// Control key toggles edges rather than just turning them on
|
||||||
|
if (d3.event.ctrlKey) {
|
||||||
|
for (const edge of edgeFrontier) {
|
||||||
|
if (edge.visible) newState = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const edge of edgeFrontier) {
|
||||||
|
edge.visible = newState;
|
||||||
|
if (newState) {
|
||||||
|
const node = inEdges ? edge.source : edge.target;
|
||||||
|
node.visible = true;
|
||||||
|
frontier.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateGraphVisibility();
|
||||||
|
return newState ? frontier : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected showSelectionFrontierNodes<EdgeType extends Edge<any>>(
|
||||||
|
inEdges: boolean, filter: (edge: EdgeType, idx: number) => boolean,
|
||||||
|
select: boolean): void {
|
||||||
|
const frontier = this.getNodeFrontier(this.state.selection, inEdges, filter);
|
||||||
|
if (frontier !== undefined && frontier.size) {
|
||||||
|
if (select) {
|
||||||
|
if (!d3.event.shiftKey) this.state.selection.clear();
|
||||||
|
this.state.selection.select([...frontier], true);
|
||||||
|
}
|
||||||
|
this.updateGraphVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getEdgeFrontier<EdgeType extends Edge<any>> (nodes: Iterable<Node<any>>,
|
||||||
|
inEdges: boolean, edgeFilter: (edge: EdgeType, idx: number) => boolean): Set<EdgeType> {
|
||||||
|
const frontier = new Set<EdgeType>();
|
||||||
|
for (const node of nodes) {
|
||||||
|
let edgeNumber = 0;
|
||||||
|
const edges = inEdges ? node.inputs : node.outputs;
|
||||||
|
for (const edge of edges) {
|
||||||
|
if (edgeFilter === undefined || edgeFilter(edge, edgeNumber)) {
|
||||||
|
frontier.add(edge);
|
||||||
|
}
|
||||||
|
++edgeNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frontier;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected connectVisibleSelectedElements(): void {
|
||||||
|
for (const element of this.state.selection) {
|
||||||
|
element.inputs.forEach((edge: Edge<any>) => {
|
||||||
|
if (edge.source.visible && edge.target.visible) {
|
||||||
|
edge.visible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
element.outputs.forEach((edge: Edge<any>) => {
|
||||||
|
if (edge.source.visible && edge.target.visible) {
|
||||||
|
edge.visible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected showVisible() {
|
||||||
|
this.updateGraphVisibility();
|
||||||
|
this.viewWholeGraph();
|
||||||
|
this.focusOnSvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected viewWholeGraph(): void {
|
||||||
|
this.panZoom.scaleTo(this.svg, 0);
|
||||||
|
this.panZoom.translateTo(this.svg,
|
||||||
|
this.graph.minGraphX + this.graph.width / 2,
|
||||||
|
this.graph.minGraphY + this.graph.height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteContent(): void {
|
||||||
|
for (const item of this.toolbox.querySelectorAll(".graph-toolbox-item")) {
|
||||||
|
item.parentElement.removeChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.cacheLayout) {
|
||||||
|
this.updateGraphStateType(GraphStateType.NeedToFullRebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graph.graphPhase.rendered = false;
|
||||||
|
this.updateGraphVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSvgViewDimensions(): [number, number] {
|
||||||
|
return [this.container.clientWidth, this.container.clientHeight];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSvgExtent(): [[number, number], [number, number]] {
|
||||||
|
return [[0, 0], [this.container.clientWidth, this.container.clientHeight]];
|
||||||
|
}
|
||||||
|
|
||||||
|
private createImgInput(id: string, title: string, onClick): HTMLElement {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.setAttribute("id", id);
|
||||||
|
input.setAttribute("type", "image");
|
||||||
|
input.setAttribute("title", title);
|
||||||
|
input.setAttribute("src", `img/toolbox/${id}-icon.png`);
|
||||||
|
input.className = "button-input graph-toolbox-item";
|
||||||
|
input.addEventListener("click", onClick);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createImgToggleInput(id: string, title: string, initState: boolean, onClick):
|
||||||
|
HTMLElement {
|
||||||
|
const input = this.createImgInput(id, title, onClick);
|
||||||
|
input.classList.toggle("button-input-toggled", initState);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MovableViewState {
|
||||||
|
public selection: SelectionMap;
|
||||||
|
|
||||||
|
public get hideDead(): boolean {
|
||||||
|
return storageGetItem("toggle-hide-dead", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set hideDead(value: boolean) {
|
||||||
|
storageSetItem("toggle-hide-dead", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get showTypes(): boolean {
|
||||||
|
return storageGetItem("toggle-types", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set showTypes(value: boolean) {
|
||||||
|
storageSetItem("toggle-types", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get showProperties(): boolean {
|
||||||
|
return storageGetItem("toggle-properties", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set showProperties(value: boolean) {
|
||||||
|
storageSetItem("toggle-properties", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cacheLayout(): boolean {
|
||||||
|
return storageGetItem("toggle-cache-layout", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set cacheLayout(value: boolean) {
|
||||||
|
storageSetItem("toggle-cache-layout", value);
|
||||||
|
}
|
||||||
|
}
|
332
tools/turbolizer/src/views/turboshaft-graph-view.ts
Normal file
332
tools/turbolizer/src/views/turboshaft-graph-view.ts
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import * as d3 from "d3";
|
||||||
|
import * as C from "../common/constants";
|
||||||
|
import { partial } from "../common/util";
|
||||||
|
import { MovableView } from "./movable-view";
|
||||||
|
import { TurboshaftGraphPhase } from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
|
||||||
|
import { SelectionBroker } from "../selection/selection-broker";
|
||||||
|
import { SelectionMap } from "../selection/selection";
|
||||||
|
import { TurboshaftGraphBlock, TurboshaftGraphBlockType } from "../phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||||||
|
import { TurboshaftGraphNode } from "../phases/turboshaft-graph-phase/turboshaft-graph-node";
|
||||||
|
import { TurboshaftGraphEdge } from "../phases/turboshaft-graph-phase/turboshaft-graph-edge";
|
||||||
|
import { TurboshaftGraph } from "../turboshaft-graph";
|
||||||
|
import { TurboshaftGraphLayout } from "../turboshaft-graph-layout";
|
||||||
|
import { GraphStateType } from "../phases/graph-phase/graph-phase";
|
||||||
|
import { OutputVisibilityType } from "../node";
|
||||||
|
import { BlockSelectionHandler, ClearableHandler } from "../selection/selection-handler";
|
||||||
|
|
||||||
|
export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
|
||||||
|
graphLayout: TurboshaftGraphLayout;
|
||||||
|
blocksSelectionHandler: BlockSelectionHandler & ClearableHandler;
|
||||||
|
visibleBlocks: d3.Selection<any, TurboshaftGraphBlock, any, any>;
|
||||||
|
visibleNodes: d3.Selection<any, TurboshaftGraphNode, any, any>;
|
||||||
|
visibleEdges: d3.Selection<any, TurboshaftGraphEdge<TurboshaftGraphBlock>, any, any>;
|
||||||
|
visibleBubbles: d3.Selection<any, any, any, any>;
|
||||||
|
blockDrag: d3.DragBehavior<any, TurboshaftGraphBlock, TurboshaftGraphBlock>;
|
||||||
|
|
||||||
|
constructor(idOrContainer: string | HTMLElement, broker: SelectionBroker,
|
||||||
|
showPhaseByName: (name: string) => void, toolbox: HTMLElement) {
|
||||||
|
super(idOrContainer, broker, showPhaseByName, toolbox);
|
||||||
|
|
||||||
|
this.state.selection = new SelectionMap((b: TurboshaftGraphBlock) => b.identifier());
|
||||||
|
|
||||||
|
this.visibleBlocks = this.graphElement.append("g");
|
||||||
|
this.visibleEdges = this.graphElement.append("g");
|
||||||
|
this.visibleNodes = this.graphElement.append("g");
|
||||||
|
|
||||||
|
this.blockDrag = d3.drag<any, TurboshaftGraphBlock, TurboshaftGraphBlock>()
|
||||||
|
.on("drag", (block: TurboshaftGraphBlock) => {
|
||||||
|
block.x += d3.event.dx;
|
||||||
|
block.y += d3.event.dy;
|
||||||
|
this.updateVisibleBlocks();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeContent(data: TurboshaftGraphPhase, rememberedSelection: Map<string, any>):
|
||||||
|
void {
|
||||||
|
this.show();
|
||||||
|
this.addImgInput("layout", "layout graph",
|
||||||
|
partial(this.layoutAction, this));
|
||||||
|
this.addImgInput("show-all", "show all blocks",
|
||||||
|
partial(this.showAllBlocksAction, this));
|
||||||
|
this.addToggleImgInput("toggle-properties", "toggle propetries",
|
||||||
|
this.state.showProperties, partial(this.togglePropertiesAction, this));
|
||||||
|
this.addToggleImgInput("toggle-cache-layout", "toggle saving graph layout",
|
||||||
|
this.state.cacheLayout, partial(this.toggleLayoutCachingAction, this));
|
||||||
|
|
||||||
|
this.phaseName = data.name;
|
||||||
|
this.createGraph(data, rememberedSelection);
|
||||||
|
|
||||||
|
this.viewWholeGraph();
|
||||||
|
if (this.state.cacheLayout && data.transform) {
|
||||||
|
this.svg.call(this.panZoom.transform, d3.zoomIdentity
|
||||||
|
.translate(data.transform.x, data.transform.y)
|
||||||
|
.scale(data.transform.scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateGraphVisibility(): void {
|
||||||
|
if (!this.graph) return;
|
||||||
|
this.updateVisibleBlocks();
|
||||||
|
this.visibleNodes = d3.selectAll(".turboshaft-node");
|
||||||
|
this.visibleBubbles = d3.selectAll("circle");
|
||||||
|
this.updateInlineNodes();
|
||||||
|
this.updateInputAndOutputBubbles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public svgKeyDown(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
private createGraph(data: TurboshaftGraphPhase, selection) {
|
||||||
|
this.graph = new TurboshaftGraph(data);
|
||||||
|
this.graphLayout = new TurboshaftGraphLayout(this.graph);
|
||||||
|
|
||||||
|
if (!this.state.cacheLayout ||
|
||||||
|
this.graph.graphPhase.stateType == GraphStateType.NeedToFullRebuild) {
|
||||||
|
this.updateGraphStateType(GraphStateType.NeedToFullRebuild);
|
||||||
|
this.showAllBlocksAction(this);
|
||||||
|
} else {
|
||||||
|
this.showVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layoutGraph();
|
||||||
|
this.updateGraphVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private layoutGraph(): void {
|
||||||
|
const layoutMessage = this.graph.graphPhase.stateType == GraphStateType.Cached
|
||||||
|
? "Layout graph from cache"
|
||||||
|
: "Layout graph";
|
||||||
|
|
||||||
|
console.time(layoutMessage);
|
||||||
|
this.graphLayout.rebuild(this.state.showProperties);
|
||||||
|
const extent = this.graph.redetermineGraphBoundingBox(this.state.showProperties);
|
||||||
|
this.panZoom.translateExtent(extent);
|
||||||
|
this.minScale(this.graph.width, this.graph.height);
|
||||||
|
console.timeEnd(layoutMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateVisibleBlocks(): void {
|
||||||
|
const view = this;
|
||||||
|
// select existing blocks
|
||||||
|
const filteredBlocks = [
|
||||||
|
...this.graph.blocks(block => this.graph.isRendered() && block.visible)
|
||||||
|
];
|
||||||
|
const allBlocks = view.visibleBlocks
|
||||||
|
.selectAll<SVGGElement, TurboshaftGraphBlock>(".turboshaft-block");
|
||||||
|
const selBlocks = allBlocks.data(filteredBlocks, block => block.toString());
|
||||||
|
|
||||||
|
// remove old blocks
|
||||||
|
selBlocks.exit().remove();
|
||||||
|
|
||||||
|
// add new blocks
|
||||||
|
const newBlocks = selBlocks
|
||||||
|
.enter()
|
||||||
|
.append("g")
|
||||||
|
.classed("turboshaft-block", true)
|
||||||
|
.classed("block", b => b.type == TurboshaftGraphBlockType.Block)
|
||||||
|
.classed("merge", b => b.type == TurboshaftGraphBlockType.Merge)
|
||||||
|
.classed("loop", b => b.type == TurboshaftGraphBlockType.Loop)
|
||||||
|
.call(view.blockDrag);
|
||||||
|
|
||||||
|
newBlocks
|
||||||
|
.append("rect")
|
||||||
|
.attr("rx", 35)
|
||||||
|
.attr("ry", 35)
|
||||||
|
.attr("width", block => block.getWidth())
|
||||||
|
.attr("height", block => block.getHeight(view.state.showProperties));
|
||||||
|
|
||||||
|
newBlocks.each(function (block: TurboshaftGraphBlock) {
|
||||||
|
const svg = d3.select<SVGGElement, TurboshaftGraphBlock>(this);
|
||||||
|
svg
|
||||||
|
.append("text")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("x", block.getWidth() / 2)
|
||||||
|
.classed("block-label", true)
|
||||||
|
.append("tspan")
|
||||||
|
.text(block.displayLabel);
|
||||||
|
view.appendInlineNodes(svg, block);
|
||||||
|
view.appendInputAndOutputBubbles(svg, block);
|
||||||
|
});
|
||||||
|
|
||||||
|
newBlocks.merge(selBlocks)
|
||||||
|
.classed("selected", block => view.state.selection.isSelected(block))
|
||||||
|
.attr("transform", block => `translate(${block.x},${block.y})`)
|
||||||
|
.select("rect")
|
||||||
|
.attr("height", block => block.getHeight(view.state.showProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendInlineNodes(svg: d3.Selection<SVGGElement, TurboshaftGraphBlock, any, any>,
|
||||||
|
block: TurboshaftGraphBlock): void {
|
||||||
|
const state = this.state;
|
||||||
|
const graph = this.graph;
|
||||||
|
const filteredNodes = [...block.nodes.filter(node => graph.isRendered() && node.visible)];
|
||||||
|
const allNodes = svg.selectAll<SVGGElement, TurboshaftGraphNode>(".turboshaft-inline-node");
|
||||||
|
const selNodes = allNodes.data(filteredNodes, node => node.toString());
|
||||||
|
|
||||||
|
// remove old nodes
|
||||||
|
selNodes.exit().remove();
|
||||||
|
|
||||||
|
// add new nodes
|
||||||
|
const newNodes = selNodes
|
||||||
|
.enter()
|
||||||
|
.append("g")
|
||||||
|
.classed("turboshaft-node", true)
|
||||||
|
.classed("inline-node", true);
|
||||||
|
|
||||||
|
let nodeY = block.labelBox.height;
|
||||||
|
const blockWidth = block.getWidth();
|
||||||
|
newNodes.each(function (node: TurboshaftGraphNode) {
|
||||||
|
const nodeSvg = d3.select(this);
|
||||||
|
nodeSvg
|
||||||
|
.attr("id", node.id)
|
||||||
|
.append("text")
|
||||||
|
.attr("dx", 25)
|
||||||
|
.classed("inline-node-label", true)
|
||||||
|
.attr("dy", nodeY)
|
||||||
|
.append("tspan")
|
||||||
|
.text(`${node.getInlineLabel()}[${node.getPropertiesTypeAbbreviation()}]`);
|
||||||
|
nodeY += node.labelBox.height;
|
||||||
|
if (node.properties) {
|
||||||
|
nodeSvg
|
||||||
|
.append("text")
|
||||||
|
.attr("dx", 25)
|
||||||
|
.classed("inline-node-properties", true)
|
||||||
|
.attr("dy", nodeY)
|
||||||
|
.append("tspan")
|
||||||
|
.text(node.getReadableProperties(blockWidth))
|
||||||
|
.append("title")
|
||||||
|
.text(node.properties);
|
||||||
|
nodeY += node.labelBox.height;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newNodes.merge(selNodes)
|
||||||
|
.classed("selected", node => state.selection.isSelected(node))
|
||||||
|
.select("rect")
|
||||||
|
.attr("height", node => node.getHeight(state.showProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateInputAndOutputBubbles(): void {
|
||||||
|
const view = this;
|
||||||
|
const graph = this.graph;
|
||||||
|
this.visibleBubbles.classed("filledBubbleStyle", function () {
|
||||||
|
const components = this.id.split(",");
|
||||||
|
if (components[0] === "ib") {
|
||||||
|
return graph.blockMap[components[3]].inputs[components[2]].isVisible();
|
||||||
|
}
|
||||||
|
return graph.blockMap[components[1]].areAnyOutputsVisible()
|
||||||
|
== OutputVisibilityType.AllNodesVisible;
|
||||||
|
}).classed("halfFilledBubbleStyle", function () {
|
||||||
|
const components = this.id.split(",");
|
||||||
|
if (components[0] === "ib") return false;
|
||||||
|
return graph.blockMap[components[1]].areAnyOutputsVisible()
|
||||||
|
== OutputVisibilityType.SomeNodesVisible;
|
||||||
|
}).classed("bubbleStyle", function () {
|
||||||
|
const components = this.id.split(",");
|
||||||
|
if (components[0] === "ib") {
|
||||||
|
return !graph.blockMap[components[3]].inputs[components[2]].isVisible();
|
||||||
|
}
|
||||||
|
return graph.blockMap[components[1]].areAnyOutputsVisible()
|
||||||
|
== OutputVisibilityType.NoVisibleNodes;
|
||||||
|
});
|
||||||
|
this.visibleBubbles.each(function () {
|
||||||
|
const components = this.id.split(",");
|
||||||
|
if (components[0] === "ob") {
|
||||||
|
const from = graph.blockMap[components[1]];
|
||||||
|
const x = from.getOutputX();
|
||||||
|
const y = from.getHeight(view.state.showProperties) + C.DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
|
this.setAttribute("transform", `translate(${x},${y})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendInputAndOutputBubbles(
|
||||||
|
svg: d3.Selection<SVGGElement, TurboshaftGraphBlock, any, any>,
|
||||||
|
block: TurboshaftGraphBlock): void {
|
||||||
|
for (let i = 0; i < block.inputs.length; i++) {
|
||||||
|
const x = block.getInputX(i);
|
||||||
|
const y = -C.DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
|
svg.append("circle")
|
||||||
|
.classed("filledBubbleStyle", block.inputs[i].isVisible())
|
||||||
|
.classed("bubbleStyle", !block.inputs[i].isVisible())
|
||||||
|
.attr("id", `ib,${block.inputs[i].toString(i)}`)
|
||||||
|
.attr("r", C.DEFAULT_NODE_BUBBLE_RADIUS)
|
||||||
|
.attr("transform", `translate(${x},${y})`);
|
||||||
|
}
|
||||||
|
if (block.outputs.length > 0) {
|
||||||
|
const x = block.getOutputX();
|
||||||
|
const y = block.getHeight(this.state.showProperties) + C.DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
|
svg.append("circle")
|
||||||
|
.classed("filledBubbleStyle", block.areAnyOutputsVisible()
|
||||||
|
== OutputVisibilityType.AllNodesVisible)
|
||||||
|
.classed("halFilledBubbleStyle", block.areAnyOutputsVisible()
|
||||||
|
== OutputVisibilityType.SomeNodesVisible)
|
||||||
|
.classed("bubbleStyle", block.areAnyOutputsVisible()
|
||||||
|
== OutputVisibilityType.NoVisibleNodes)
|
||||||
|
.attr("id", `ob,${block.id}`)
|
||||||
|
.attr("r", C.DEFAULT_NODE_BUBBLE_RADIUS)
|
||||||
|
.attr("transform", `translate(${x},${y})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateInlineNodes(): void {
|
||||||
|
const state = this.state;
|
||||||
|
let totalHeight = 0;
|
||||||
|
let blockId = 0;
|
||||||
|
this.visibleNodes.each(function (node: TurboshaftGraphNode) {
|
||||||
|
if (blockId != node.block.id) {
|
||||||
|
blockId = node.block.id;
|
||||||
|
totalHeight = 0;
|
||||||
|
}
|
||||||
|
totalHeight += node.getHeight(state.showProperties);
|
||||||
|
const nodeSvg = d3.select(this);
|
||||||
|
const nodeY = state.showProperties && node.properties
|
||||||
|
? totalHeight - node.labelBox.height
|
||||||
|
: totalHeight;
|
||||||
|
nodeSvg.select(".inline-node-label").attr("dy", nodeY);
|
||||||
|
nodeSvg.select(".inline-node-properties").attr("visibility", state.showProperties
|
||||||
|
? "visible"
|
||||||
|
: "hidden");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions (handlers of toolbox menu and hotkeys events)
|
||||||
|
private layoutAction(view: TurboshaftGraphView): void {
|
||||||
|
view.updateGraphStateType(GraphStateType.NeedToFullRebuild);
|
||||||
|
view.layoutGraph();
|
||||||
|
view.updateGraphVisibility();
|
||||||
|
view.viewWholeGraph();
|
||||||
|
view.focusOnSvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
private showAllBlocksAction(view: TurboshaftGraphView): void {
|
||||||
|
for (const node of view.graph.blocks()) {
|
||||||
|
node.visible = true;
|
||||||
|
}
|
||||||
|
for (const edge of view.graph.blocksEdges()) {
|
||||||
|
edge.visible = true;
|
||||||
|
}
|
||||||
|
view.showVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
private togglePropertiesAction(view: TurboshaftGraphView): void {
|
||||||
|
view.state.showProperties = !view.state.showProperties;
|
||||||
|
const element = document.getElementById("toggle-properties");
|
||||||
|
element.classList.toggle("button-input-toggled", view.state.showProperties);
|
||||||
|
view.updateGraphVisibility();
|
||||||
|
view.focusOnSvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleLayoutCachingAction(view: TurboshaftGraphView): void {
|
||||||
|
view.state.cacheLayout = !view.state.cacheLayout;
|
||||||
|
const element = document.getElementById("toggle-cache-layout");
|
||||||
|
element.classList.toggle("button-input-toggled", view.state.cacheLayout);
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,12 @@
|
|||||||
"src/views/disassembly-view.ts",
|
"src/views/disassembly-view.ts",
|
||||||
"src/views/text-view.ts",
|
"src/views/text-view.ts",
|
||||||
"src/views/info-view.ts",
|
"src/views/info-view.ts",
|
||||||
|
"src/views/movable-view.ts",
|
||||||
|
"src/views/turboshaft-graph-view.ts",
|
||||||
"src/origin.ts",
|
"src/origin.ts",
|
||||||
|
"src/movable-container.ts",
|
||||||
|
"src/turboshaft-graph.ts",
|
||||||
|
"src/turboshaft-graph-layout.ts",
|
||||||
"src/position.ts",
|
"src/position.ts",
|
||||||
"src/source.ts",
|
"src/source.ts",
|
||||||
"src/node.ts",
|
"src/node.ts",
|
||||||
|
Loading…
Reference in New Issue
Block a user