[turbolizer] Turboshaft custom blocks/nodes data

Bug: v8:7327
Change-Id: I41faceac568a87cec4ae47ce2e4fc2c03822ddca
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3794649
Commit-Queue: Danylo Boiko <danielboyko02@gmail.com>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82421}
This commit is contained in:
Danylo Boiko 2022-08-11 21:40:49 +03:00 committed by V8 LUCI CQ
parent 573084572a
commit e6804d0181
17 changed files with 305 additions and 113 deletions

View File

@ -165,6 +165,7 @@ body {
display: flex;
flex-direction: row;
width: 100vw;
height: 100vh;
}
p {
@ -357,8 +358,11 @@ input:hover,
display: inline-flex;
}
.viewpane {
#multiview {
height: 100vh;
}
.viewpane {
background-color: #FFFFFF;
display: flex;
flex-direction: column;
@ -792,3 +796,7 @@ svg.history-svg-container .history-item-record tspan:hover {
fill: #606060;
cursor: pointer;
}
svg.history-svg-container .current-history-item tspan {
font-weight: bold;
}

View File

@ -73,8 +73,8 @@ g.loop rect {
fill: var(--select);
}
.inline-node-properties tspan, .block-collapsed-label tspan {
fill: #ca48f6;
.inline-node-custom-data tspan, .block-collapsed-label tspan {
fill: #c34ce8;
}
g.turboshaft-node.input .inline-node-label tspan {
@ -85,8 +85,8 @@ g.turboshaft-node.output .inline-node-label tspan {
fill: var(--output);
}
#layout-type-select {
box-sizing: border-box;
#custom-data-select {
height: 1.5em;
margin-left: 5px;
margin: 0 3px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

View File

@ -20,6 +20,7 @@ export abstract class Phase {
export enum PhaseType {
Graph = "graph",
TurboshaftGraph = "turboshaft_graph",
TurboshaftCustomData = "turboshaft_custom_data",
Disassembly = "disassembly",
Instructions = "instructions",
Sequence = "sequence",

View File

@ -0,0 +1,29 @@
// 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 { Phase, PhaseType } from "./phase";
export class TurboshaftCustomDataPhase extends Phase {
dataTarget: DataTarget;
data: Array<string>;
constructor(name: string, dataTarget: DataTarget, dataJSON) {
super(name, PhaseType.TurboshaftCustomData);
this.dataTarget = dataTarget;
this.data = new Array<string>();
this.parseDataFromJSON(dataJSON);
}
private parseDataFromJSON(dataJSON): void {
if (!dataJSON) return;
for (const item of dataJSON) {
this.data[item.key] = item.value;
}
}
}
export enum DataTarget {
Nodes = "operations",
Blocks = "blocks"
}

View File

@ -13,7 +13,7 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
deferred: boolean;
predecessors: Array<string>;
nodes: Array<TurboshaftGraphNode>;
showProperties: boolean;
showCustomData: boolean;
collapsed: boolean;
collapsedLabel: string;
collapsedLabelBox: { width: number, height: number };
@ -30,14 +30,14 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
this.visible = true;
}
public getHeight(showProperties: boolean): number {
public getHeight(showCustomData: boolean): number {
if (this.collapsed) return this.labelBox.height + this.collapsedLabelBox.height;
if (this.showProperties != showProperties) {
if (this.showCustomData != showCustomData) {
this.height = this.nodes.reduce<number>((accumulator: number, node: TurboshaftGraphNode) => {
return accumulator + node.getHeight(showProperties);
return accumulator + node.getHeight(showCustomData);
}, this.labelBox.height);
this.showProperties = showProperties;
this.showCustomData = showCustomData;
}
return this.height;
@ -58,7 +58,7 @@ export class TurboshaftGraphBlock extends Node<TurboshaftGraphEdge<TurboshaftGra
public compressHeight(): void {
if (this.collapsed) {
this.height = this.getHeight(null);
this.showProperties = null;
this.showCustomData = null;
}
}

View File

@ -12,32 +12,25 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
title: string;
block: TurboshaftGraphBlock;
opPropertiesType: OpPropertiesType;
properties: string;
propertiesBox: { width: number, height: number };
constructor(id: number, title: string, block: TurboshaftGraphBlock,
opPropertiesType: OpPropertiesType, properties: string) {
opPropertiesType: OpPropertiesType) {
super(id);
this.title = title;
this.block = block;
this.opPropertiesType = opPropertiesType;
this.properties = properties;
this.propertiesBox = measureText(this.properties);
this.visible = true;
}
public getHeight(showProperties: boolean): number {
if (this.properties && showProperties) {
return this.labelBox.height + this.propertiesBox.height;
}
return this.labelBox.height;
public getHeight(showCustomData: boolean): number {
return showCustomData ? this.labelBox.height * 2 : this.labelBox.height;
}
public getWidth(): number {
return Math.max(this.inputs.length * C.NODE_INPUT_WIDTH, this.labelBox.width);
}
public initDisplayLabel() {
public initDisplayLabel(): void {
this.displayLabel = this.getInlineLabel();
this.labelBox = measureText(this.displayLabel);
}
@ -50,21 +43,13 @@ export class TurboshaftGraphNode extends Node<TurboshaftGraphEdge<TurboshaftGrap
if (this.outputs.length > 0) {
title += `\nOutputs: ${this.outputs.map(i => i.target.id).join(", ")}`;
}
const opPropertiesStr = this.properties.length > 0 ? this.properties : "No op properties";
return `${title}\n${opPropertiesStr}`;
return title;
}
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 {
if (blockWidth > this.propertiesBox.width) return this.properties;
const widthOfOneSymbol = Math.floor(this.propertiesBox.width / this.properties.length);
const lengthOfReadableProperties = Math.floor(blockWidth / widthOfOneSymbol);
return `${this.properties.slice(0, lengthOfReadableProperties - 3)}..`;
}
}
export enum OpPropertiesType {

View File

@ -6,19 +6,22 @@ import { GraphStateType, Phase, PhaseType } from "../phase";
import { TurboshaftGraphNode } from "./turboshaft-graph-node";
import { TurboshaftGraphEdge } from "./turboshaft-graph-edge";
import { TurboshaftGraphBlock } from "./turboshaft-graph-block";
import { DataTarget, TurboshaftCustomDataPhase } from "../turboshaft-custom-data-phase";
export class TurboshaftGraphPhase extends Phase {
data: TurboshaftGraphData;
customData: TurboshaftCustomData;
stateType: GraphStateType;
nodeIdToNodeMap: Array<TurboshaftGraphNode>;
blockIdToBlockMap: Array<TurboshaftGraphBlock>;
rendered: boolean;
propertiesShowed: boolean;
customDataShowed: boolean;
transform: { x: number, y: number, scale: number };
constructor(name: string, dataJson) {
super(name, PhaseType.TurboshaftGraph);
this.stateType = GraphStateType.NeedToFullRebuild;
this.customData = new TurboshaftCustomData();
this.nodeIdToNodeMap = new Array<TurboshaftGraphNode>();
this.blockIdToBlockMap = new Array<TurboshaftGraphBlock>();
this.rendered = false;
@ -53,7 +56,7 @@ export class TurboshaftGraphPhase extends Phase {
for (const nodeJson of nodesJson) {
const block = this.blockIdToBlockMap[nodeJson.block_id];
const node = new TurboshaftGraphNode(nodeJson.id, nodeJson.title,
block, nodeJson.op_properties_type, nodeJson.properties);
block, nodeJson.op_properties_type);
block.nodes.push(node);
this.data.nodes.push(node);
this.nodeIdToNodeMap[node.identifier()] = node;
@ -89,3 +92,43 @@ export class TurboshaftGraphData {
this.blocks = new Array<TurboshaftGraphBlock>();
}
}
export class TurboshaftCustomData {
nodes: Map<string, TurboshaftCustomDataPhase>;
blocks: Map<string, TurboshaftCustomDataPhase>;
constructor() {
this.nodes = new Map<string, TurboshaftCustomDataPhase>();
this.blocks = new Map<string, TurboshaftCustomDataPhase>();
}
public addCustomData(customDataPhase: TurboshaftCustomDataPhase): void {
switch (customDataPhase.dataTarget) {
case DataTarget.Nodes:
this.nodes.set(customDataPhase.name, customDataPhase);
break;
case DataTarget.Blocks:
this.blocks.set(customDataPhase.name, customDataPhase);
break;
default:
throw "Unsupported turboshaft custom data target type";
}
}
public getTitle(key: number, dataTarget: DataTarget): string {
switch (dataTarget) {
case DataTarget.Nodes:
return this.concatCustomData(key, this.nodes);
case DataTarget.Blocks:
return this.concatCustomData(key, this.blocks);
}
}
private concatCustomData(key: number, items: Map<string, TurboshaftCustomDataPhase>): string {
let customData = "";
for (const [name, dataPhase] of items.entries()) {
customData += `\n${name}: ${dataPhase.data[key] ?? ""}`;
}
return customData;
}
}

View File

@ -13,11 +13,12 @@ import { SequencePhase } from "./phases/sequence-phase";
import { BytecodeOrigin } from "./origin";
import { Source } from "./source";
import { NodeLabel } from "./node-label";
import { TurboshaftCustomDataPhase } from "./phases/turboshaft-custom-data-phase";
import { TurboshaftGraphPhase } from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
export type GenericPosition = SourcePosition | BytecodePosition;
export type GenericPhase = GraphPhase | TurboshaftGraphPhase | DisassemblyPhase
| InstructionsPhase | SchedulePhase | SequencePhase;
export type GenericPhase = GraphPhase | TurboshaftGraphPhase | TurboshaftCustomDataPhase
| DisassemblyPhase | InstructionsPhase | SchedulePhase | SequencePhase;
export class SourceResolver {
nodePositionMap: Array<GenericPosition>;
@ -129,6 +130,7 @@ export class SourceResolver {
public parsePhases(phasesJson): void {
const nodeLabelMap = new Array<NodeLabel>();
let lastTurboshaftGraphPhase: TurboshaftGraphPhase = null;
for (const [, genericPhase] of Object.entries<GenericPhase>(phasesJson)) {
switch (genericPhase.type) {
case PhaseType.Disassembly:
@ -179,6 +181,13 @@ export class SourceResolver {
castedTurboshaftGraph.data);
this.phaseNames.set(turboshaftGraphPhase.name, this.phases.length);
this.phases.push(turboshaftGraphPhase);
lastTurboshaftGraphPhase = turboshaftGraphPhase;
break;
case PhaseType.TurboshaftCustomData:
const castedCustomData = camelize(genericPhase) as TurboshaftCustomDataPhase;
const customDataPhase = new TurboshaftCustomDataPhase(castedCustomData.name,
castedCustomData.dataTarget, castedCustomData.data);
lastTurboshaftGraphPhase?.customData?.addCustomData(customDataPhase);
break;
default:
throw "Unsupported phase type";

View File

@ -102,7 +102,7 @@ window.onload = function () {
historyView.show();
} catch (err) {
if (window.confirm("Error: Exception during load of TurboFan JSON file:\n" +
`error: ${err.message} \nDo you want to clear session storage?`)) {
`error: ${err} \nDo you want to clear session storage?`)) {
window.sessionStorage.clear();
}
return;

View File

@ -21,10 +21,10 @@ export class TurboshaftGraphLayout {
this.maxRank = 0;
}
public rebuild(showProperties: boolean): void {
public rebuild(showCustomData: boolean): void {
switch (this.graph.graphPhase.stateType) {
case GraphStateType.NeedToFullRebuild:
this.fullRebuild(showProperties);
this.fullRebuild(showCustomData);
break;
case GraphStateType.Cached:
this.cachedRebuild();
@ -35,7 +35,7 @@ export class TurboshaftGraphLayout {
this.graph.graphPhase.rendered = true;
}
private fullRebuild(showProperties: boolean): void {
private fullRebuild(showCustomData: boolean): void {
this.startTime = performance.now();
this.maxRank = 0;
this.visitOrderWithinRank = 0;
@ -46,8 +46,8 @@ export class TurboshaftGraphLayout {
const visited = new Array<boolean>();
blocks.forEach((block: TurboshaftGraphBlock) => this.dfsRankOrder(visited, block));
const rankSets = this.getRankSets(showProperties);
this.placeBlocks(rankSets, showProperties);
const rankSets = this.getRankSets(showCustomData);
this.placeBlocks(rankSets, showCustomData);
this.calculateBackEdgeNumbers();
this.graph.graphPhase.stateType = GraphStateType.Cached;
}
@ -133,8 +133,8 @@ export class TurboshaftGraphLayout {
}
}
private getRankSets(showProperties: boolean): Array<Array<TurboshaftGraphBlock>> {
const ranksMaxBlockHeight = this.graph.getRanksMaxBlockHeight(showProperties);
private getRankSets(showCustomData: boolean): Array<Array<TurboshaftGraphBlock>> {
const ranksMaxBlockHeight = this.graph.getRanksMaxBlockHeight(showCustomData);
const rankSets = new Array<Array<TurboshaftGraphBlock>>();
for (const block of this.graph.blocks()) {
block.y = ranksMaxBlockHeight.slice(1, block.rank).reduce<number>((accumulator, current) => {
@ -149,13 +149,13 @@ export class TurboshaftGraphLayout {
return rankSets;
}
private placeBlocks(rankSets: Array<Array<TurboshaftGraphBlock>>, showProperties: boolean): void {
private placeBlocks(rankSets: Array<Array<TurboshaftGraphBlock>>, showCustomData: boolean): void {
// Iterate backwards from highest to lowest rank, placing blocks so that they
// spread out from the "center" as much as possible while still being
// compact and not overlapping live input lines.
rankSets.reverse().forEach((rankSet: Array<TurboshaftGraphBlock>) => {
for (const block of rankSet) {
this.layoutOccupation.clearOutputs(block, showProperties);
this.layoutOccupation.clearOutputs(block, showCustomData);
}
this.traceOccupation("After clearing outputs");
@ -178,7 +178,7 @@ export class TurboshaftGraphLayout {
this.traceOccupation("After clearing blocks");
for (const block of rankSet) {
this.layoutOccupation.occupyInputs(block, showProperties);
this.layoutOccupation.occupyInputs(block, showCustomData);
}
this.traceOccupation("After occupying inputs and determining bounding box");

View File

@ -4,19 +4,25 @@
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";
import { DataTarget } from "./phases/turboshaft-custom-data-phase";
import {
TurboshaftCustomData,
TurboshaftGraphPhase
} from "./phases/turboshaft-graph-phase/turboshaft-graph-phase";
export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
blockMap: Array<TurboshaftGraphBlock>;
nodeMap: Array<TurboshaftGraphNode>;
customData: TurboshaftCustomData;
constructor(graphPhase: TurboshaftGraphPhase) {
super(graphPhase);
this.blockMap = graphPhase.blockIdToBlockMap;
this.nodeMap = graphPhase.nodeIdToNodeMap;
this.customData = graphPhase.customData;
}
public *blocks(func = (b: TurboshaftGraphBlock) => true) {
@ -53,7 +59,7 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
}
}
public redetermineGraphBoundingBox(showProperties: boolean):
public redetermineGraphBoundingBox(showCustomData: boolean):
[[number, number], [number, number]] {
this.minGraphX = 0;
this.maxGraphNodeX = 1;
@ -65,7 +71,7 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
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)
this.maxGraphY = Math.max(this.maxGraphY, block.y + block.getHeight(showCustomData)
+ C.NODE_INPUT_WIDTH);
}
@ -80,12 +86,30 @@ export class TurboshaftGraph extends MovableContainer<TurboshaftGraphPhase> {
];
}
public getRanksMaxBlockHeight(showProperties: boolean): Array<number> {
public hasCustomData(customData: string, dataTarget: DataTarget): boolean {
switch (dataTarget) {
case DataTarget.Nodes:
return this.customData.nodes.has(customData);
case DataTarget.Blocks:
return this.customData.blocks.has(customData);
}
}
public getCustomData(customData: string, key: number, dataTarget: DataTarget): string {
switch (dataTarget) {
case DataTarget.Nodes:
return this.customData.nodes.get(customData).data[key];
case DataTarget.Blocks:
return this.customData.blocks.get(customData).data[key];
}
}
public getRanksMaxBlockHeight(showCustomData: boolean): Array<number> {
const ranksMaxBlockHeight = new Array<number>();
for (const block of this.blocks()) {
ranksMaxBlockHeight[block.rank] = Math.max(ranksMaxBlockHeight[block.rank] ?? 0,
block.getHeight(showProperties));
block.getHeight(showCustomData));
}
return ranksMaxBlockHeight;

View File

@ -181,6 +181,7 @@ export class HistoryView extends View {
this.historyList
.append("text")
.classed("history-item history-item-record", true)
.classed("current-history-item", record.changes.has(HistoryChange.Current))
.attr("dy", recordY)
.attr("dx", this.labelBox.height * 0.75)
.append("tspan")
@ -389,7 +390,11 @@ export class HistoryView extends View {
this.phaseIdToHistory.set(phaseId, new PhaseHistory(phaseId));
}
this.phaseIdToHistory.get(phaseId).addChange(node, change);
this.maxNodeWidth = Math.max(this.maxNodeWidth, node.labelBox.width);
if (change == HistoryChange.Current) {
this.maxNodeWidth = Math.max(this.maxNodeWidth, node.labelBox.width * 1.07);
} else {
this.maxNodeWidth = Math.max(this.maxNodeWidth, node.labelBox.width);
}
}
private clear(): void {

View File

@ -252,6 +252,13 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
})];
}
protected 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;
}
private deleteContent(): void {
for (const item of this.toolbox.querySelectorAll(".graph-toolbox-item")) {
item.parentElement.removeChild(item);
@ -283,13 +290,6 @@ export abstract class MovableView<GraphType extends Graph | TurboshaftGraph> ext
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 {
@ -312,12 +312,12 @@ export class MovableViewState {
storageSetItem("toggle-types", value);
}
public get showProperties(): boolean {
return storageGetItem("toggle-properties", false);
public get showCustomData(): boolean {
return storageGetItem("toggle-custom-data", false);
}
public set showProperties(value: boolean) {
storageSetItem("toggle-properties", value);
public set showCustomData(value: boolean) {
storageSetItem("toggle-custom-data", value);
}
public get cacheLayout(): boolean {

View File

@ -4,7 +4,13 @@
import * as C from "../common/constants";
import * as d3 from "d3";
import { copyToClipboard, partial, storageSetItem } from "../common/util";
import {
copyToClipboard,
measureText,
partial,
storageGetItem,
storageSetItem
} from "../common/util";
import { MovableView } from "./movable-view";
import { SelectionBroker } from "../selection/selection-broker";
import { SelectionMap } from "../selection/selection-map";
@ -14,7 +20,11 @@ import { TurboshaftGraph } from "../turboshaft-graph";
import { TurboshaftGraphLayout } from "../turboshaft-graph-layout";
import { GraphStateType } from "../phases/graph-phase/graph-phase";
import { SelectionStorage } from "../selection/selection-storage";
import { TurboshaftGraphPhase } from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
import { DataTarget } from "../phases/turboshaft-custom-data-phase";
import {
TurboshaftCustomData,
TurboshaftGraphPhase
} from "../phases/turboshaft-graph-phase/turboshaft-graph-phase";
import {
BlockSelectionHandler,
ClearableHandler,
@ -79,12 +89,12 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
partial(this.changeSelectedCollapsingAction, this, false));
this.addImgInput("zoom-selection", "zoom selection",
partial(this.zoomSelectionAction, this));
this.addToggleImgInput("toggle-properties", "toggle properties",
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.addCustomDataSelect(data.customData);
const adaptedSelection = this.createGraph(data, rememberedSelection);
this.broker.addNodeHandler(this.nodeSelectionHandler);
this.broker.addBlockHandler(this.blockSelectionHandler);
@ -100,7 +110,8 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
}
if (this.graphLayout.graph.graphPhase.propertiesShowed != this.state.showProperties) {
const customDataShowed = this.graph.graphPhase.customDataShowed;
if (customDataShowed != null && customDataShowed != this.nodesCustomDataShowed()) {
this.compressLayoutAction(this);
}
}
@ -181,9 +192,9 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const reg = new RegExp(query);
const filterFunction = (node: TurboshaftGraphNode) => {
if (!onlyVisible) node.block.collapsed = false;
return (!onlyVisible || !node.block.collapsed) && (reg.exec(node.displayLabel) !== null ||
(this.state.showProperties && reg.exec(node.properties)) ||
reg.exec(node.getTitle()));
const customDataTitle = this.graph.customData.getTitle(node.id, DataTarget.Nodes);
return (!onlyVisible || !node.block.collapsed) &&
reg.exec(`${node.getTitle()}${customDataTitle}`);
};
const selection = this.searchNodes(filterFunction, e, onlyVisible);
@ -198,7 +209,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
public hide(): void {
this.graphLayout.graph.graphPhase.propertiesShowed = this.state.showProperties;
this.graph.graphPhase.customDataShowed = this.nodesCustomDataShowed();
this.broker.deleteBlockHandler(this.blockSelectionHandler);
super.hide();
}
@ -317,13 +328,51 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
: "Layout turboshaft graph";
console.time(layoutMessage);
this.graphLayout.rebuild(this.state.showProperties);
const extent = this.graph.redetermineGraphBoundingBox(this.state.showProperties);
this.graphLayout.rebuild(this.nodesCustomDataShowed());
const extent = this.graph.redetermineGraphBoundingBox(this.nodesCustomDataShowed());
this.panZoom.translateExtent(extent);
this.minScale();
console.timeEnd(layoutMessage);
}
private addCustomDataSelect(customData: TurboshaftCustomData): void {
const keys = Array.from(customData.nodes.keys());
if (keys.length == 0) return;
const select = document.createElement("select") as HTMLSelectElement;
select.setAttribute("id", "custom-data-select");
select.setAttribute("class", "graph-toolbox-item");
select.setAttribute("title", "custom data");
const checkBox = this.createImgToggleInput("toggle-custom-data",
"toggle custom data visibility", this.state.showCustomData,
partial(this.toggleCustomDataAction, this));
for (const key of keys) {
const option = document.createElement("option");
option.text = key;
select.add(option);
}
const storageKey = this.customDataStorageKey();
const indexOfSelected = keys.indexOf(storageGetItem(storageKey, null, false));
if (indexOfSelected != -1) {
select.selectedIndex = indexOfSelected;
} else {
storageSetItem(storageKey, keys[0]);
}
const view = this;
select.onchange = function (this: HTMLSelectElement) {
const selectedCustomData = select.options[this.selectedIndex].text;
storageSetItem(storageKey, selectedCustomData);
view.updateGraphVisibility();
};
this.toolbox.appendChild(select);
this.toolbox.appendChild(checkBox);
}
private updateBlockLocation(block: TurboshaftGraphBlock): void {
this.visibleBlocks
.selectAll<SVGGElement, TurboshaftGraphBlock>(".turboshaft-block")
@ -333,7 +382,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
this.visibleEdges
.selectAll<SVGPathElement, TurboshaftGraphEdge<TurboshaftGraphBlock>>("path")
.filter(edge => edge.target === block || edge.source === block)
.attr("d", edge => edge.generatePath(this.graph, this.state.showProperties));
.attr("d", edge => edge.generatePath(this.graph, this.nodesCustomDataShowed()));
}
private updateVisibleBlocksAndEdges(): void {
@ -341,9 +390,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
const iconsPath = "img/turboshaft/";
// select existing edges
const filteredEdges = [
...this.graph.blocksEdges(_ => this.graph.isRendered())
];
const filteredEdges = [...view.graph.blocksEdges(_ => view.graph.isRendered())];
const selEdges = view.visibleEdges
.selectAll<SVGPathElement, TurboshaftGraphEdge<TurboshaftGraphBlock>>("path")
@ -372,9 +419,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
newAndOldEdges.classed("hidden", edge => !edge.isVisible());
// select existing blocks
const filteredBlocks = [
...this.graph.blocks(_ => this.graph.isRendered())
];
const filteredBlocks = [...view.graph.blocks(_ => view.graph.isRendered())];
const allBlocks = view.visibleBlocks
.selectAll<SVGGElement, TurboshaftGraphBlock>(".turboshaft-block");
const selBlocks = allBlocks.data(filteredBlocks, block => block.toString());
@ -419,7 +464,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.attr("rx", C.TURBOSHAFT_BLOCK_BORDER_RADIUS)
.attr("ry", C.TURBOSHAFT_BLOCK_BORDER_RADIUS)
.attr("width", block => block.getWidth())
.attr("height", block => block.getHeight(view.state.showProperties));
.attr("height", block => block.getHeight(view.nodesCustomDataShowed()));
newBlocks.each(function (block: TurboshaftGraphBlock) {
const svg = d3.select<SVGGElement, TurboshaftGraphBlock>(this);
@ -429,7 +474,9 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.attr("text-anchor", "middle")
.attr("x", block.getWidth() / 2)
.append("tspan")
.text(block.displayLabel);
.text(block.displayLabel)
.append("title")
.text(view.graph.customData.getTitle(block.id, DataTarget.Blocks));
svg
.append("text")
@ -462,7 +509,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.classed("selected", block => view.state.blocksSelection.isSelected(block))
.attr("transform", block => `translate(${block.x},${block.y})`)
.select("rect")
.attr("height", block => block.getHeight(view.state.showProperties));
.attr("height", block => block.getHeight(view.nodesCustomDataShowed()));
newAndOldBlocks.select("image")
.attr("xlink:href", block => `${iconsPath}collapse_${block.collapsed ? "down" : "up"}.svg`);
@ -470,7 +517,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
newAndOldBlocks.select(".block-collapsed-label")
.attr("visibility", block => block.collapsed ? "visible" : "hidden");
newAndOldEdges.attr("d", edge => edge.generatePath(this.graph, view.state.showProperties));
newAndOldEdges.attr("d", edge => edge.generatePath(view.graph, view.nodesCustomDataShowed()));
}
private appendInlineNodes(svg: d3.Selection<SVGGElement, TurboshaftGraphBlock, any, any>,
@ -493,6 +540,9 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
let nodeY = block.labelBox.height;
const blockWidth = block.getWidth();
const view = this;
const customData = this.graph.customData;
const storageKey = this.customDataStorageKey();
const selectedCustomData = storageGetItem(storageKey, null, false);
newNodes.each(function (node: TurboshaftGraphNode) {
const nodeSvg = d3.select(this);
nodeSvg
@ -504,7 +554,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.append("tspan")
.text(node.displayLabel)
.append("title")
.text(node.getTitle());
.text(`${node.getTitle()}${customData.getTitle(node.id, DataTarget.Nodes)}`);
nodeSvg
.on("mouseenter", (node: TurboshaftGraphNode) => {
@ -531,17 +581,18 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
d3.event.stopPropagation();
});
nodeY += node.labelBox.height;
if (node.properties) {
if (view.graph.customData.nodes.size > 0) {
const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes);
nodeSvg
.append("text")
.attr("dx", C.TURBOSHAFT_NODE_X_INDENT)
.classed("inline-node-properties", true)
.classed("inline-node-custom-data", true)
.attr("dy", nodeY)
.append("tspan")
.text(node.getReadableProperties(blockWidth))
.text(view.getReadableString(customData, blockWidth))
.append("title")
.text(node.properties);
nodeY += node.propertiesBox.height;
.text(customData);
nodeY += node.labelBox.height;
}
});
@ -550,19 +601,21 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
private updateInlineNodes(): void {
const view = this;
const state = this.state;
const storageKey = this.customDataStorageKey();
const selectedCustomData = storageGetItem(storageKey, null, false);
let totalHeight = 0;
let blockId = 0;
this.visibleNodes.each(function (node: TurboshaftGraphNode) {
view.visibleNodes.each(function (node: TurboshaftGraphNode) {
const nodeSvg = d3.select(this);
const showCustomData = view.nodesCustomDataShowed();
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;
totalHeight += node.getHeight(showCustomData);
const nodeY = showCustomData ? totalHeight - node.labelBox.height : totalHeight;
nodeSvg
.select(".inline-node-label")
@ -570,9 +623,18 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
.attr("dy", nodeY)
.attr("visibility", !node.block.collapsed ? "visible" : "hidden");
nodeSvg
.select(".inline-node-properties")
.attr("visibility", !node.block.collapsed && state.showProperties ? "visible" : "hidden");
const svgNodeCustomData = nodeSvg
.select(".inline-node-custom-data")
.attr("visibility", !node.block.collapsed && showCustomData ? "visible" : "hidden");
if (!node.block.collapsed && showCustomData) {
const customData = view.graph.getCustomData(selectedCustomData, node.id, DataTarget.Nodes);
svgNodeCustomData
.select("tspan")
.text(view.getReadableString(customData, node.block.width))
.append("title")
.text(customData);
}
});
}
@ -590,7 +652,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}
if (block.outputs.length > 0) {
const x = block.getOutputX();
const y = block.getHeight(this.state.showProperties) + C.DEFAULT_NODE_BUBBLE_RADIUS;
const y = block.getHeight(this.nodesCustomDataShowed()) + C.DEFAULT_NODE_BUBBLE_RADIUS;
svg.append("circle")
.classed("filledBubbleStyle", true)
.attr("id", `ob,${block.id}`)
@ -601,12 +663,12 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
private updateInputAndOutputBubbles(): void {
const view = this;
this.visibleBubbles.each(function () {
view.visibleBubbles.each(function () {
const components = this.id.split(",");
if (components[0] === "ob") {
const from = view.graph.blockMap[components[1]];
const x = from.getOutputX();
const y = from.getHeight(view.state.showProperties) + C.DEFAULT_NODE_BUBBLE_RADIUS;
const y = from.getHeight(view.nodesCustomDataShowed()) + C.DEFAULT_NODE_BUBBLE_RADIUS;
this.setAttribute("transform", `translate(${x},${y})`);
}
});
@ -628,8 +690,8 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
maxX = maxX ? Math.max(maxX, block.x + block.getWidth()) : block.x + block.getWidth();
minY = minY ? Math.min(minY, block.y) : block.y;
maxY = maxY
? Math.max(maxY, block.y + block.getHeight(this.state.showProperties))
: block.y + block.getHeight(this.state.showProperties);
? Math.max(maxY, block.y + block.getHeight(this.nodesCustomDataShowed()))
: block.y + block.getHeight(this.nodesCustomDataShowed());
}
if (blockHasSelection) {
hasSelection = true;
@ -659,6 +721,28 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
this.blockSelectionHandler.select(selectedBlocks, true);
}
private nodesCustomDataShowed(): boolean {
const storageKey = this.customDataStorageKey();
const selectedCustomData = storageGetItem(storageKey, null, false);
if (selectedCustomData == null) return false;
return this.graph.hasCustomData(selectedCustomData, DataTarget.Nodes) &&
this.state.showCustomData;
}
private customDataStorageKey(): string {
return `${this.phaseName}-selected-custom-data`;
}
private getReadableString(str: string, maxWidth: number): string {
if (!str) return "";
const strBox = measureText(str);
if (maxWidth > strBox.width) return str;
const widthOfOneSymbol = Math.floor(strBox.width / str.length);
const lengthOfReadableProperties = Math.floor(maxWidth / widthOfOneSymbol);
return `${str.slice(0, lengthOfReadableProperties - 3)}..`;
}
// Actions (handlers of toolbox menu and hotkeys events)
private layoutAction(view: TurboshaftGraphView): void {
view.updateGraphStateType(GraphStateType.NeedToFullRebuild);
@ -681,7 +765,7 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
block.compressHeight();
}
const ranksMaxBlockHeight = view.graph.getRanksMaxBlockHeight(view.state.showProperties);
const ranksMaxBlockHeight = view.graph.getRanksMaxBlockHeight(view.nodesCustomDataShowed());
for (const block of view.graph.blocks()) {
block.y = ranksMaxBlockHeight.slice(1, block.rank).reduce<number>((accumulator, current) => {
@ -707,15 +791,15 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
view.focusOnSvg();
}
private togglePropertiesAction(view: TurboshaftGraphView): void {
view.state.showProperties = !view.state.showProperties;
private toggleCustomDataAction(view: TurboshaftGraphView): void {
view.state.showCustomData = !view.state.showCustomData;
const ranksMaxBlockHeight = new Array<number>();
for (const block of view.graph.blocks()) {
ranksMaxBlockHeight[block.rank] = Math.max(ranksMaxBlockHeight[block.rank] ?? 0,
block.collapsed
? block.height
: block.getHeight(view.state.showProperties));
: block.getHeight(view.nodesCustomDataShowed()));
}
for (const block of view.graph.blocks()) {
@ -724,8 +808,10 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
}, block.getRankIndent());
}
const element = document.getElementById("toggle-properties");
element.classList.toggle("button-input-toggled", view.state.showProperties);
const element = document.getElementById("toggle-custom-data");
element.classList.toggle("button-input-toggled", view.state.showCustomData);
const extent = view.graph.redetermineGraphBoundingBox(view.state.showCustomData);
view.panZoom.translateExtent(extent);
view.adaptiveUpdateGraphVisibility();
}
@ -767,7 +853,8 @@ export class TurboshaftGraphView extends MovableView<TurboshaftGraph> {
private copyToClipboardHoveredNodeInfo(): void {
const node = this.graph.nodeMap[this.hoveredNodeIdentifier];
if (!node) return;
copyToClipboard(node.getTitle());
const customData = this.graph.customData;
copyToClipboard(`${node.getTitle()}${customData.getTitle(node.id, DataTarget.Nodes)}`);
}
private selectNodesOfSelectedBlocks(): void {

View File

@ -29,6 +29,7 @@
"src/phases/phase.ts",
"src/phases/schedule-phase.ts",
"src/phases/sequence-phase.ts",
"src/phases/turboshaft-custom-data-phase.ts",
"src/selection/selection-storage.ts",
"src/selection/selection-map.ts",
"src/selection/selection-broker.ts",