Wait for autostart by promise

Since the completion is checked using the promise the `onTick` function
can be removed.
This replaces the `AutoStart` `AutoStartClient` and `AutoStartHost` by
async functions.
The functions can now return something to the engine. That is used to
inform the engine which page to open. That was previously done in
JavaScript. Which is ugly because it doesn't know whether it's in
visual-mode.
This commit is contained in:
phosit 2026-03-14 10:13:49 +01:00
parent 6805efc08a
commit 46b27f22ca
No known key found for this signature in database
GPG key ID: C9430B600671C268
5 changed files with 140 additions and 206 deletions

View file

@ -1,36 +1,22 @@
class AutoStart
async function autoStart(cmdLineArgs)
{
constructor(cmdLineArgs)
{
this.playerAssignments = {
"local": {
"player": +(cmdLineArgs?.['autostart-player'] ?? 1),
"name": "anonymous",
},
};
this.settings = new GameSettings().init();
const playerAssignments = {
"local": {
"player": +(cmdLineArgs?.['autostart-player'] ?? 1),
"name": "anonymous",
},
};
const settings = new GameSettings().init();
// Enable cheats in SP
this.settings.cheats.setEnabled(true);
// Enable cheats in SP
settings.cheats.setEnabled(true);
parseCmdLineArgs(this.settings, cmdLineArgs);
parseCmdLineArgs(settings, cmdLineArgs);
this.settings.launchGame(this.playerAssignments, !('autostart-disable-replay' in cmdLineArgs));
this.onLaunch();
}
settings.launchGame(playerAssignments, !('autostart-disable-replay' in cmdLineArgs));
onTick()
{
}
/**
* In the visual autostart path, we need to show the loading screen.
*/
onLaunch()
{
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": this.settings.finalizedAttributes,
"playerAssignments": this.playerAssignments
});
}
return ["page_loading.xml", {
"attribs": settings.finalizedAttributes,
"playerAssignments": playerAssignments
}];
}

View file

@ -1,61 +1,36 @@
class AutoStartClient
async function autoStartClient(cmdLineArgs)
{
done = false;
constructor(cmdLineArgs)
try
{
this.playerAssignments = {};
try
{
const playerName = cmdLineArgs['autostart-playername'] || "anonymous";
const ip = cmdLineArgs['autostart-client'] ?? "127.0.0.1";
const port = +(cmdLineArgs['autostart-port'] ?? 5073);
Engine.StartNetworkJoin(playerName, ip, port, !('autostart-disable-replay' in cmdLineArgs));
}
catch(e)
{
const message = sprintf(translate("Cannot join game: %(message)s."), { "message": e.message });
messageBox(400, 200, message, translate("Error"));
}
(async() =>
{
while (true)
{
const message = await Engine.PollNetworkClient();
switch (message.type)
{
case "players":
this.playerAssignments = message.newAssignments;
Engine.SendNetworkReady(2);
break;
case "start":
this.onLaunch(message);
// Process further pending netmessages in the session page.
this.done = true;
return;
default:
}
}
})();
const playerName = cmdLineArgs['autostart-playername'] || "anonymous";
const ip = cmdLineArgs['autostart-client'] ?? "127.0.0.1";
const port = +(cmdLineArgs['autostart-port'] ?? 5073);
Engine.StartNetworkJoin(playerName, ip, port, !('autostart-disable-replay' in cmdLineArgs));
}
catch(e)
{
const message = sprintf(translate("Cannot join game: %(message)s."), { "message": e.message });
messageBox(400, 200, message, translate("Error"));
}
onTick()
let playerAssignments = {};
while (true)
{
return this.done;
}
const message = await Engine.PollNetworkClient();
/**
* In the visual autostart path, we need to show the loading screen.
* Overload this as appropriate, the default implementation works for the public mod.
*/
onLaunch(message)
{
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": message.initAttributes,
"isRejoining": true,
"playerAssignments": this.playerAssignments
});
switch (message.type)
{
case "players":
playerAssignments = message.newAssignments;
Engine.SendNetworkReady(2);
break;
case "start":
return ["page_loading.xml", {
"attribs": message.initAttributes,
"isRejoining": true,
"playerAssignments": playerAssignments
}];
default:
}
}
}

View file

@ -1,97 +1,74 @@
class AutoStartHost
async function autoStartHost(cmdLineArgs)
{
done = false;
constructor(cmdLineArgs)
const maxPlayers = +(cmdLineArgs['autostart-host-players'] ?? 2);
try
{
this.launched = false;
this.playerAssignments = {};
const playerName = cmdLineArgs['autostart-playername'] || "anonymous";
const port = +(cmdLineArgs['autostart-port'] ?? 5073);
this.maxPlayers = +(cmdLineArgs['autostart-host-players'] ?? 2);
this.cmdLineArgs = cmdLineArgs;
try
{
const playerName = cmdLineArgs['autostart-playername'] || "anonymous";
const port = +(cmdLineArgs['autostart-port'] ?? 5073);
// Password not implemented for autostart.
Engine.StartNetworkHost(playerName, port, "", false, !('autostart-disable-replay' in cmdLineArgs));
}
catch(e)
{
const message = sprintf(translate("Cannot host game: %(message)s."), { "message": e.message });
messageBox(400, 200, message, translate("Error"));
}
/**
* Handles a simple implementation of player assignments.
* Should not need be overloaded in mods unless you want to change that logic.
*/
(async() =>
{
while (true)
{
const message = await Engine.PollNetworkClient();
switch (message.type)
{
case "players":
{
this.playerAssignments = message.newAssignments;
Engine.SendNetworkReady(2);
let max = 0;
for (const uid in this.playerAssignments)
{
max = Math.max(this.playerAssignments[uid].player, max);
if (this.playerAssignments[uid].player == -1)
Engine.AssignNetworkPlayer(++max, uid);
}
break;
}
case "ready":
this.playerAssignments[message.guid].status = message.status;
break;
case "start":
this.done = true;
return;
default:
}
if (!this.launched)
{
const assignementArray = Object.values(this.playerAssignments);
if (assignementArray.length === this.maxPlayers &&
assignementArray.every(assignement =>
assignement.player !== -1 || assignement.status !== 0))
{
this.onLaunch();
}
}
}
})();
// Password not implemented for autostart.
Engine.StartNetworkHost(playerName, port, "", false, !('autostart-disable-replay' in cmdLineArgs));
}
onTick()
catch(e)
{
return this.done;
const message = sprintf(translate("Cannot host game: %(message)s."), { "message": e.message });
messageBox(400, 200, message, translate("Error"));
}
/**
* In the visual autostart path, we need to show the loading screen.
* Overload this as appropriate.
* Handles a simple implementation of player assignments.
* Should not need be overloaded in mods unless you want to change that logic.
*/
onLaunch()
let playerAssignments = {};
while (true)
{
this.launched = true;
const message = await Engine.PollNetworkClient();
switch (message.type)
{
case "players":
{
playerAssignments = message.newAssignments;
Engine.SendNetworkReady(2);
let max = 0;
for (const uid in playerAssignments)
{
max = Math.max(playerAssignments[uid].player, max);
if (playerAssignments[uid].player == -1)
Engine.AssignNetworkPlayer(++max, uid);
}
break;
}
case "ready":
playerAssignments[message.guid].status = message.status;
break;
default:
}
this.settings = new GameSettings().init();
parseCmdLineArgs(this.settings, this.cmdLineArgs);
this.settings.playerCount.setNb(Object.keys(this.playerAssignments).length);
this.settings.launchGame(this.playerAssignments, this.storeReplay);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": this.settings.finalizedAttributes,
"playerAssignments": this.playerAssignments
});
const assignementArray = Object.values(playerAssignments);
if (assignementArray.length === maxPlayers &&
assignementArray.every(assignement =>
assignement.player !== -1 || assignement.status !== 0))
{
break;
}
}
const settings = new GameSettings().init();
parseCmdLineArgs(settings, cmdLineArgs);
settings.playerCount.setNb(Object.keys(playerAssignments).length);
settings.launchGame(playerAssignments, false);
while ((await Engine.PollNetworkClient()).type !== "start")
{
// Just wait for condition
}
return ["page_loading.xml", {
"attribs": settings.finalizedAttributes,
"playerAssignments": playerAssignments
}];
}

View file

@ -7,8 +7,6 @@
Engine.HasXmppClient = () => false;
Engine.SetRankedGame = () => {};
Engine.TextureExists = () => false;
Engine.OpenChildPage = () => {};
Engine.SwitchGuiPage = () => {};
var translateObjectKeys = () => {};
var translate = x => x;
@ -20,11 +18,9 @@ Engine.LoadScript("globalscripts/");
// TODO: clean this up and show errors better in the non-visual path.
Engine.LoadScript("gui/common/functions_msgbox.js");
var autostartInstance;
function autostartClient(cmdLineArgs)
{
autostartInstance = new AutoStartClient(cmdLineArgs);
return autoStartClient(cmdLineArgs);
}
/**
@ -44,15 +40,6 @@ function autostartHost(cmdLineArgs, networked = false)
Engine.LoadScript("gamesettings/attributes/");
if (networked)
autostartInstance = new AutoStartHost(cmdLineArgs);
else
autostartInstance = new AutoStart(cmdLineArgs);
}
/**
* @returns false if the loop should carry on.
*/
function onTick()
{
return autostartInstance.onTick();
return autoStartHost(cmdLineArgs);
return autoStart(cmdLineArgs);
}

View file

@ -109,6 +109,7 @@
#include <fmt/format.h>
#include <fstream>
#include <js/CallArgs.h>
#include <js/Promise.h>
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/Value.h>
@ -841,39 +842,47 @@ bool Autostart(const CmdLineArgs& args)
JS::RootedValue cmdLineArgs(rq.cx);
Script::ToJSVal(rq, &cmdLineArgs, args);
if (args.Has("autostart-client") || args.Has("autostart-host"))
{
// Pass the default port if undefined, to avoid duplicating it in JS.
if (!Script::HasProperty(rq, cmdLineArgs, "autostart-port"))
Script::SetProperty(rq, cmdLineArgs, "autostart-port", PS_DEFAULT_PORT);
// Pass the default port if undefined, to avoid duplicating it in JS.
if (!Script::HasProperty(rq, cmdLineArgs, "autostart-port"))
Script::SetProperty(rq, cmdLineArgs, "autostart-port", PS_DEFAULT_PORT);
JS::RootedValue global(rq.cx, rq.globalValue());
if (!ScriptFunction::CallVoid(rq, global, args.Has("autostart-client") ? "autostartClient" : "autostartHost", cmdLineArgs, true))
return false;
bool shouldQuit = false;
while (!shouldQuit)
{
g_NetClient->Poll();
g_ScriptContext->RunJobs();
if (!ScriptFunction::Call(rq, global, "onTick", shouldQuit))
return false;
std::this_thread::sleep_for(std::chrono::microseconds(200));
}
}
else
JS::RootedValue resultValue{rq.cx};
JS::RootedValue global(rq.cx, rq.globalValue());
if (!ScriptFunction::Call(rq, global,
args.Has("autostart-client") ? "autostartClient" : "autostartHost", &resultValue,
cmdLineArgs, args.Has("autostart-host")) && !resultValue.isObject())
{
JS::RootedValue global(rq.cx, rq.globalValue());
if (!ScriptFunction::CallVoid(rq, global, "autostartHost", cmdLineArgs, false))
return false;
return false;
}
JS::RootedObject result{rq.cx, &resultValue.toObject()};
while (JS::IsPromiseObject(result) && JS::GetPromiseState(result) == JS::PromiseState::Pending)
{
g_ScriptContext->RunJobs();
g_NetClient->Poll();
std::this_thread::sleep_for(std::chrono::microseconds(200));
}
if (JS::IsPromiseObject(result) && JS::GetPromiseState(result) == JS::PromiseState::Rejected)
return false;
JS::RootedValue pageData{rq.cx, JS::IsPromiseObject(result) ? JS::GetPromiseResult(result) :
resultValue};
if (args.Has("autostart-nonvisual"))
{
PS::Loader::NonprogressiveLoad();
g_Game->ReallyStartGame();
return true;
}
std::wstring pageName;
Script::GetPropertyInt(rq, pageData, 0, pageName);
JS::RootedValue pageArgs{rq.cx};
Script::GetPropertyInt(rq, pageData, 1, &pageArgs);
Script::StructuredClone clonedpageArgs{Script::WriteStructuredClone(rq, pageArgs)};
g_GUI->OpenChildPage(pageName, std::move(clonedpageArgs));
return true;
}