SETTINGS: Add an autoexec setting (#505)

This commit is contained in:
David Walker 2023-05-08 21:13:05 -07:00 committed by GitHub
parent 4e07900c5a
commit e2e9b084bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 6 deletions

@ -0,0 +1,106 @@
import React, { useState } from "react";
import { Box, InputAdornment, TextField, Tooltip, Typography } from "@mui/material";
import { Settings } from "../../Settings/Settings";
import { parseCommand } from "../../Terminal/Parser";
import { resolveScriptFilePath } from "../../Paths/ScriptFilePath";
import { formatRam } from "../../ui/formatNumber";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import WarningIcon from "@mui/icons-material/Warning";
import ErrorIcon from "@mui/icons-material/Error";
import { Player } from "@player";
interface IProps {
tooltip: React.ReactElement;
label: string;
}
export const AutoexecInput = (props: IProps): React.ReactElement => {
const [autoexec, setAutoexec] = useState(Settings.AutoexecScript);
function handleAutoexecChange(event: React.ChangeEvent<HTMLInputElement>): void {
Settings.AutoexecScript = event.target.value;
setAutoexec(event.target.value);
}
// None of these errors block the saving of the setting; what is invalid now
// could become valid later.
function createTooltip() {
const args = parseCommand(autoexec);
if (args.length === 0) {
return (
<Tooltip title={<Typography>No script will be auto-launched</Typography>}>
<CheckCircleIcon color="primary" />
</Tooltip>
);
}
const cmd = String(args[0]);
const scriptPath = resolveScriptFilePath(cmd);
if (!scriptPath) {
return (
<Tooltip title={<Typography>"{cmd}" is invalid for a script name (maybe missing suffix?)</Typography>}>
<ErrorIcon color="error" />
</Tooltip>
);
}
const home = Player.getHomeComputer();
const script = home.scripts.get(scriptPath);
if (!script) {
return (
<Tooltip title={<Typography>{cmd} does not exist!</Typography>}>
<ErrorIcon color="error" />
</Tooltip>
);
}
const ramUsage = script.getRamUsage(home.scripts);
if (ramUsage === null) {
return (
<Tooltip title={<Typography>{cmd} has errors!</Typography>}>
<ErrorIcon color="error" />
</Tooltip>
);
}
// Stolen from Prestige.ts
const minRam = Player.sourceFileLvl(9) >= 2 ? 128 : Player.sourceFileLvl(1) > 0 ? 32 : 8;
if (ramUsage <= minRam) {
return (
<Tooltip
title={
<Typography>
{cmd} costs {formatRam(ramUsage)}
</Typography>
}
>
<CheckCircleIcon color="primary" />
</Tooltip>
);
} else {
return (
<Tooltip
title={
<Typography>
{cmd} costs {formatRam(ramUsage)}, you might only have {formatRam(minRam)} on home!
</Typography>
}
>
<WarningIcon color="warning" />
</Tooltip>
);
}
}
return (
<Box>
<Tooltip title={<Typography>{props.tooltip}</Typography>}>
<Typography>{props.label}</Typography>
</Tooltip>
<TextField
fullWidth
InputProps={{
endAdornment: <InputAdornment position="end">{createTooltip()}</InputAdornment>,
}}
value={autoexec}
onChange={handleAutoexecChange}
/>
</Box>
);
};

@ -2,6 +2,7 @@ import React, { useState } from "react";
import { Settings } from "../../Settings/Settings";
import { GameOptionsPage } from "./GameOptionsPage";
import { OptionsSlider } from "./OptionsSlider";
import { AutoexecInput } from "./AutoexecInput";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
export const SystemPage = (): React.ReactElement => {
@ -45,6 +46,16 @@ export const SystemPage = (): React.ReactElement => {
<GameOptionsPage title="System">
{/* Wrap in a React fragment to prevent the sliders from breaking as list items */}
<>
<AutoexecInput
label="Autoexec Script + Args"
tooltip={
<>
Path to a script (with optional args) to run on game load. The script will be run on home, launched before
any saved running scripts. It will have the "temporary" setting, so if it stays running it won't be saved.
</>
}
/>
<br />
<OptionsSlider
label=".script exec time (ms)"
initialValue={execTime}
@ -148,7 +159,8 @@ export const SystemPage = (): React.ReactElement => {
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.
a lot. You'll have to restart your script every time you launch the game, possibly by using the "autoexec"
option.
</>
}
/>

@ -23,11 +23,13 @@ import { Settings } from "./Settings/Settings";
import { generate } from "escodegen";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { formatRam } from "./ui/formatNumber";
import { arrayToString } from "./utils/helpers/ArrayHelpers";
import { roundToTwo } from "./utils/helpers/roundToTwo";
import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
import { parseCommand } from "./Terminal/Parser";
import { Terminal } from "./Terminal";
import { ScriptArg } from "@nsdefs";
import { handleUnknownError, CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/NetscriptHelpers";
@ -284,10 +286,12 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
const ramAvailable = server.maxRam - server.ramUsed;
// Check failure conditions before generating the workersScript and return false
if (ramUsage > ramAvailable + 0.001) {
dialogBoxCreate(
`Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(runningScriptObj.args)}.\n` +
`This can occur when you reload the game and the script's RAM usage has increased (either because of an update to the game or ` +
`your changes to the script).\nThis can also occur if you have attempted to launch a script from a tail window with insufficient RAM. `,
deferredError(
`Not enough RAM to run script ${runningScriptObj.filename} with args ${arrayToString(
runningScriptObj.args,
)}, needed ${formatRam(ramUsage)} but only have ${formatRam(ramAvailable)} free
If you are seeing this on startup, likely causes are that the autoexec script is too big to fit in RAM, or it took up too much space and other previously running scripts couldn't fit on home.
Otherwise, this can also occur if you have attempted to launch a script from a tail window with insufficient RAM.`,
);
return false;
}
@ -295,7 +299,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
// Get the pid
const pid = generateNextPid();
if (pid === -1) {
dialogBoxCreate(
deferredError(
`Failed to start script because could not find available PID. This is most ` +
`because you have too many scripts running.`,
);
@ -340,6 +344,42 @@ export function updateOnlineScriptTimes(numCycles = 1): void {
}
}
// Needed for popping dialog boxes in functions that run *before* the UI is
// created, and thus before AlertManager exists to listen to the alerts we
// create.
function deferredError(msg: string) {
setTimeout(() => dialogBoxCreate(msg), 0);
}
function createAutoexec(server: BaseServer): RunningScript | null {
const args = parseCommand(Settings.AutoexecScript);
if (args.length === 0) return null;
const cmd = String(args[0]);
const scriptPath = resolveScriptFilePath(cmd);
if (!scriptPath) {
deferredError(`While running autoexec script:
"${cmd}" is invalid for a script name (maybe missing suffix?)`);
return null;
}
const script = server.scripts.get(scriptPath);
if (!script) {
deferredError(`While running autoexec script:
"${cmd}" does not exist!`);
return null;
}
const ramUsage = script.getRamUsage(server.scripts);
if (ramUsage === null) {
deferredError(`While running autoexec script:
"${cmd}" has errors!`);
return null;
}
args.shift();
const rs = new RunningScript(script, ramUsage, args);
rs.temporary = true;
return rs;
}
/**
* Called when the game is loaded. Loads all running scripts (from all servers)
* into worker scripts so that they will start running
@ -360,6 +400,13 @@ export function loadAllRunningScripts(): void {
// Start game with no scripts
continue;
}
if (server.hostname === "home") {
// Push autoexec script onto the front of the list
const runningScript = createAutoexec(server);
if (runningScript) {
rsList.unshift(runningScript);
}
}
for (const runningScript of rsList) {
startWorkerScript(runningScript, server);
scriptCalculateOfflineProduction(runningScript);

@ -10,6 +10,8 @@ export const Settings = {
ActiveScriptsServerPageSize: 10,
/** How many scripts per page */
ActiveScriptsScriptPageSize: 10,
/** Script + args to launch on game load */
AutoexecScript: "",
/** How often the game should autosave the player's progress, in seconds. */
AutosaveInterval: 60,
/** How many milliseconds between execution points for Netscript 1 statements. */