import{Events,PerformanceModel}from'./PerformanceModel.js';import{TimelineModeViewDelegate}from'./TimelinePanel.js';export class CountersGraph extends UI.VBox{constructor(delegate){super();this.element.id='memory-graphs-container';this._delegate=delegate;this._calculator=new Calculator();this._header=new UI.HBox();this._header.element.classList.add('timeline-memory-header');this._header.show(this.element);this._toolbar=new UI.Toolbar('timeline-memory-toolbar');this._header.element.appendChild(this._toolbar.element);this._graphsContainer=new UI.VBox();this._graphsContainer.show(this.element);const canvasWidget=new UI.VBoxWithResizeCallback(this._resize.bind(this));canvasWidget.show(this._graphsContainer.element);this._createCurrentValuesBar();this._canvasContainer=canvasWidget.element;this._canvasContainer.id='memory-graphs-canvas-container';this._canvas=this._canvasContainer.createChild('canvas');this._canvas.id='memory-counters-graph';this._canvasContainer.addEventListener('mouseover',this._onMouseMove.bind(this),true);this._canvasContainer.addEventListener('mousemove',this._onMouseMove.bind(this),true);this._canvasContainer.addEventListener('mouseleave',this._onMouseLeave.bind(this),true);this._canvasContainer.addEventListener('click',this._onClick.bind(this),true);this._timelineGrid=new PerfUI.TimelineGrid();this._canvasContainer.appendChild(this._timelineGrid.dividersElement);this._counters=[];this._counterUI=[];this._countersByName={};this._countersByName['jsHeapSizeUsed']=this._createCounter(Common.UIString('JS Heap'),Common.UIString('JS Heap: %s'),'hsl(220, 90%, 43%)',Number.bytesToString);this._countersByName['documents']=this._createCounter(Common.UIString('Documents'),Common.UIString('Documents: %s'),'hsl(0, 90%, 43%)');this._countersByName['nodes']=this._createCounter(Common.UIString('Nodes'),Common.UIString('Nodes: %s'),'hsl(120, 90%, 43%)');this._countersByName['jsEventListeners']=this._createCounter(Common.UIString('Listeners'),Common.UIString('Listeners: %s'),'hsl(38, 90%, 43%)');this._gpuMemoryCounter=this._createCounter(Common.UIString('GPU Memory'),Common.UIString('GPU Memory [KB]: %s'),'hsl(300, 90%, 43%)',Number.bytesToString);this._countersByName['gpuMemoryUsedKB']=this._gpuMemoryCounter;}
setModel(model,track){if(this._model!==model){if(this._model){this._model.removeEventListener(Events.WindowChanged,this._onWindowChanged,this);}
this._model=model;if(this._model){this._model.addEventListener(Events.WindowChanged,this._onWindowChanged,this);}}
this._calculator.setZeroTime(model?model.timelineModel().minimumRecordTime():0);for(let i=0;i<this._counters.length;++i){this._counters[i].reset();this._counterUI[i].reset();}
this.scheduleRefresh();this._track=track;if(!track){return;}
const events=track.syncEvents();for(let i=0;i<events.length;++i){const event=events[i];if(event.name!==TimelineModel.TimelineModel.RecordType.UpdateCounters){continue;}
const counters=event.args.data;if(!counters){return;}
for(const name in counters){const counter=this._countersByName[name];if(counter){counter.appendSample(event.startTime,counters[name]);}}
const gpuMemoryLimitCounterName='gpuMemoryLimitKB';if(gpuMemoryLimitCounterName in counters){this._gpuMemoryCounter.setLimit(counters[gpuMemoryLimitCounterName]);}}}
_createCurrentValuesBar(){this._currentValuesBar=this._graphsContainer.element.createChild('div');this._currentValuesBar.id='counter-values-bar';}
_createCounter(uiName,uiValueTemplate,color,formatter){const counter=new Counter();this._counters.push(counter);this._counterUI.push(new CounterUI(this,uiName,uiValueTemplate,color,counter,formatter));return counter;}
resizerElement(){return this._header.element;}
_resize(){const parentElement=this._canvas.parentElement;this._canvas.width=parentElement.clientWidth*window.devicePixelRatio;this._canvas.height=parentElement.clientHeight*window.devicePixelRatio;this._calculator.setDisplayWidth(this._canvas.width);this.refresh();}
_onWindowChanged(event){const window=(event.data.window);this._calculator.setWindow(window.left,window.right);this.scheduleRefresh();}
scheduleRefresh(){UI.invokeOnceAfterBatchUpdate(this,this.refresh);}
draw(){this._clear();for(const counter of this._counters){counter._calculateVisibleIndexes(this._calculator);counter._calculateXValues(this._canvas.width);}
for(const counterUI of this._counterUI){counterUI._drawGraph(this._canvas);}}
_onClick(event){const x=event.x-this._canvasContainer.totalOffsetLeft();let minDistance=Infinity;let bestTime;for(const counterUI of this._counterUI){if(!counterUI.counter.times.length){continue;}
const index=counterUI._recordIndexAt(x);const distance=Math.abs(x*window.devicePixelRatio-counterUI.counter.x[index]);if(distance<minDistance){minDistance=distance;bestTime=counterUI.counter.times[index];}}
if(bestTime!==undefined){this._delegate.selectEntryAtTime(this._track.events.length?this._track.events:this._track.asyncEvents,bestTime);}}
_onMouseLeave(event){delete this._markerXPosition;this._clearCurrentValueAndMarker();}
_clearCurrentValueAndMarker(){for(let i=0;i<this._counterUI.length;i++){this._counterUI[i]._clearCurrentValueAndMarker();}}
_onMouseMove(event){const x=event.x-this._canvasContainer.totalOffsetLeft();this._markerXPosition=x;this._refreshCurrentValues();}
_refreshCurrentValues(){if(this._markerXPosition===undefined){return;}
for(let i=0;i<this._counterUI.length;++i){this._counterUI[i].updateCurrentValue(this._markerXPosition);}}
refresh(){this._timelineGrid.updateDividers(this._calculator);this.draw();this._refreshCurrentValues();}
_clear(){const ctx=this._canvas.getContext('2d');ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);}}
export class Counter{constructor(){this.times=[];this.values=[];}
appendSample(time,value){if(this.values.length&&this.values.peekLast()===value){return;}
this.times.push(time);this.values.push(value);}
reset(){this.times=[];this.values=[];}
setLimit(value){this._limitValue=value;}
_calculateBounds(){let maxValue;let minValue;for(let i=this._minimumIndex;i<=this._maximumIndex;i++){const value=this.values[i];if(minValue===undefined||value<minValue){minValue=value;}
if(maxValue===undefined||value>maxValue){maxValue=value;}}
minValue=minValue||0;maxValue=maxValue||1;if(this._limitValue){if(maxValue>this._limitValue*0.5){maxValue=Math.max(maxValue,this._limitValue);}
minValue=Math.min(minValue,this._limitValue);}
return{min:minValue,max:maxValue};}
_calculateVisibleIndexes(calculator){const start=calculator.minimumBoundary();const end=calculator.maximumBoundary();this._minimumIndex=Number.constrain(this.times.upperBound(start)-1,0,this.times.length-1);this._maximumIndex=Number.constrain(this.times.lowerBound(end),0,this.times.length-1);this._minTime=start;this._maxTime=end;}
_calculateXValues(width){if(!this.values.length){return;}
const xFactor=width/(this._maxTime-this._minTime);this.x=new Array(this.values.length);for(let i=this._minimumIndex+1;i<=this._maximumIndex;i++){this.x[i]=xFactor*(this.times[i]-this._minTime);}}}
export class CounterUI{constructor(countersPane,title,currentValueLabel,graphColor,counter,formatter){this._countersPane=countersPane;this.counter=counter;this._formatter=formatter||Number.withThousandsSeparator;this._setting=self.Common.settings.createSetting('timelineCountersGraph-'+title,true);this._setting.setTitle(title);this._filter=new UI.ToolbarSettingCheckbox(this._setting,title);this._filter.inputElement.classList.add('-theme-preserve');const color=Common.Color.parse(graphColor).setAlpha(0.5).asString(Common.Color.Format.RGBA);if(color){this._filter.element.backgroundColor=color;this._filter.element.borderColor='transparent';}
this._filter.inputElement.addEventListener('click',this._toggleCounterGraph.bind(this));countersPane._toolbar.appendToolbarItem(this._filter);this._range=this._filter.element.createChild('span','range');this._value=countersPane._currentValuesBar.createChild('span','memory-counter-value');this._value.style.color=graphColor;this.graphColor=graphColor;this.limitColor=Common.Color.parse(graphColor).setAlpha(0.3).asString(Common.Color.Format.RGBA);this.graphYValues=[];this._verticalPadding=10;this._currentValueLabel=currentValueLabel;this._marker=countersPane._canvasContainer.createChild('div','memory-counter-marker');this._marker.style.backgroundColor=graphColor;this._clearCurrentValueAndMarker();}
reset(){this._range.textContent='';}
setRange(minValue,maxValue){const min=this._formatter(minValue);const max=this._formatter(maxValue);this._range.textContent=Common.UIString('[%s\xa0\u2013\xa0%s]',min,max);}
_toggleCounterGraph(event){this._value.classList.toggle('hidden',!this._filter.checked());this._countersPane.refresh();}
_recordIndexAt(x){return this.counter.x.upperBound(x*window.devicePixelRatio,null,this.counter._minimumIndex+1,this.counter._maximumIndex+1)-
1;}
updateCurrentValue(x){if(!this.visible()||!this.counter.values.length||!this.counter.x){return;}
const index=this._recordIndexAt(x);const value=Number.withThousandsSeparator(this.counter.values[index]);this._value.textContent=Common.UIString(this._currentValueLabel,value);const y=this.graphYValues[index]/window.devicePixelRatio;this._marker.style.left=x+'px';this._marker.style.top=y+'px';this._marker.classList.remove('hidden');}
_clearCurrentValueAndMarker(){this._value.textContent='';this._marker.classList.add('hidden');}
_drawGraph(canvas){const ctx=canvas.getContext('2d');const width=canvas.width;const height=canvas.height-2*this._verticalPadding;if(height<=0){this.graphYValues=[];return;}
const originY=this._verticalPadding;const counter=this.counter;const values=counter.values;if(!values.length){return;}
const bounds=counter._calculateBounds();const minValue=bounds.min;const maxValue=bounds.max;this.setRange(minValue,maxValue);if(!this.visible()){return;}
const yValues=this.graphYValues;const maxYRange=maxValue-minValue;const yFactor=maxYRange?height/(maxYRange):1;ctx.save();ctx.lineWidth=window.devicePixelRatio;if(ctx.lineWidth%2){ctx.translate(0.5,0.5);}
ctx.beginPath();let value=values[counter._minimumIndex];let currentY=Math.round(originY+height-(value-minValue)*yFactor);ctx.moveTo(0,currentY);let i=counter._minimumIndex;for(;i<=counter._maximumIndex;i++){const x=Math.round(counter.x[i]);ctx.lineTo(x,currentY);const currentValue=values[i];if(typeof currentValue!=='undefined'){value=currentValue;}
currentY=Math.round(originY+height-(value-minValue)*yFactor);ctx.lineTo(x,currentY);yValues[i]=currentY;}
yValues.length=i;ctx.lineTo(width,currentY);ctx.strokeStyle=this.graphColor;ctx.stroke();if(counter._limitValue){const limitLineY=Math.round(originY+height-(counter._limitValue-minValue)*yFactor);ctx.moveTo(0,limitLineY);ctx.lineTo(width,limitLineY);ctx.strokeStyle=this.limitColor;ctx.stroke();}
ctx.closePath();ctx.restore();}
visible(){return this._filter.checked();}}
export class Calculator{setZeroTime(time){this._zeroTime=time;}
computePosition(time){return(time-this._minimumBoundary)/this.boundarySpan()*this._workingArea;}
setWindow(minimumBoundary,maximumBoundary){this._minimumBoundary=minimumBoundary;this._maximumBoundary=maximumBoundary;}
setDisplayWidth(clientWidth){this._workingArea=clientWidth;}
formatValue(value,precision){return Number.preciseMillisToString(value-this.zeroTime(),precision);}
maximumBoundary(){return this._maximumBoundary;}
minimumBoundary(){return this._minimumBoundary;}
zeroTime(){return this._zeroTime;}
boundarySpan(){return this._maximumBoundary-this._minimumBoundary;}}