| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- const lessPrecise = (num: number, precision = 5) =>
- parseFloat(num.toPrecision(precision));
- const getAvgFrameTime = (times: number[]) =>
- lessPrecise(times.reduce((a, b) => a + b) / times.length);
- export class Debug {
- public static DEBUG_LOG_TIMES = true;
- private static TIMES_AGGR: Record<string, { t: number; times: number[] }> =
- {};
- private static TIMES_AVG: Record<
- string,
- { t: number; times: number[]; avg: number | null }
- > = {};
- private static LAST_DEBUG_LOG_CALL = 0;
- private static DEBUG_LOG_INTERVAL_ID: null | number = null;
- private static LAST_FRAME_TIMESTAMP = 0;
- private static FRAME_COUNT = 0;
- private static ANIMATION_FRAME_ID: null | number = null;
- private static scheduleAnimationFrame = () => {
- if (Debug.DEBUG_LOG_INTERVAL_ID !== null) {
- Debug.ANIMATION_FRAME_ID = requestAnimationFrame((timestamp) => {
- if (Debug.LAST_FRAME_TIMESTAMP !== timestamp) {
- Debug.LAST_FRAME_TIMESTAMP = timestamp;
- Debug.FRAME_COUNT++;
- }
- if (Debug.DEBUG_LOG_INTERVAL_ID !== null) {
- Debug.scheduleAnimationFrame();
- }
- });
- }
- };
- private static setupInterval = () => {
- if (Debug.DEBUG_LOG_INTERVAL_ID === null) {
- console.info("%c(starting perf recording)", "color: lime");
- Debug.DEBUG_LOG_INTERVAL_ID = window.setInterval(Debug.debugLogger, 1000);
- Debug.scheduleAnimationFrame();
- }
- Debug.LAST_DEBUG_LOG_CALL = Date.now();
- };
- private static debugLogger = () => {
- if (Debug.DEBUG_LOG_TIMES) {
- for (const [name, { t, times }] of Object.entries(Debug.TIMES_AGGR)) {
- if (times.length) {
- console.info(
- name,
- lessPrecise(times.reduce((a, b) => a + b)),
- times.sort((a, b) => a - b).map((x) => lessPrecise(x)),
- );
- Debug.TIMES_AGGR[name] = { t, times: [] };
- }
- }
- for (const [name, { t, times, avg }] of Object.entries(Debug.TIMES_AVG)) {
- if (times.length) {
- // const avgFrameTime = getAvgFrameTime(times);
- const totalTime = times.reduce((a, b) => a + b);
- const avgFrameTime = lessPrecise(totalTime / Debug.FRAME_COUNT);
- console.info(
- name,
- `- ${times.length} calls - ${avgFrameTime}ms/frame across ${
- Debug.FRAME_COUNT
- } frames (${lessPrecise(
- (avgFrameTime / 16.67) * 100,
- 1,
- )}% of frame budget)`,
- );
- Debug.TIMES_AVG[name] = {
- t,
- times: [],
- avg:
- avg != null ? getAvgFrameTime([avg, avgFrameTime]) : avgFrameTime,
- };
- }
- }
- }
- Debug.FRAME_COUNT = 0;
- // Check for stop condition after logging
- if (
- Date.now() - Debug.LAST_DEBUG_LOG_CALL > 600 &&
- Debug.DEBUG_LOG_INTERVAL_ID !== null
- ) {
- console.info("%c(stopping perf recording)", "color: red");
- window.clearInterval(Debug.DEBUG_LOG_INTERVAL_ID);
- window.cancelAnimationFrame(Debug.ANIMATION_FRAME_ID!);
- Debug.ANIMATION_FRAME_ID = null;
- Debug.FRAME_COUNT = 0;
- Debug.LAST_FRAME_TIMESTAMP = 0;
- Debug.DEBUG_LOG_INTERVAL_ID = null;
- Debug.TIMES_AGGR = {};
- Debug.TIMES_AVG = {};
- }
- };
- public static logTime = (time?: number, name = "default") => {
- Debug.setupInterval();
- const now = performance.now();
- const { t, times } = (Debug.TIMES_AGGR[name] = Debug.TIMES_AGGR[name] || {
- t: 0,
- times: [],
- });
- if (t) {
- times.push(time != null ? time : now - t);
- }
- Debug.TIMES_AGGR[name].t = now;
- };
- public static logTimeAverage = (time?: number, name = "default") => {
- Debug.setupInterval();
- const now = performance.now();
- const { t, times } = (Debug.TIMES_AVG[name] = Debug.TIMES_AVG[name] || {
- t: 0,
- times: [],
- });
- if (t) {
- times.push(time != null ? time : now - t);
- }
- Debug.TIMES_AVG[name].t = now;
- };
- private static logWrapper =
- (type: "logTime" | "logTimeAverage") =>
- <T extends any[], R>(fn: (...args: T) => R, name = "default") => {
- return (...args: T) => {
- const t0 = performance.now();
- const ret = fn(...args);
- Debug[type](performance.now() - t0, name);
- return ret;
- };
- };
- public static logTimeWrap = Debug.logWrapper("logTime");
- public static logTimeAverageWrap = Debug.logWrapper("logTimeAverage");
- public static perfWrap = <T extends any[], R>(
- fn: (...args: T) => R,
- name = "default",
- ) => {
- return (...args: T) => {
- // eslint-disable-next-line no-console
- console.time(name);
- const ret = fn(...args);
- // eslint-disable-next-line no-console
- console.timeEnd(name);
- return ret;
- };
- };
- private static CHANGED_CACHE: Record<string, Record<string, unknown>> = {};
- public static logChanged(name: string, obj: Record<string, unknown>) {
- const prev = Debug.CHANGED_CACHE[name];
- Debug.CHANGED_CACHE[name] = obj;
- if (!prev) {
- return;
- }
- const allKeys = new Set([...Object.keys(prev), ...Object.keys(obj)]);
- const changed: Record<string, { prev: unknown; next: unknown }> = {};
- for (const key of allKeys) {
- const prevVal = prev[key];
- const nextVal = obj[key];
- if (!deepEqual(prevVal, nextVal)) {
- changed[key] = { prev: prevVal, next: nextVal };
- }
- }
- if (Object.keys(changed).length > 0) {
- console.info(`[${name}] changed:`, changed);
- }
- }
- }
- function deepEqual(a: unknown, b: unknown): boolean {
- if (Object.is(a, b)) {
- return true;
- }
- if (
- a === null ||
- b === null ||
- typeof a !== "object" ||
- typeof b !== "object"
- ) {
- return false;
- }
- if (Array.isArray(a) !== Array.isArray(b)) {
- return false;
- }
- const keysA = Object.keys(a as Record<string, unknown>);
- const keysB = Object.keys(b as Record<string, unknown>);
- if (keysA.length !== keysB.length) {
- return false;
- }
- for (const key of keysA) {
- if (
- !deepEqual(
- (a as Record<string, unknown>)[key],
- (b as Record<string, unknown>)[key],
- )
- ) {
- return false;
- }
- }
- return true;
- }
|