Spaces:
Sleeping
Sleeping
| import { HistoricalTimeManager } from '@/hooks/useHistoricalTime'; | |
| import { useEffect, useLayoutEffect, useRef, useState } from 'react'; | |
| import uPlot, { AlignedData, Options } from 'uplot'; | |
| const MAX_DATA_POINTS = 10000; | |
| export function DebugTimeManager(props: { | |
| timeManager: HistoricalTimeManager; | |
| width: number; | |
| height: number; | |
| }) { | |
| const [plotElement, setPlotElement] = useState<HTMLDivElement | null>(null); | |
| const [plot, setPlot] = useState<uPlot>(); | |
| useLayoutEffect(() => { | |
| if (!plotElement) { | |
| return; | |
| } | |
| const opts: Options = { | |
| width: props.width, | |
| height: props.height, | |
| series: [ | |
| {}, | |
| { | |
| stroke: 'white', | |
| spanGaps: true, | |
| pxAlign: 0, | |
| points: { show: false }, | |
| label: 'Buffer health', | |
| }, | |
| ], | |
| scales: { | |
| y: { distr: 1 }, | |
| }, | |
| axes: [ | |
| { | |
| side: 0, | |
| show: false, | |
| }, | |
| { | |
| ticks: { size: 0 }, | |
| side: 1, | |
| stroke: 'white', | |
| }, | |
| ], | |
| legend: { | |
| show: false, | |
| }, | |
| }; | |
| const data: AlignedData = [[], []]; | |
| const plot = new uPlot(opts, data, plotElement); | |
| setPlot(plot); | |
| }, [plotElement, props.width, props.height]); | |
| const timeManager = props.timeManager; | |
| const [intervals, setIntervals] = useState([...timeManager.intervals]); | |
| useEffect(() => { | |
| let reqId: ReturnType<typeof requestAnimationFrame> = 0; | |
| const data = { | |
| t: [] as number[], | |
| bufferHealth: [] as number[], | |
| }; | |
| const update = () => { | |
| if (plot) { | |
| if (data.t.length > MAX_DATA_POINTS) { | |
| data.t = data.t.slice(-MAX_DATA_POINTS); | |
| data.bufferHealth = data.bufferHealth.slice(-MAX_DATA_POINTS); | |
| } | |
| const now = Date.now() / 1000; | |
| data.t.push(now); | |
| data.bufferHealth.push(timeManager.bufferHealth()); | |
| setIntervals([...timeManager.intervals]); | |
| plot.setData([data.t, data.bufferHealth], true); | |
| plot.setScale('x', { min: now - 10, max: now }); | |
| } | |
| reqId = requestAnimationFrame(update); | |
| }; | |
| update(); | |
| return () => cancelAnimationFrame(reqId); | |
| }, [plot, timeManager]); | |
| let intervalNode: React.ReactNode | null = null; | |
| if (intervals.length > 0) { | |
| const base = intervals[0].startTs; | |
| const baseAge = Date.now() - base; | |
| intervalNode = ( | |
| <div style={{ fontSize: '12px' }}> | |
| {intervals.length} {intervals.length > 1 ? 'intervals' : 'interval'}: | |
| <div style={{ fontSize: '9px', height: '48px' }}> | |
| <p>Base: {toSeconds(baseAge)}s ago</p> | |
| {intervals.map((interval) => { | |
| const containsServerTs = | |
| timeManager.prevServerTs && | |
| interval.startTs < timeManager.prevServerTs && | |
| timeManager.prevServerTs <= interval.endTs; | |
| let serverTs = null; | |
| if (containsServerTs) { | |
| serverTs = ` (server: ${toSeconds((timeManager.prevServerTs ?? base) - base)})`; | |
| } | |
| return ( | |
| <div | |
| key={interval.startTs} | |
| style={{ paddingRight: '3px', fontWeight: containsServerTs ? 'bold' : '' }} | |
| > | |
| {toSeconds(interval.startTs - base)} - {toSeconds(interval.endTs - base)} | |
| {serverTs} | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| let statusNode: React.ReactNode | null = null; | |
| if (timeManager.latestEngineStatus) { | |
| const status = timeManager.latestEngineStatus; | |
| let statusMsg = status.running ? 'Running' : 'Stopped'; | |
| statusNode = ( | |
| <div style={{ fontSize: '12px', paddingTop: '8px' }}> | |
| <p>Generation number: {status.generationNumber}</p> | |
| <p>Input number: {status.processedInputNumber}</p> | |
| <p>Status: {statusMsg}</p> | |
| <p>Client skew: {toSeconds(timeManager.clockSkew())}s</p> | |
| </div> | |
| ); | |
| } | |
| timeManager.latestEngineStatus?.generationNumber; | |
| return ( | |
| <div | |
| style={{ | |
| background: 'rgb(53, 59, 89)', | |
| position: 'fixed', | |
| top: '20px', | |
| left: '20px', | |
| padding: '10px', | |
| border: '1px solid rgb(23, 20, 33)', | |
| color: 'white', | |
| zIndex: 1, | |
| }} | |
| > | |
| <div style={{ height: '20px', width: '100%', textAlign: 'center' }}>Engine stats</div> | |
| {statusNode} | |
| <div ref={setPlotElement} /> | |
| {intervalNode} | |
| </div> | |
| ); | |
| } | |
| // D3's Tableau10 | |
| export const COLORS = ( | |
| '4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755fbab0ab'.match(/.{6}/g) as string[] | |
| ).map((x) => `#${x}`); | |
| const toSeconds = (n: number) => (n / 1000).toFixed(2); | |