mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-09 17:23:53 +01:00
BUGFIX: Fix "Router called before initialization" race (#1474)
If the game takes long enough to load, certain counters can become eligible to run as soon as Engine.start() runs. When this happens, eventually Router.page() is called, which throws an Error since Router isn't initialized yet. (Dropping a breakpoint before Engine.start() and waiting at least 30 seconds is enough to reliably repro, but I have seen this both live and in tests.) This fixes it so that Router.page() is valid immediately, returning a value of Page.LoadingScreen. It also removes the isInitialized field, since this is now redundant. Trying to switch pages is still an error, but that doesn't happen without user input, whereas checking the current page is quite common. This also consolidates a check for "should we show toasts" behind a function in Router, making the logic central and equal for a few usecases. This means (for instance) that the "autosave is disabled" logic won't run during infiltration. (The toast should have already been suppressed.)
This commit is contained in:
parent
2b6ec5cd33
commit
06553d9700
@ -22,6 +22,7 @@ import { Skills } from "./data/Skills";
|
||||
import { City } from "./City";
|
||||
import { Player } from "@player";
|
||||
import { Router } from "../ui/GameRoot";
|
||||
import { Page } from "../ui/Router";
|
||||
import { ConsoleHelpText } from "./data/Help";
|
||||
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../utils/helpers/getRandomIntInclusive";
|
||||
@ -1307,7 +1308,7 @@ export class Bladeburner {
|
||||
|
||||
process(): void {
|
||||
// Edge race condition when the engine checks the processing counters and attempts to route before the router is initialized.
|
||||
if (!Router.isInitialized) return;
|
||||
if (Router.page() === Page.LoadingScreen) return;
|
||||
|
||||
// If the Player starts doing some other actions, set action to idle and alert
|
||||
if (!Player.hasAugmentation(AugmentationName.BladesSimulacrum, true) && Player.currentWork) {
|
||||
|
@ -3,7 +3,6 @@ import { Message } from "./Message";
|
||||
import { AugmentationName, CompletedProgramName, FactionName, MessageFilename } from "@enums";
|
||||
import { Router } from "../ui/GameRoot";
|
||||
import { Player } from "@player";
|
||||
import { Page } from "../ui/Router";
|
||||
import { GetServer } from "../Server/AllServers";
|
||||
import { SpecialServers } from "../Server/data/SpecialServers";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
@ -53,7 +52,7 @@ function recvd(name: MessageFilename): boolean {
|
||||
|
||||
//Checks if any of the 'timed' messages should be sent
|
||||
function checkForMessagesToSend(): void {
|
||||
if (Router.page() === Page.BitVerse) return;
|
||||
if (Router.hidingMessages()) return;
|
||||
|
||||
if (Player.hasAugmentation(AugmentationName.TheRedPill, true)) {
|
||||
//Get the world daemon required hacking level
|
||||
|
@ -8,7 +8,6 @@ import { Factions } from "./Faction/Factions";
|
||||
import { staneksGift } from "./CotMG/Helper";
|
||||
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
|
||||
import { Router } from "./ui/GameRoot";
|
||||
import { Page } from "./ui/Router";
|
||||
import "./utils/Protections"; // Side-effect: Protect against certain unrecoverable errors
|
||||
import "./PersonObjects/Player/PlayerObject"; // For side-effect of creating Player
|
||||
|
||||
@ -441,8 +440,7 @@ function warnAutosaveDisabled(): void {
|
||||
|
||||
// We don't want this warning to show up on certain pages.
|
||||
// When in recovery or importing we want to keep autosave disabled.
|
||||
const ignoredPages = [Page.Recovery as Page, Page.ImportSave];
|
||||
if (ignoredPages.includes(Router.page())) return;
|
||||
if (Router.hidingMessages()) return;
|
||||
|
||||
const warningToast = (
|
||||
<>
|
||||
|
@ -18,7 +18,8 @@ import { dialogBoxCreate } from "./React/DialogBox";
|
||||
import { GetAllServers } from "../Server/AllServers";
|
||||
import { StockMarket } from "../StockMarket/StockMarket";
|
||||
|
||||
import { Page, PageWithContext, IRouter, ComplexPage, PageContext } from "./Router";
|
||||
import type { PageWithContext, IRouter, ComplexPage, PageContext } from "./Router";
|
||||
import { Page } from "./Router";
|
||||
import { Overview } from "./React/Overview";
|
||||
import { SidebarRoot } from "../Sidebar/ui/SidebarRoot";
|
||||
import { AugmentationsRoot } from "../Augmentation/ui/AugmentationsRoot";
|
||||
@ -90,20 +91,18 @@ const useStyles = makeStyles()((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const uninitialized = (): void => {
|
||||
throw new Error("Router called before initialization - uninitialized");
|
||||
};
|
||||
|
||||
const MAX_PAGES_IN_HISTORY = 10;
|
||||
|
||||
export let Router: IRouter = {
|
||||
isInitialized: false,
|
||||
page: () => {
|
||||
throw new Error("Router called before initialization - page");
|
||||
return Page.LoadingScreen;
|
||||
},
|
||||
allowRouting: uninitialized,
|
||||
toPage: () => {
|
||||
throw new Error("Router called before initialization - toPage");
|
||||
allowRouting: () => {
|
||||
throw new Error("Router called before initialization - allowRouting");
|
||||
},
|
||||
hidingMessages: () => true,
|
||||
toPage: (page: Page) => {
|
||||
throw new Error(`Router called before initialization - toPage(${page})`);
|
||||
},
|
||||
back: () => {
|
||||
throw new Error("Router called before initialization - back");
|
||||
@ -163,10 +162,18 @@ export function GameRoot(): React.ReactElement {
|
||||
console.error(`Routing is currently disabled - Attempted router.${name}()`);
|
||||
}
|
||||
|
||||
const hiddenPages = new Set([
|
||||
Page.Recovery,
|
||||
Page.ImportSave,
|
||||
Page.BitVerse,
|
||||
Page.Infiltration,
|
||||
Page.BladeburnerCinematic,
|
||||
]);
|
||||
|
||||
Router = {
|
||||
isInitialized: true,
|
||||
page: () => pageWithContext.page,
|
||||
allowRouting: (value: boolean) => setAllowRoutingCalls(value),
|
||||
hidingMessages: () => hiddenPages.has(pageWithContext.page),
|
||||
toPage: (page: Page, context?: PageContext<ComplexPage>) => {
|
||||
if (!allowRoutingCalls) return attemptedForbiddenRouting("toPage");
|
||||
switch (page) {
|
||||
@ -199,32 +206,28 @@ export function GameRoot(): React.ReactElement {
|
||||
|
||||
let mainPage = <Typography>Cannot load</Typography>;
|
||||
let withSidebar = true;
|
||||
let withPopups = true;
|
||||
const hidePopups = Router.hidingMessages();
|
||||
let bypassGame = false;
|
||||
switch (pageWithContext.page) {
|
||||
case Page.Recovery: {
|
||||
mainPage = <RecoveryRoot softReset={softReset} />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
bypassGame = true;
|
||||
break;
|
||||
}
|
||||
case Page.BitVerse: {
|
||||
mainPage = <BitverseRoot flume={pageWithContext.flume} quick={pageWithContext.quick} />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
break;
|
||||
}
|
||||
case Page.Infiltration: {
|
||||
mainPage = <InfiltrationRoot location={pageWithContext.location} />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
break;
|
||||
}
|
||||
case Page.BladeburnerCinematic: {
|
||||
mainPage = <BladeburnerCinematic />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
break;
|
||||
}
|
||||
case Page.Work: {
|
||||
@ -377,7 +380,6 @@ export function GameRoot(): React.ReactElement {
|
||||
case Page.ImportSave: {
|
||||
mainPage = <ImportSave saveData={pageWithContext.saveData} automatic={!!pageWithContext.automatic} />;
|
||||
withSidebar = false;
|
||||
withPopups = false;
|
||||
bypassGame = true;
|
||||
}
|
||||
}
|
||||
@ -410,11 +412,11 @@ export function GameRoot(): React.ReactElement {
|
||||
<Box className={classes.root}>{mainPage}</Box>
|
||||
)}
|
||||
<Unclickable />
|
||||
<LogBoxManager hidden={!withPopups} />
|
||||
<AlertManager hidden={!withPopups} />
|
||||
<PromptManager hidden={!withPopups} />
|
||||
<FactionInvitationManager hidden={!withPopups} />
|
||||
<Snackbar hidden={!withPopups} />
|
||||
<LogBoxManager hidden={hidePopups} />
|
||||
<AlertManager hidden={hidePopups} />
|
||||
<PromptManager hidden={hidePopups} />
|
||||
<FactionInvitationManager hidden={hidePopups} />
|
||||
<Snackbar hidden={hidePopups} />
|
||||
<Apr1 />
|
||||
</SnackbarProvider>
|
||||
</HistoryProvider>
|
||||
|
@ -49,6 +49,7 @@ export enum ComplexPage {
|
||||
Location = "Location",
|
||||
ImportSave = "Import Save",
|
||||
Documentation = "Documentation",
|
||||
LoadingScreen = "Loading Screen", // Has no PageContext, and thus toPage() cannot be used
|
||||
}
|
||||
|
||||
// Using the same name as both type and object to mimic enum-like behavior.
|
||||
@ -86,6 +87,7 @@ export type PageWithContext =
|
||||
| ({ page: ComplexPage.Location } & PageContext<ComplexPage.Location>)
|
||||
| ({ page: ComplexPage.ImportSave } & PageContext<ComplexPage.ImportSave>)
|
||||
| ({ page: ComplexPage.Documentation } & PageContext<ComplexPage.Documentation>)
|
||||
| { page: ComplexPage.LoadingScreen }
|
||||
| { page: SimplePage };
|
||||
|
||||
export interface ScriptEditorRouteOptions {
|
||||
@ -95,9 +97,10 @@ export interface ScriptEditorRouteOptions {
|
||||
|
||||
/** The router keeps track of player navigation/routing within the game. */
|
||||
export interface IRouter {
|
||||
isInitialized: boolean;
|
||||
page(): Page;
|
||||
allowRouting(value: boolean): void;
|
||||
/** If messages/toasts are hidden on this page */
|
||||
hidingMessages(): boolean;
|
||||
toPage(page: SimplePage): void;
|
||||
toPage<T extends ComplexPage>(page: T, context: PageContext<T>): void;
|
||||
/** go to a preveious page (if any) */
|
||||
|
@ -12,6 +12,7 @@ jest.mock("../../../src/ui/GameRoot", () => ({
|
||||
Router: {
|
||||
page: () => ({}),
|
||||
toPage: () => ({}),
|
||||
hidingMessages: () => false,
|
||||
},
|
||||
}));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user