debug.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. declare global {
  2. interface Window {
  3. debug: typeof Debug;
  4. }
  5. }
  6. const lessPrecise = (num: number, precision = 5) =>
  7. parseFloat(num.toPrecision(precision));
  8. const getAvgFrameTime = (times: number[]) =>
  9. lessPrecise(times.reduce((a, b) => a + b) / times.length);
  10. const getFps = (frametime: number) => lessPrecise(1000 / frametime);
  11. export class Debug {
  12. public static DEBUG_LOG_TIMES = true;
  13. private static TIMES_AGGR: Record<string, { t: number; times: number[] }> =
  14. {};
  15. private static TIMES_AVG: Record<
  16. string,
  17. { t: number; times: number[]; avg: number | null }
  18. > = {};
  19. private static LAST_DEBUG_LOG_CALL = 0;
  20. private static DEBUG_LOG_INTERVAL_ID: null | number = null;
  21. private static setupInterval = () => {
  22. if (Debug.DEBUG_LOG_INTERVAL_ID === null) {
  23. console.info("%c(starting perf recording)", "color: lime");
  24. Debug.DEBUG_LOG_INTERVAL_ID = window.setInterval(Debug.debugLogger, 1000);
  25. }
  26. Debug.LAST_DEBUG_LOG_CALL = Date.now();
  27. };
  28. private static debugLogger = () => {
  29. if (
  30. Date.now() - Debug.LAST_DEBUG_LOG_CALL > 600 &&
  31. Debug.DEBUG_LOG_INTERVAL_ID !== null
  32. ) {
  33. window.clearInterval(Debug.DEBUG_LOG_INTERVAL_ID);
  34. Debug.DEBUG_LOG_INTERVAL_ID = null;
  35. for (const [name, { avg }] of Object.entries(Debug.TIMES_AVG)) {
  36. if (avg != null) {
  37. console.info(
  38. `%c${name} run avg: ${avg}ms (${getFps(avg)} fps)`,
  39. "color: blue",
  40. );
  41. }
  42. }
  43. console.info("%c(stopping perf recording)", "color: red");
  44. Debug.TIMES_AGGR = {};
  45. Debug.TIMES_AVG = {};
  46. return;
  47. }
  48. if (Debug.DEBUG_LOG_TIMES) {
  49. for (const [name, { t, times }] of Object.entries(Debug.TIMES_AGGR)) {
  50. if (times.length) {
  51. console.info(
  52. name,
  53. lessPrecise(times.reduce((a, b) => a + b)),
  54. times.sort((a, b) => a - b).map((x) => lessPrecise(x)),
  55. );
  56. Debug.TIMES_AGGR[name] = { t, times: [] };
  57. }
  58. }
  59. for (const [name, { t, times, avg }] of Object.entries(Debug.TIMES_AVG)) {
  60. if (times.length) {
  61. const avgFrameTime = getAvgFrameTime(times);
  62. console.info(name, `${avgFrameTime}ms (${getFps(avgFrameTime)} fps)`);
  63. Debug.TIMES_AVG[name] = {
  64. t,
  65. times: [],
  66. avg:
  67. avg != null ? getAvgFrameTime([avg, avgFrameTime]) : avgFrameTime,
  68. };
  69. }
  70. }
  71. }
  72. };
  73. public static logTime = (time?: number, name = "default") => {
  74. Debug.setupInterval();
  75. const now = performance.now();
  76. const { t, times } = (Debug.TIMES_AGGR[name] = Debug.TIMES_AGGR[name] || {
  77. t: 0,
  78. times: [],
  79. });
  80. if (t) {
  81. times.push(time != null ? time : now - t);
  82. }
  83. Debug.TIMES_AGGR[name].t = now;
  84. };
  85. public static logTimeAverage = (time?: number, name = "default") => {
  86. Debug.setupInterval();
  87. const now = performance.now();
  88. const { t, times } = (Debug.TIMES_AVG[name] = Debug.TIMES_AVG[name] || {
  89. t: 0,
  90. times: [],
  91. });
  92. if (t) {
  93. times.push(time != null ? time : now - t);
  94. }
  95. Debug.TIMES_AVG[name].t = now;
  96. };
  97. private static logWrapper =
  98. (type: "logTime" | "logTimeAverage") =>
  99. <T extends any[], R>(fn: (...args: T) => R, name = "default") => {
  100. return (...args: T) => {
  101. const t0 = performance.now();
  102. const ret = fn(...args);
  103. Debug.logTime(performance.now() - t0, name);
  104. return ret;
  105. };
  106. };
  107. public static logTimeWrap = Debug.logWrapper("logTime");
  108. public static logTimeAverageWrap = Debug.logWrapper("logTimeAverage");
  109. public static perfWrap = <T extends any[], R>(
  110. fn: (...args: T) => R,
  111. name = "default",
  112. ) => {
  113. return (...args: T) => {
  114. // eslint-disable-next-line no-console
  115. console.time(name);
  116. const ret = fn(...args);
  117. // eslint-disable-next-line no-console
  118. console.timeEnd(name);
  119. return ret;
  120. };
  121. };
  122. }
  123. //@ts-ignore
  124. window.debug = Debug;