Commit 4a092170 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[tools][system-analyzer] Display timer events

Add common TimelineTrackStackedBase base class for TimelineTrackTick
and TimelineTrackTimer for visualising stacked time ranges that only
need rescaling when zooming in.

Additional changes:
- Highlight matching registers in disassembly
- Simplify CodeLogEntry summary for script code
- Show event for array items in the property-link-table


Bug: v8:10644
Change-Id: I0b37274e12ba55f1c6251b90d39d996ffae7f37e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2992716Reviewed-by: 's avatarVictor Gomes <victorgomes@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75437}
parent 115b8664
......@@ -546,8 +546,8 @@ export class Profile {
addDisassemble(start, kind, disassemble) {
const entry = this.codeMap_.findDynamicEntryByStartAddress(start);
if (!entry) return;
this.getOrCreateSourceInfo(entry).setDisassemble(disassemble);
if (entry) this.getOrCreateSourceInfo(entry).setDisassemble(disassemble);
return entry;
}
getOrCreateSourceInfo(entry) {
......
......@@ -19,6 +19,7 @@ class State {
_codeTimeline;
_apiTimeline;
_tickTimeline;
_timerTimeline;
_minStartTime = Number.POSITIVE_INFINITY;
_maxEndTime = Number.NEGATIVE_INFINITY;
......@@ -42,13 +43,14 @@ class State {
setTimelines(
mapTimeline, icTimeline, deoptTimeline, codeTimeline, apiTimeline,
tickTimeline) {
tickTimeline, timerTimeline) {
this._mapTimeline = mapTimeline;
this._icTimeline = icTimeline;
this._deoptTimeline = deoptTimeline;
this._codeTimeline = codeTimeline;
this._apiTimeline = apiTimeline;
this._tickTimeline = tickTimeline;
this._timerTimeline = timerTimeline;
for (let timeline of arguments) {
if (timeline === undefined) return;
this._minStartTime = Math.min(this._minStartTime, timeline.startTime);
......@@ -84,10 +86,15 @@ class State {
return this._tickTimeline;
}
get timerTimeline() {
return this._timerTimeline;
}
get timelines() {
return [
this._mapTimeline, this._icTimeline, this._deoptTimeline,
this._codeTimeline, this._apiTimeline, this._tickTimeline
this._codeTimeline, this._apiTimeline, this._tickTimeline,
this._timerTimeline
];
}
......
......@@ -18,8 +18,30 @@ export function formatBytes(bytes) {
return bytes.toFixed(2) + units[index];
}
export function formatMicroSeconds(millis) {
return (millis * kMicro2Milli).toFixed(1) + 'ms';
export function formatMicroSeconds(micro) {
return (micro * kMicro2Milli).toFixed(1) + 'ms';
}
export function formatDurationMicros(micros, secondsDigits = 3) {
return formatDurationMillis(micros * kMicro2Milli, secondsDigits);
}
export function formatDurationMillis(millis, secondsDigits = 3) {
if (millis < 1000) {
if (millis < 1) {
return (millis / kMicro2Milli).toFixed(1) + 'ns';
}
return millis.toFixed(2) + 'ms';
}
let seconds = millis / 1000;
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
seconds = seconds % 60;
let buffer = ''
if (hours > 0) buffer += hours + 'h ';
if (hours > 0 || minutes > 0) buffer += minutes + 'm ';
buffer += seconds.toFixed(secondsDigits) + 's'
return buffer;
}
export function delay(time) {
......
......@@ -48,6 +48,10 @@ body {
background-color: var(--background-color);
}
h3 {
margin-block-end: 0.3em;
}
section {
margin-bottom: 10px;
}
......
......@@ -56,6 +56,7 @@ found in the LICENSE file. -->
<section id="container" class="initial">
<timeline-panel id="timeline-panel">
<timeline-track-tick id="tick-track" title="Samples"></timeline-track-tick>
<timeline-track-timer id="timer-track" title="Timers"></timeline-track-timer>
<timeline-track-map id="map-track" title="Map"></timeline-track-map>
<timeline-track id="ic-track" title="IC"></timeline-track>
<timeline-track id="deopt-track" title="Deopt"></timeline-track>
......
......@@ -13,6 +13,7 @@ import {IcLogEntry} from './log/ic.mjs';
import {LogEntry} from './log/log.mjs';
import {MapLogEntry} from './log/map.mjs';
import {TickLogEntry} from './log/tick.mjs';
import {TimerLogEntry} from './log/timer.mjs';
import {Processor} from './processor.mjs';
import {Timeline} from './timeline.mjs'
import {FocusEvent, SelectionEvent, SelectRelatedEvent, SelectTimeEvent, ToolTipEvent,} from './view/events.mjs';
......@@ -35,6 +36,7 @@ class App {
deoptTrack: $('#deopt-track'),
codeTrack: $('#code-track'),
apiTrack: $('#api-track'),
timerTrack: $('#timer-track'),
icList: $('#ic-list'),
mapList: $('#map-list'),
......@@ -60,8 +62,15 @@ class App {
static get allEventTypes() {
return new Set([
SourcePosition, MapLogEntry, IcLogEntry, ApiLogEntry, CodeLogEntry,
DeoptLogEntry, SharedLibLogEntry, TickLogEntry
SourcePosition,
MapLogEntry,
IcLogEntry,
ApiLogEntry,
CodeLogEntry,
DeoptLogEntry,
SharedLibLogEntry,
TickLogEntry,
TimerLogEntry,
]);
}
......@@ -112,6 +121,7 @@ class App {
case Script:
entries = entry.entries.concat(entry.sourcePositions);
break;
case TimerLogEntry:
case ApiLogEntry:
case CodeLogEntry:
case TickLogEntry:
......@@ -169,6 +179,7 @@ class App {
return this.showDeoptEntries(entries);
case SharedLibLogEntry:
return this.showSharedLibEntries(entries);
case TimerLogEntry:
case TickLogEntry:
break;
default:
......@@ -206,6 +217,7 @@ class App {
}
showTickEntries(entries, focusView = true) {}
showTimerEntries(entries, focusView = true) {}
showSourcePositions(entries, focusView = true) {
this._view.scriptPanel.selectedSourcePositions = entries
......@@ -225,6 +237,7 @@ class App {
this.showCodeEntries(this._state.codeTimeline.selectionOrSelf, false);
this.showApiEntries(this._state.apiTimeline.selectionOrSelf, false);
this.showTickEntries(this._state.tickTimeline.selectionOrSelf, false);
this.showTimerEntries(this._state.timerTimeline.selectionOrSelf, false);
this._view.timelinePanel.timeSelection = {start, end};
}
......@@ -253,6 +266,8 @@ class App {
return this.focusDeoptLogEntry(entry);
case TickLogEntry:
return this.focusTickLogEntry(entry);
case TimerLogEntry:
return this.focusTimerLogEntry(entry);
default:
throw new Error(`Unknown selection type: ${entry.constructor?.name}`);
}
......@@ -304,6 +319,11 @@ class App {
this._view.tickTrack.focusedEntry = entry;
}
focusTimerLogEntry(entry) {
this._state.timerLogEntry = entry;
this._view.timerTrack.focusedEntry = entry;
}
focusSourcePosition(sourcePosition) {
if (!sourcePosition) return;
this._view.scriptPanel.focusedSourcePositions = [sourcePosition];
......@@ -357,9 +377,10 @@ class App {
const codeTimeline = processor.codeTimeline;
const apiTimeline = processor.apiTimeline;
const tickTimeline = processor.tickTimeline;
const timerTimeline = processor.timerTimeline;
this._state.setTimelines(
mapTimeline, icTimeline, deoptTimeline, codeTimeline, apiTimeline,
tickTimeline);
tickTimeline, timerTimeline);
this._view.mapPanel.timeline = mapTimeline;
this._view.icList.timeline = icTimeline;
this._view.mapList.timeline = mapTimeline;
......@@ -368,6 +389,7 @@ class App {
this._view.apiList.timeline = apiTimeline;
this._view.scriptPanel.scripts = processor.scripts;
this._view.codePanel.timeline = codeTimeline;
this._view.codePanel.timeline = codeTimeline;
this.refreshTimelineTrackView();
} catch (e) {
this._view.logFileReader.error = 'Log file contains errors!'
......@@ -385,6 +407,7 @@ class App {
this._view.codeTrack.data = this._state.codeTimeline;
this._view.apiTrack.data = this._state.apiTimeline;
this._view.tickTrack.data = this._state.tickTimeline;
this._view.timerTrack.data = this._state.timerTimeline;
}
}
......
......@@ -61,6 +61,10 @@ export class CodeLogEntry extends LogEntry {
return this._kind;
}
get isBuiltinKind() {
return this._kindName === 'Builtin';
}
get kindName() {
return this._kindName;
}
......@@ -70,7 +74,7 @@ export class CodeLogEntry extends LogEntry {
}
get functionName() {
return this._entry.functionName;
return this._entry.functionName ?? this._entry.getRawName();
}
get size() {
......
// Copyright 2021 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 {formatDurationMicros} from '../helper.mjs';
import {LogEntry} from './log.mjs';
export class TimerLogEntry extends LogEntry {
constructor(type, startTime, endTime = -1) {
super(type, startTime);
this._endTime = endTime;
this.depth = 0;
}
end(time) {
if (this.isInitialized) throw new Error('Invalid timer change');
this._endTime = time;
}
get isInitialized() {
return this._endTime !== -1;
}
get startTime() {
return this._time;
}
get endTime() {
return this._endTime;
}
get duration() {
return this._endTime - this._time;
}
covers(time) {
return this._time <= time && time <= this._endTime;
}
get toolTipDict() {
const dict = super.toolTipDict;
dict.startTime = formatDurationMicros(dict.startTime);
dict.endTime = formatDurationMicros(dict.endTime);
dict.duration = formatDurationMicros(dict.duration);
return dict;
}
static get propertyNames() {
return [
'type',
'startTime',
'endTime',
'duration',
];
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import {CodeLogEntry, DeoptLogEntry, SharedLibLogEntry} from './log/code.mjs';
import {IcLogEntry} from './log/ic.mjs';
import {Edge, MapLogEntry} from './log/map.mjs';
import {TickLogEntry} from './log/tick.mjs';
import {TimerLogEntry} from './log/timer.mjs';
import {Timeline} from './timeline.mjs';
// ===========================================================================
......@@ -22,6 +23,7 @@ export class Processor extends LogReader {
_icTimeline = new Timeline();
_mapTimeline = new Timeline();
_tickTimeline = new Timeline();
_timerTimeline = new Timeline();
_formatPCRegexp = /(.*):[0-9]+:[0-9]+$/;
_lastTimestamp = 0;
_lastCodeLogEntry;
......@@ -93,8 +95,14 @@ export class Processor extends LogReader {
'active-runtime-timer': undefined,
'heap-sample-begin': undefined,
'heap-sample-end': undefined,
'timer-event-start': undefined,
'timer-event-end': undefined,
'timer-event-start': {
parsers: [parseString, parseInt],
processor: this.processTimerEventStart
},
'timer-event-end': {
parsers: [parseString, parseInt],
processor: this.processTimerEventEnd
},
'map-create':
{parsers: [parseInt, parseString], processor: this.processMapCreate},
'map': {
......@@ -479,6 +487,24 @@ export class Processor extends LogReader {
new ApiLogEntry(type, this._lastTimestamp, name, arg1));
}
processTimerEventStart(type, time) {
const entry = new TimerLogEntry(type, time);
this._timerTimeline.push(entry);
}
processTimerEventEnd(type, time) {
// Timer-events are infrequent, and not deeply nested, doing a linear walk
// is usually good enough.
for (let i = this._timerTimeline.length - 1; i >= 0; i--) {
const timer = this._timerTimeline.at(i);
if (timer.type == type && !timer.isInitialized) {
timer.end(time);
return;
}
}
console.error('Couldn\'t find matching timer event start', {type, time});
}
get icTimeline() {
return this._icTimeline;
}
......@@ -503,6 +529,10 @@ export class Processor extends LogReader {
return this._tickTimeline;
}
get timerTimeline() {
return this._timerTimeline;
}
get scripts() {
return this._profile.scripts_.filter(script => script !== undefined);
}
......
......@@ -4,11 +4,24 @@ found in the LICENSE file. -->
<head>
<link href="./index.css" rel="stylesheet">
</head>
<style>
#sourceCode {
white-space: pre-line;
}
.register {
border-bottom: 1px dashed;
border-radius: 2px;
}
.register:hover {
background-color: var(--border-color);
}
.register.selected {
color: var(--default-color);
background-color: var(--border-color);
}
</style>
<div class="panel">
<input type="checkbox" id="closer" class="panelCloserInput" checked>
<label class="panelCloserLabel" for="closer"></label>
......
......@@ -4,6 +4,12 @@
import {SelectRelatedEvent} from './events.mjs';
import {CollapsableElement, DOM, formatBytes, formatMicroSeconds} from './helper.mjs';
const kRegisters = ['rsp', 'rbp', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi'];
// Add Interpreter and x64 registers
for (let i = 0; i < 14; i++) {
kRegisters.push(`r${i}`);
}
DOM.defineCustomElement('view/code-panel',
(templateText) =>
class CodePanel extends CollapsableElement {
......@@ -13,6 +19,11 @@ DOM.defineCustomElement('view/code-panel',
constructor() {
super(templateText);
this._codeSelectNode = this.$('#codeSelect');
this._disassemblyNode = this.$('#disassembly');
this._sourceNode = this.$('#sourceCode');
this._registerSelector = new RegisterSelector(this._disassemblyNode);
this._codeSelectNode.onchange = this._handleSelectCode.bind(this);
this.$('#selectedRelatedButton').onclick =
this._handleSelectRelated.bind(this)
......@@ -41,7 +52,7 @@ DOM.defineCustomElement('view/code-panel',
script: entry.script,
type: entry.type,
kind: entry.kindName,
variants: entry.variants,
variants: entry.variants.length > 1 ? entry.variants : undefined,
};
} else {
this.$('#properties').propertyDict = {};
......@@ -49,24 +60,34 @@ DOM.defineCustomElement('view/code-panel',
this.requestUpdate();
}
get _disassemblyNode() {
return this.$('#disassembly');
}
get _sourceNode() {
return this.$('#sourceCode');
}
get _codeSelectNode() {
return this.$('#codeSelect');
}
_update() {
this._updateSelect();
this._disassemblyNode.innerText = this._entry?.code ?? '';
this._formatDisassembly();
this._sourceNode.innerText = this._entry?.source ?? '';
}
_formatDisassembly() {
if (!this._entry?.code) {
this._disassemblyNode.innerText = '';
return;
}
const rawCode = this._entry?.code;
try {
this._disassemblyNode.innerText = rawCode;
let formattedCode = this._disassemblyNode.innerHTML;
for (let register of kRegisters) {
const button = `<span class="register ${register}">${register}</span>`
formattedCode = formattedCode.replaceAll(register, button);
}
// Let's replace the base-address since it doesn't add any value.
// TODO
this._disassemblyNode.innerHTML = formattedCode;
} catch (e) {
console.error(e);
this._disassemblyNode.innerText = rawCode;
}
}
_updateSelect() {
const select = this._codeSelectNode;
if (select.data === this._selectedEntries) return;
......@@ -76,13 +97,19 @@ DOM.defineCustomElement('view/code-panel',
this._selectedEntries.slice().sort((a, b) => a.time - b.time);
for (const code of this._selectedEntries) {
const option = DOM.element('option');
option.text =
`${code.functionName}(...) t=${formatMicroSeconds(code.time)} size=${
formatBytes(code.size)} script=${code.script?.toString()}`;
option.text = this._entrySummary(code);
option.data = code;
select.add(option);
}
}
_entrySummary(code) {
if (code.isBuiltinKind) {
return `${code.functionName}(...) t=${
formatMicroSeconds(code.time)} size=${formatBytes(code.size)}`;
}
return `${code.functionName}(...) t=${formatMicroSeconds(code.time)} size=${
formatBytes(code.size)} script=${code.script?.toString()}`;
}
_handleSelectCode() {
this.entry = this._codeSelectNode.selectedOptions[0].data;
......@@ -92,4 +119,37 @@ DOM.defineCustomElement('view/code-panel',
if (!this._entry) return;
this.dispatchEvent(new SelectRelatedEvent(this._entry));
}
});
\ No newline at end of file
});
class RegisterSelector {
_currentRegister;
constructor(node) {
this._node = node;
this._node.onmousemove = this._handleDisassemblyMouseMove.bind(this);
}
_handleDisassemblyMouseMove(event) {
const target = event.target;
if (!target.classList.contains('register')) {
this._clear();
return;
};
this._select(target.innerText);
}
_clear() {
if (this._currentRegister == undefined) return;
for (let node of this._node.querySelectorAll('.register')) {
node.classList.remove('selected');
}
}
_select(register) {
if (register == this._currentRegister) return;
this._clear();
this._currentRegister = register;
for (let node of this._node.querySelectorAll(`.register.${register}`)) {
node.classList.add('selected');
}
}
}
\ No newline at end of file
......@@ -11,11 +11,27 @@ found in the LICENSE file. -->
}
.properties > tbody > tr > td:nth-child(2n+1):after {
content: ':';
}
.properties > tbody > tr > td:nth-child(2n+1) {
padding-right: 3px;
}
.properties > tbody > tr > td:nth-child(2n+2) {
width: 100%;
}
.properties > tfoot {
text-align: right;
}
.properties {
min-width: 350px;
border-collapse: collapse;
}
h3 {
margin-block-start: 0em;
}
</style>
<div id="body">
......
......@@ -14,6 +14,7 @@ DOM.defineCustomElement(
_instanceLinkButtons = false;
_logEntryClickHandler = this._handleLogEntryClick.bind(this);
_logEntryRelatedHandler = this._handleLogEntryRelated.bind(this);
_arrayValueSelectHandler = this._handleArrayValueSelect.bind(this);
constructor() {
super(template);
......@@ -78,6 +79,7 @@ DOM.defineCustomElement(
return DOM.text(`${array.length} items`);
}
const select = DOM.element('select');
select.onchange = this._arrayValueSelectHandler;
for (let value of array) {
const option = DOM.element('option');
option.innerText = value.toString();
......@@ -106,11 +108,15 @@ DOM.defineCustomElement(
showRelatedButton.data = this._instance;
}
_handleLogEntryClick(e) {
this.dispatchEvent(new FocusEvent(e.currentTarget.data));
_handleArrayValueSelect(event) {
const logEntry = event.currentTarget.selectedOptions[0].data;
this.dispatchEvent(new FocusEvent(logEntry));
}
_handleLogEntryClick(event) {
this.dispatchEvent(new FocusEvent(event.currentTarget.data));
}
_handleLogEntryRelated(e) {
this.dispatchEvent(new SelectRelatedEvent(e.currentTarget.data));
_handleLogEntryRelated(event) {
this.dispatchEvent(new SelectRelatedEvent(event.currentTarget.data));
}
});
......@@ -5,6 +5,7 @@
import './timeline/timeline-track.mjs';
import './timeline/timeline-track-map.mjs';
import './timeline/timeline-track-tick.mjs';
import './timeline/timeline-track-timer.mjs';
import {SynchronizeSelectionEvent} from './events.mjs';
import {DOM, V8CustomElement} from './helper.mjs';
......
......@@ -326,7 +326,7 @@ export class TimelineTrackBase extends V8CustomElement {
_handleMouseMove(event) {
if (event.button !== 0) return;
if (this._selectionHandler.isSelecting) return false;
if (this.isLocked) {
if (this.isLocked && this._focusedEntry) {
this._updateToolTip(event);
return false;
}
......
// Copyright 2021 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 {delay} from '../../helper.mjs';
import {Timeline} from '../../timeline.mjs';
import {SelectTimeEvent} from '../events.mjs';
import {CSSColor, DOM, SVG} from '../helper.mjs';
import {TimelineTrackBase} from './timeline-track-base.mjs'
const kItemHeight = 8;
export class TimelineTrackStackedBase extends TimelineTrackBase {
_originalContentWidth = 0;
_drawableItems = new Timeline();
_updateChunks() {
// We don't need to update the chunks here.
this._updateDimensions();
this.requestUpdate();
}
set data(timeline) {
super.data = timeline;
this._contentWidth = 0;
this._prepareDrawableItems();
}
_handleDoubleClick(event) {
if (event.button !== 0) return;
this._selectionHandler.clearSelection();
const item = this._getDrawableItemForEvent(event);
if (item === undefined) return;
event.stopImmediatePropagation();
this.dispatchEvent(new SelectTimeEvent(item.startTime, item.endTime));
return false;
}
_getStackDepthForEvent(event) {
return Math.floor(event.layerY / kItemHeight) - 1;
}
_getDrawableItemForEvent(event) {
const depth = this._getStackDepthForEvent(event);
const time = this.positionToTime(event.pageX);
const index = this._drawableItems.find(time);
for (let i = index - 1; i > 0; i--) {
const item = this._drawableItems.at(i);
if (item.depth != depth) continue;
if (item.endTime < time) continue;
return item;
}
return undefined;
}
_drawableItemToLogEntry(item) {
return item;
}
_getEntryForEvent(event) {
const item = this._getDrawableItemForEvent(event);
const logEntry = this._drawableItemToLogEntry(item);
if (item === undefined) return undefined;
const style = this.toolTipTargetNode.style;
style.left = `${event.layerX}px`;
style.top = `${(item.depth + 1) * kItemHeight}px`;
style.height = `${kItemHeight}px`
return logEntry;
}
_prepareDrawableItems() {
// Subclass responsibility.
}
_adjustStackDepth(maxDepth) {
// Account for empty top line
maxDepth++;
this._adjustHeight(maxDepth * kItemHeight);
}
_scaleContent(currentWidth) {
if (this._originalContentWidth == 0) return;
// Instead of repainting just scale the content.
const ratio = currentWidth / this._originalContentWidth;
this._scalableContentNode.style.transform = `scale(${ratio}, 1)`;
this.style.setProperty('--txt-scale', `scale(${1 / ratio}, 1)`);
}
async _drawContent() {
if (this._originalContentWidth > 0) return;
this._originalContentWidth = parseInt(this.timelineMarkersNode.style.width);
this._scalableContentNode.innerHTML = '';
let buffer = '';
const add = async () => {
const svg = SVG.svg();
svg.innerHTML = buffer;
this._scalableContentNode.appendChild(svg);
buffer = '';
await delay(50);
};
const items = this._drawableItems.values;
for (let i = 0; i < items.length; i++) {
if ((i % 3000) == 0) await add();
buffer += this._drawItem(items[i], i);
}
add();
}
_drawItem(item, i, outline = false) {
const x = this.timeToPosition(item.time);
const y = (item.depth + 1) * kItemHeight;
let width = item.duration * this._timeToPixel;
if (outline) {
return `<rect x=${x} y=${y} width=${width} height=${
kItemHeight - 1} class=flameSelected />`;
}
let color = this._legend.colorForType(item.type);
if (i % 2 == 1) {
color = CSSColor.darken(color, 20);
}
return `<rect x=${x} y=${y} width=${width} height=${kItemHeight - 1} fill=${
color} class=flame />`;
}
_drawItemText(item) {
const type = item.type;
const kHeight = 9;
const x = this.timeToPosition(item.time);
const y = item.depth * (kHeight + 1);
let width = item.duration * this._timeToPixel;
width -= width * 0.1;
let buffer = '';
if (width < 15 || type == 'Other') return buffer;
const rawName = item.entry.getName();
if (rawName.length == 0) return buffer;
const kChartWidth = 5;
const maxChars = Math.floor(width / kChartWidth)
const text = rawName.substr(0, maxChars);
buffer += `<text x=${x + 1} y=${y - 3} class=txt>${text}</text>`
return buffer;
}
}
\ No newline at end of file
// Copyright 2021 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 {CSSColor, DOM, SVG, V8CustomElement} from '../helper.mjs';
import {TimelineTrackBase} from './timeline-track-base.mjs'
import {TimelineTrackStackedBase} from './timeline-track-stacked-base.mjs'
DOM.defineCustomElement(
'view/timeline/timeline-track', 'timeline-track-timer',
(templateText) =>
class TimelineTrackTimer extends TimelineTrackStackedBase {
constructor() {
super(templateText);
}
_prepareDrawableItems() {
const stack = [];
let maxDepth = 0;
for (let i = 0; i < this._timeline.length; i++) {
const timer = this._timeline.at(i);
let insertDepth = -1;
for (let depth = 0; depth < stack.length; depth++) {
const pendingTimer = stack[depth];
if (pendingTimer === undefined) {
if (insertDepth === -1) insertDepth = depth;
} else if (pendingTimer.endTime <= timer.startTime) {
stack[depth] == undefined;
if (insertDepth === -1) insertDepth = depth;
}
}
if (insertDepth === -1) insertDepth = stack.length;
stack[insertDepth] = timer;
timer.depth = insertDepth;
maxDepth = Math.max(maxDepth, insertDepth);
}
this._drawableItems = this._timeline;
this._adjustStackDepth(maxDepth++);
}
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment