Merge pull request #2724 from danielyxie/dev

RM caching
This commit is contained in:
hydroflame 2022-01-20 16:22:41 -05:00 committed by GitHub
commit aff9b58040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 213 additions and 259 deletions

44
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -113,7 +113,7 @@ The list contains the name of (i.e. the value returned by
| | | to any position from i to i+n. |
| | | |
| | | Assuming you are initially positioned at the start of the array, determine |
| | | whether you are able to reach the last index of the array. |
| | | whether you are able to reach the last index of the array. |
+------------------------------------+------------------------------------------------------------------------------------------+
| Merge Overlapping Intervals | | Given an array of intervals, merge all overlapping intervals. An interval |
| | | is an array with two numbers, where the first number is always less than |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -48,7 +48,7 @@ for (let i = 0; i < scripts.length; ++i) {
```ts
// NS2:
const ps = ns.ps("home");
for (script of ps) {
for (let script of ps) {
ns.tprint(`${script.filename} ${ps[i].threads}`);
ns.tprint(script.args);
}

@ -19,7 +19,7 @@ export const Literatures: IMap<Literature> = {};
"money on a server, and grow() increases the amount of money on a server by some percentage (multiplicatively)<br><br>" +
"-Because hack() and grow() work by percentages, they are more effective if the target server has a high amount of money. " +
"Therefore, you should try to increase the amount of money on a server (using grow()) to a certain amount before hacking it. Two " +
"import Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()<br><br>" +
"important Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()<br><br>" +
"-Keep security level low. Security level affects everything when hacking. Two important Netscript functions " +
"for this are getServerSecurityLevel() and getServerMinSecurityLevel()<br><br>" +
"-Purchase additional servers by visiting 'Alpha Enterprises' in the city. They are relatively cheap " +

@ -9,9 +9,6 @@ import { makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { ScriptUrl } from "./Script/ScriptUrl";
import { WorkerScript } from "./Netscript/WorkerScript";
import { Script } from "./Script/Script";
import { computeHash } from "./utils/helpers/computeHash";
import { BlobCache } from "./utils/BlobCache";
import { ImportCache } from "./utils/ImportCache";
import { areImportsEquals } from "./Terminal/DirectoryHelpers";
import { IPlayer } from "./PersonObjects/IPlayer";
@ -190,31 +187,12 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
if (matchingScripts.length === 0) continue;
const [importedScript] = matchingScripts;
// Check to see if the urls for this script are stored in the cache by the hash value.
let urls = ImportCache.get(importedScript.hash());
// If we don't have it in the cache, then we need to generate the urls for it.
if (urls) {
// Verify that these urls are valid and have not been updated.
for (const url of urls) {
if (isDependencyOutOfDate(url.filename, scripts, url.moduleSequenceNumber)) {
// Revoke these URLs from the browser. We will be unable to use them again.
for (const url of urls) URL.revokeObjectURL(url.url);
// Clear the cache and prepare for new blobs.
urls = null;
ImportCache.remove(importedScript.hash());
break;
}
}
}
if (!urls) {
// Try to get a URL for the requested script and its dependencies.
urls = _getScriptUrls(importedScript, scripts, seen);
}
const urls = _getScriptUrls(importedScript, scripts, seen);
// The top url in the stack is the replacement import file for this script.
urlStack.push(...urls);
const blob = urls[urls.length - 1].url;
ImportCache.store(importedScript.hash(), urls);
// Replace the blob inside the import statement.
transformedCode = transformedCode.substring(0, node.start) + blob + transformedCode.substring(node.end);
@ -224,17 +202,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
// accidental calls to window.print() do not bring up the "print screen" dialog
transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}`;
// If we successfully transformed the code, create a blob url for it
// Compute the hash for the transformed code
const transformedHash = computeHash(transformedCode);
// Check to see if this transformed hash is in our cache
let blob = BlobCache.get(transformedHash);
if (!blob) {
blob = URL.createObjectURL(makeScriptBlob(transformedCode));
}
// Store this blob in the cache. Any script that transforms the same
// (e.g. same scripts on server, same hash value, etc) can use this blob url.
BlobCache.store(transformedHash, blob);
const blob = URL.createObjectURL(makeScriptBlob(transformedCode));
// Push the blob URL onto the top of the stack.
urlStack.push(new ScriptUrl(script.filename, blob, script.moduleSequenceNumber));
return urlStack;

@ -8,6 +8,5 @@ export function StartSharing(threads: number): () => void {
}
export function CalculateShareMult(): number {
console.log(`${sharePower} => ${CSM(sharePower)}`);
return CSM(sharePower);
}

@ -9,13 +9,14 @@ import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { computeHash } from "../utils/helpers/computeHash";
import { ImportCache } from "../utils/ImportCache";
import { IPlayer } from "../PersonObjects/IPlayer";
let globalModuleSequenceNumber = 0;
interface ScriptReference { filename: string; server: string }
interface ScriptReference {
filename: string;
server: string;
}
export class Script {
// Code for this script
@ -47,9 +48,6 @@ export class Script {
// hostname of server that this script is on.
server = "";
// sha256 hash of the code in the Script. Do not access directly.
_hash = "";
constructor(player: IPlayer | null = null, fn = "", code = "", server = "", otherScripts: Script[] = []) {
this.filename = fn;
this.code = code;
@ -57,10 +55,8 @@ export class Script {
this.server = server; // hostname of server this script is on
this.module = "";
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
this._hash = "";
if (this.code !== "" && player !== null) {
this.updateRamUsage(player, otherScripts);
this.rehash();
}
}
@ -96,27 +92,6 @@ export class Script {
markUpdated(): void {
this.module = "";
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
this.rehash();
}
/**
* Force update of the computed hash based on the source code.
*/
rehash(): void {
const oldHash = this._hash;
this._hash = computeHash(this.code);
if (oldHash !== this._hash) {
ImportCache.remove(oldHash);
}
}
/**
* If the hash is not computed, computes the hash. Otherwise return the computed hash.
* @returns the computed hash of the script
*/
hash(): string {
if (!this._hash) this.rehash();
return this._hash;
}
/**
@ -133,7 +108,9 @@ export class Script {
this.updateRamUsage(player, otherScripts);
this.markUpdated();
for (const dependent of this.dependents) {
const [dependentScript] = otherScripts.filter(s => s.filename === dependent.filename && s.server == dependent.server);
const [dependentScript] = otherScripts.filter(
(s) => s.filename === dependent.filename && s.server == dependent.server,
);
if (dependentScript !== null) dependentScript.markUpdated();
}
}
@ -166,8 +143,6 @@ export class Script {
const s = Generic_fromJSON(Script, value.data);
// Force the url to blank from the save data. Urls are not valid outside the current browser page load.
s.url = "";
// Rehash the code to ensure that hash is set properly.
s.rehash();
s.dependents = [];
return s;
}
@ -177,7 +152,7 @@ export class Script {
* @param {string} code - The code to format
* @returns The formatted code
*/
static formatCode(code: string): string {
static formatCode(code: string): string {
return code.replace(/^\s+|\s+$/g, "");
}
}

@ -30,6 +30,13 @@ export function LoadingScreen(): React.ReactElement {
const [show, setShow] = useState(false);
const [loaded, setLoaded] = useState(false);
const version = `v${CONSTANTS.VersionString} (${hash()})`;
if (process.env.NODE_ENV === "development") {
document.title = `[dev] Bitburner ${version}`;
} else {
document.title = `Bitburner ${version}`;
}
useEffect(() => {
const id = setTimeout(() => {
if (!loaded) setShow(true);
@ -70,9 +77,7 @@ export function LoadingScreen(): React.ReactElement {
<CircularProgress size={150} color="primary" />
</Grid>
<Grid item>
<Typography variant="h3">
Loading Bitburner v{CONSTANTS.VersionString} ({hash()})
</Typography>
<Typography variant="h3">Loading Bitburner {version}</Typography>
</Grid>
{show && (
<Grid item>

@ -453,14 +453,14 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</Table>
<Box sx={{ display: "flex", borderTop: `1px solid ${Settings.theme.welllight}` }}>
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-start", alignItems: "center" }}>
<IconButton onClick={save}>
<IconButton aria-label="save game" onClick={save}>
<Tooltip title="Save game">
<SaveIcon color={Settings.AutosaveInterval !== 0 ? "primary" : "error"} />
</Tooltip>
</IconButton>
</Box>
<Box sx={{ display: "flex", flex: 1, justifyContent: "flex-end", alignItems: "center" }}>
<IconButton onClick={() => setKillOpen(true)}>
<IconButton aria-label="kill all scripts" onClick={() => setKillOpen(true)}>
<Tooltip title="Kill all running scripts">
<ClearAllIcon color="error" />
</Tooltip>

@ -21,7 +21,7 @@ import TextField from "@mui/material/TextField";
import DownloadIcon from "@mui/icons-material/Download";
import UploadIcon from "@mui/icons-material/Upload";
import SaveIcon from '@mui/icons-material/Save';
import SaveIcon from "@mui/icons-material/Save";
import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { dialogBoxCreate } from "./DialogBox";
@ -152,7 +152,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
console.log(error); // We'll handle below
}
if (!newSave || newSave === '') {
if (!newSave || newSave === "") {
SnackbarEvents.emit("Save game had not content or was not base64 encoded", "error", 5000);
return;
}
@ -164,7 +164,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
console.log(error); // We'll handle below
}
if (!parsedSave || parsedSave.ctor !== 'BitburnerSaveObject' || !parsedSave.data) {
if (!parsedSave || parsedSave.ctor !== "BitburnerSaveObject" || !parsedSave.data) {
SnackbarEvents.emit("Save game did not seem valid", "error", 5000);
return;
}
@ -172,14 +172,14 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const data: ImportData = {
base64: contents,
parsed: parsedSave,
}
};
const timestamp = parsedSave.data.SaveTimestamp;
if (timestamp && timestamp !== '0') {
data.exportDate = new Date(parseInt(timestamp, 10))
if (timestamp && timestamp !== "0") {
data.exportDate = new Date(parseInt(timestamp, 10));
}
setImportData(data)
setImportData(data);
setImportSaveOpen(true);
};
reader.readAsText(file);
@ -191,7 +191,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
setImportSaveOpen(false);
save(importData.base64).then(() => {
setImportData(null);
setTimeout(() => location.reload(), 1000)
setTimeout(() => location.reload(), 1000);
});
}
@ -213,7 +213,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</Typography>
}
>
<Typography>Netscript exec time (ms)</Typography>
<Typography>.script exec time (ms)</Typography>
</Tooltip>
<Slider
value={execTime}
@ -304,128 +304,165 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.SuppressMessages}
onChange={(newValue) => Settings.SuppressMessages = newValue}
<OptionSwitch
checked={Settings.SuppressMessages}
onChange={(newValue) => (Settings.SuppressMessages = newValue)}
text="Suppress story messages"
tooltip={<>
If this is set, then any messages you receive will not appear as popups on the screen. They will
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
command.
</>} />
tooltip={
<>
If this is set, then any messages you receive will not appear as popups on the screen. They will
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
command.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.SuppressFactionInvites}
onChange={(newValue) => Settings.SuppressFactionInvites = newValue}
<OptionSwitch
checked={Settings.SuppressFactionInvites}
onChange={(newValue) => (Settings.SuppressFactionInvites = newValue)}
text="Suppress faction invites"
tooltip={<>
If this is set, then any faction invites you receive will not appear as popups on the screen.
Your outstanding faction invites can be viewed in the 'Factions' page.
</>} />
tooltip={
<>
If this is set, then any faction invites you receive will not appear as popups on the screen. Your
outstanding faction invites can be viewed in the 'Factions' page.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.SuppressTravelConfirmation}
onChange={(newValue) => Settings.SuppressTravelConfirmation = newValue}
<OptionSwitch
checked={Settings.SuppressTravelConfirmation}
onChange={(newValue) => (Settings.SuppressTravelConfirmation = newValue)}
text="Suppress travel confirmations"
tooltip={<>
If this is set, the confirmation message before traveling will not show up. You will
automatically be deducted the travel cost as soon as you click.
</>} />
tooltip={
<>
If this is set, the confirmation message before traveling will not show up. You will automatically
be deducted the travel cost as soon as you click.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.SuppressBuyAugmentationConfirmation}
onChange={(newValue) => Settings.SuppressBuyAugmentationConfirmation = newValue}
<OptionSwitch
checked={Settings.SuppressBuyAugmentationConfirmation}
onChange={(newValue) => (Settings.SuppressBuyAugmentationConfirmation = newValue)}
text="Suppress augmentations confirmation"
tooltip={<>
If this is set, the confirmation message before buying augmentation will not show up.
</>} />
tooltip={<>If this is set, the confirmation message before buying augmentation will not show up.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.SuppressTIXPopup}
onChange={(newValue) => Settings.SuppressTIXPopup = newValue}
<OptionSwitch
checked={Settings.SuppressTIXPopup}
onChange={(newValue) => (Settings.SuppressTIXPopup = newValue)}
text="Suppress TIX messages"
tooltip={<>
If this is set, the stock market will never create any popup.
</>} />
tooltip={<>If this is set, the stock market will never create any popup.</>}
/>
</ListItem>
{!!props.player.bladeburner && (
<ListItem>
<OptionSwitch checked={Settings.SuppressBladeburnerPopup}
onChange={(newValue) => Settings.SuppressBladeburnerPopup = newValue}
<OptionSwitch
checked={Settings.SuppressBladeburnerPopup}
onChange={(newValue) => (Settings.SuppressBladeburnerPopup = newValue)}
text="Suppress bladeburner popup"
tooltip={<>
If this is set, then having your Bladeburner actions interrupted by being busy with something
else will not display a popup message.
</>} />
tooltip={
<>
If this is set, then having your Bladeburner actions interrupted by being busy with something else
will not display a popup message.
</>
}
/>
</ListItem>
)}
<ListItem>
<OptionSwitch checked={Settings.SuppressSavedGameToast}
onChange={(newValue) => Settings.SuppressSavedGameToast = newValue}
<OptionSwitch
checked={Settings.SuppressSavedGameToast}
onChange={(newValue) => (Settings.SuppressSavedGameToast = newValue)}
text="Suppress Auto-Save Game Toast"
tooltip={<>
If this is set, there will be no "Game Saved!" toast appearing after an auto-save.
</>} />
tooltip={<>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.DisableHotkeys}
onChange={(newValue) => Settings.DisableHotkeys = newValue}
<OptionSwitch
checked={Settings.DisableHotkeys}
onChange={(newValue) => (Settings.DisableHotkeys = newValue)}
text="Disable hotkeys"
tooltip={<>
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and
Close (Ctrl + b)" hotkey in the Text Editor.
</>} />
tooltip={
<>
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and Close
(Ctrl + b)" hotkey in the Text Editor.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.DisableASCIIArt}
onChange={(newValue) => Settings.DisableASCIIArt = newValue}
<OptionSwitch
checked={Settings.DisableASCIIArt}
onChange={(newValue) => (Settings.DisableASCIIArt = newValue)}
text="Disable ascii art"
tooltip={<>
If this is set all ASCII art will be disabled.
</>} />
tooltip={<>If this is set all ASCII art will be disabled.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.DisableTextEffects}
onChange={(newValue) => Settings.DisableTextEffects = newValue}
<OptionSwitch
checked={Settings.DisableTextEffects}
onChange={(newValue) => (Settings.DisableTextEffects = newValue)}
text="Disable text effects"
tooltip={<>
If this is set, text effects will not be displayed. This can help if text is difficult to read
in certain areas.
</>} />
tooltip={
<>
If this is set, text effects will not be displayed. This can help if text is difficult to read in
certain areas.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.DisableOverviewProgressBars}
onChange={(newValue) => Settings.DisableOverviewProgressBars = newValue}
<OptionSwitch
checked={Settings.DisableOverviewProgressBars}
onChange={(newValue) => (Settings.DisableOverviewProgressBars = newValue)}
text="Disable Overview Progress Bars"
tooltip={<>
If this is set, the progress bars in the character overview will be hidden.
</>} />
tooltip={<>If this is set, the progress bars in the character overview will be hidden.</>}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.EnableBashHotkeys}
onChange={(newValue) => Settings.EnableBashHotkeys = newValue}
<OptionSwitch
checked={Settings.EnableBashHotkeys}
onChange={(newValue) => (Settings.EnableBashHotkeys = newValue)}
text="Enable bash hotkeys"
tooltip={<>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and
features that more closely resemble a real Bash-style shell. Note that when this mode is
enabled, the default browser shortcuts are overriden by the new Bash shortcuts.
</>} />
tooltip={
<>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and features
that more closely resemble a real Bash-style shell. Note that when this mode is enabled, the default
browser shortcuts are overriden by the new Bash shortcuts.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.UseIEC60027_2}
onChange={(newValue) => Settings.UseIEC60027_2 = newValue}
<OptionSwitch
checked={Settings.UseIEC60027_2}
onChange={(newValue) => (Settings.UseIEC60027_2 = newValue)}
text="Use GiB instead of GB"
tooltip={<>
If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.
</>} />
tooltip={
<>
If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.
</>
}
/>
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.ExcludeRunningScriptsFromSave}
onChange={(newValue) => Settings.ExcludeRunningScriptsFromSave = newValue}
<OptionSwitch
checked={Settings.ExcludeRunningScriptsFromSave}
onChange={(newValue) => (Settings.ExcludeRunningScriptsFromSave = newValue)}
text="Exclude Running Scripts from Save"
tooltip={<>
If this is set, the save file will exclude all running scripts. This is only useful if your save is lagging a lot. You'll have to restart your script every time you launch the game.
</>} />
tooltip={
<>
If this is set, the save file will exclude all running scripts. This is only useful if your save is
lagging a lot. You'll have to restart your script every time you launch the game.
</>
}
/>
</ListItem>
<ListItem>
<Tooltip
@ -460,12 +497,12 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.SaveGameOnFileSave}
onChange={(newValue) => Settings.SaveGameOnFileSave = newValue}
<OptionSwitch
checked={Settings.SaveGameOnFileSave}
onChange={(newValue) => (Settings.SaveGameOnFileSave = newValue)}
text="Save game on file save"
tooltip={<>
Save your game any time a file is saved in the script editor.
</>} />
tooltip={<>Save your game any time a file is saved in the script editor.</>}
/>
</ListItem>
<ListItem>
@ -522,20 +559,28 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</>
)}
</Grid>
<Box sx={{ display: 'grid', width: 'fit-content', height: 'fit-content' }}>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
<Button onClick={() => props.save()} startIcon={<SaveIcon />} >
<Box sx={{ display: "grid", width: "fit-content", height: "fit-content" }}>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Button onClick={() => props.save()} startIcon={<SaveIcon />}>
Save Game
</Button>
<DeleteGameButton />
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Tooltip title={<Typography>Export your game to a text file.</Typography>}>
<Button onClick={() => props.export()} startIcon={<DownloadIcon />}>
Export Game
</Button>
</Tooltip>
<Tooltip title={<Typography>Import your game from a text file.<br />This will <strong>overwrite</strong> your current game. Back it up first!</Typography>}>
<Tooltip
title={
<Typography>
Import your game from a text file.
<br />
This will <strong>overwrite</strong> your current game. Back it up first!
</Typography>
}
>
<Button onClick={startImport} startIcon={<UploadIcon />}>
Import Game
<input ref={importInput} id="import-game-file-selector" type="file" hidden onChange={onImport} />
@ -555,16 +600,18 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
The file you are attempting to import seems valid.
<br />
<br />
{importData?.exportDate && (<>
The export date of the save file is <strong>{importData?.exportDate.toString()}</strong>
<br />
<br />
</>)}
{importData?.exportDate && (
<>
The export date of the save file is <strong>{importData?.exportDate.toString()}</strong>
<br />
<br />
</>
)}
</>
}
/>
</Box>
<Box sx={{ display: 'grid' }}>
<Box sx={{ display: "grid" }}>
<Tooltip
title={
<Typography>
@ -579,8 +626,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<Button onClick={() => props.forceKill()}>Force kill all active scripts</Button>
</Tooltip>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
<SoftResetButton noConfirmation={Settings.SuppressBuyAugmentationConfirmation} onTriggered={props.softReset} />
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<SoftResetButton
noConfirmation={Settings.SuppressBuyAugmentationConfirmation}
onTriggered={props.softReset}
/>
<Tooltip
title={
<Typography>
@ -592,7 +642,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
</Tooltip>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button>
<Button onClick={() => setStyleEditorOpen(true)}>Style editor</Button>
</Box>

@ -124,7 +124,12 @@ export function Overview({ children, mode }: IProps): React.ReactElement {
<Typography flexGrow={1} color="secondary">
{header}
</Typography>
<Button variant="text" size="small" className={classes.visibilityToggle}>
<Button
aria-label="expand or collapse character overview"
variant="text"
size="small"
className={classes.visibilityToggle}
>
{<CurrentIcon className={classes.icon} color="secondary" onClick={() => setOpen((old) => !old)} />}
</Button>
</Box>

@ -1,18 +0,0 @@
const blobCache: { [hash: string]: string } = {};
export class BlobCache {
static get(hash: string): string {
return blobCache[hash];
}
static store(hash: string, value: string): void {
if (blobCache[hash]) return;
blobCache[hash] = value;
}
static removeByValue(value: string): void {
const keys = Object.keys(blobCache).filter((key) => blobCache[key] === value);
keys.forEach((key) => delete blobCache[key]);
}
}

@ -1,18 +0,0 @@
import { ScriptUrl } from "../Script/ScriptUrl";
const importCache: { [hash: string]: ScriptUrl[] } = {};
export class ImportCache {
static get(hash: string): ScriptUrl[] | null {
return importCache[hash] || null;
}
static store(hash: string, value: ScriptUrl[]): void {
if (importCache[hash]) return;
importCache[hash] = value;
}
static remove(hash: string): void {
delete importCache[hash];
}
}

@ -1,12 +0,0 @@
import { sha256 } from "js-sha256";
/**
* Computes a SHA-256 hash of a string synchronously
* @param message The input string
* @returns The SHA-256 hash in hex
*/
export function computeHash(message: string): string {
const hash = sha256.create();
hash.update(message);
return hash.hex();
}