diff --git a/src/Gang/Gang.ts b/src/Gang/Gang.ts index 45589a1df..c30e4b89b 100644 --- a/src/Gang/Gang.ts +++ b/src/Gang/Gang.ts @@ -97,9 +97,6 @@ export class Gang { /** Main process function called by the engine loop every game cycle */ process(numCycles = 1): void { - if (isNaN(numCycles)) { - console.error(`NaN passed into Gang.process(): ${numCycles}`); - } this.storedCycles += numCycles; if (this.storedCycles < GangConstants.minCyclesToProcess) return; @@ -112,7 +109,7 @@ export class Gang { this.processTerritoryAndPowerGains(cycles); this.storedCycles -= cycles; } catch (e: unknown) { - console.error("Exception caught when processing Gang", e); + exceptionAlert(e, true); } // Handle "nextUpdate" resolver after this update diff --git a/src/engine.tsx b/src/engine.tsx index 93eb35a40..b49d026a8 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -170,7 +170,10 @@ const Engine: { decrementAllCounters: function (numCycles = 1) { for (const [counterName, counter] of Object.entries(Engine.Counters)) { - if (counter === undefined) throw new Error("counter should not be undefined"); + if (counter === undefined) { + exceptionAlert(new Error(`counter value is undefined. counterName: ${counterName}.`), true); + continue; + } Engine.Counters[counterName] = counter - numCycles; } }, @@ -207,7 +210,7 @@ const Engine: { try { Player.bladeburner.process(); } catch (e) { - exceptionAlert(e); + exceptionAlert(e, true); } } Engine.Counters.mechanicProcess = 5; diff --git a/src/utils/helpers/exceptionAlert.tsx b/src/utils/helpers/exceptionAlert.tsx index 3cd8d9c0c..1352dd9b0 100644 --- a/src/utils/helpers/exceptionAlert.tsx +++ b/src/utils/helpers/exceptionAlert.tsx @@ -2,19 +2,43 @@ import React from "react"; import { dialogBoxCreate } from "../../ui/React/DialogBox"; import Typography from "@mui/material/Typography"; import { getErrorMetadata } from "../ErrorHelper"; +import { cyrb53 } from "../StringHelperFunctions"; -export function exceptionAlert(e: unknown): void { - console.error(e); - const errorMetadata = getErrorMetadata(e); +const errorSet = new Set(); + +/** + * Show the error in a popup: + * - Indicate that this is a bug and should be reported to developers. + * - Automatically include debug information (e.g., stack trace, commit id, user agent). + * + * @param error Error + * @param showOnlyOnce Set to true if you want to show the error only once, even when it happens many times. Default: false. + * @returns + */ +export function exceptionAlert(error: unknown, showOnlyOnce = false): void { + console.error(error); + const errorAsString = String(error); + const errorStackTrace = error instanceof Error ? error.stack : undefined; + if (showOnlyOnce) { + // Calculate the "id" of the error. + const errorId = cyrb53(errorAsString + errorStackTrace); + // Check if we showed it + if (errorSet.has(errorId)) { + return; + } else { + errorSet.add(errorId); + } + } + const errorMetadata = getErrorMetadata(error); dialogBoxCreate( <> - Caught an exception: {String(e)} + Caught an exception: {errorAsString}

- {e instanceof Error && ( + {errorStackTrace && ( - Stack: {e.stack?.toString()} + Stack: {errorStackTrace} )} Commit: {errorMetadata.version.commitHash}