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
5 changed files with 140 additions and 206 deletions
@@ -1,36 +1,22 @@
class AutoStart async function autoStart(cmdLineArgs)
{ {
constructor(cmdLineArgs) const playerAssignments = {
{ "local": {
this.playerAssignments = { "player": +(cmdLineArgs?.['autostart-player'] ?? 1),
"local": { "name": "anonymous",
"player": +(cmdLineArgs?.['autostart-player'] ?? 1), },
"name": "anonymous", };
}, const settings = new GameSettings().init();
};
this.settings = new GameSettings().init();
// Enable cheats in SP // Enable cheats in SP
this.settings.cheats.setEnabled(true); settings.cheats.setEnabled(true);
parseCmdLineArgs(this.settings, cmdLineArgs); parseCmdLineArgs(settings, cmdLineArgs);
this.settings.launchGame(this.playerAssignments, !('autostart-disable-replay' in cmdLineArgs)); settings.launchGame(playerAssignments, !('autostart-disable-replay' in cmdLineArgs));
this.onLaunch();
}
onTick() return ["page_loading.xml", {
{ "attribs": settings.finalizedAttributes,
} "playerAssignments": playerAssignments
}];
/**
* 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
});
}
} }
@@ -1,61 +1,36 @@
class AutoStartClient async function autoStartClient(cmdLineArgs)
{ {
done = false; try
constructor(cmdLineArgs)
{ {
this.playerAssignments = {}; const playerName = cmdLineArgs['autostart-playername'] || "anonymous";
const ip = cmdLineArgs['autostart-client'] ?? "127.0.0.1";
try const port = +(cmdLineArgs['autostart-port'] ?? 5073);
{ Engine.StartNetworkJoin(playerName, ip, port, !('autostart-disable-replay' in cmdLineArgs));
const playerName = cmdLineArgs['autostart-playername'] || "anonymous"; }
const ip = cmdLineArgs['autostart-client'] ?? "127.0.0.1"; catch(e)
const port = +(cmdLineArgs['autostart-port'] ?? 5073); {
Engine.StartNetworkJoin(playerName, ip, port, !('autostart-disable-replay' in cmdLineArgs)); const message = sprintf(translate("Cannot join game: %(message)s."), { "message": e.message });
} messageBox(400, 200, message, translate("Error"));
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:
}
}
})();
} }
onTick() let playerAssignments = {};
while (true)
{ {
return this.done; const message = await Engine.PollNetworkClient();
}
/** switch (message.type)
* In the visual autostart path, we need to show the loading screen. {
* Overload this as appropriate, the default implementation works for the public mod. case "players":
*/ playerAssignments = message.newAssignments;
onLaunch(message) Engine.SendNetworkReady(2);
{ break;
Engine.SwitchGuiPage("page_loading.xml", { case "start":
"attribs": message.initAttributes, return ["page_loading.xml", {
"isRejoining": true, "attribs": message.initAttributes,
"playerAssignments": this.playerAssignments "isRejoining": true,
}); "playerAssignments": playerAssignments
}];
default:
}
} }
} }
@@ -1,97 +1,74 @@
class AutoStartHost async function autoStartHost(cmdLineArgs)
{ {
done = false; const maxPlayers = +(cmdLineArgs['autostart-host-players'] ?? 2);
constructor(cmdLineArgs)
try
{ {
this.launched = false; const playerName = cmdLineArgs['autostart-playername'] || "anonymous";
this.playerAssignments = {}; const port = +(cmdLineArgs['autostart-port'] ?? 5073);
this.maxPlayers = +(cmdLineArgs['autostart-host-players'] ?? 2); // Password not implemented for autostart.
this.cmdLineArgs = cmdLineArgs; Engine.StartNetworkHost(playerName, port, "", false, !('autostart-disable-replay' in 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();
}
}
}
})();
} }
catch(e)
onTick()
{ {
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. * Handles a simple implementation of player assignments.
* Overload this as appropriate. * 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(); const assignementArray = Object.values(playerAssignments);
if (assignementArray.length === maxPlayers &&
parseCmdLineArgs(this.settings, this.cmdLineArgs); assignementArray.every(assignement =>
assignement.player !== -1 || assignement.status !== 0))
this.settings.playerCount.setNb(Object.keys(this.playerAssignments).length); {
this.settings.launchGame(this.playerAssignments, this.storeReplay); break;
Engine.SwitchGuiPage("page_loading.xml", { }
"attribs": this.settings.finalizedAttributes,
"playerAssignments": this.playerAssignments
});
} }
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
}];
} }
@@ -7,8 +7,6 @@
Engine.HasXmppClient = () => false; Engine.HasXmppClient = () => false;
Engine.SetRankedGame = () => {}; Engine.SetRankedGame = () => {};
Engine.TextureExists = () => false; Engine.TextureExists = () => false;
Engine.OpenChildPage = () => {};
Engine.SwitchGuiPage = () => {};
var translateObjectKeys = () => {}; var translateObjectKeys = () => {};
var translate = x => x; var translate = x => x;
@@ -20,11 +18,9 @@ Engine.LoadScript("globalscripts/");
// TODO: clean this up and show errors better in the non-visual path. // TODO: clean this up and show errors better in the non-visual path.
Engine.LoadScript("gui/common/functions_msgbox.js"); Engine.LoadScript("gui/common/functions_msgbox.js");
var autostartInstance;
function autostartClient(cmdLineArgs) function autostartClient(cmdLineArgs)
{ {
autostartInstance = new AutoStartClient(cmdLineArgs); return autoStartClient(cmdLineArgs);
} }
/** /**
@@ -44,15 +40,6 @@ function autostartHost(cmdLineArgs, networked = false)
Engine.LoadScript("gamesettings/attributes/"); Engine.LoadScript("gamesettings/attributes/");
if (networked) if (networked)
autostartInstance = new AutoStartHost(cmdLineArgs); return autoStartHost(cmdLineArgs);
else return autoStart(cmdLineArgs);
autostartInstance = new AutoStart(cmdLineArgs);
}
/**
* @returns false if the loop should carry on.
*/
function onTick()
{
return autostartInstance.onTick();
} }
+32 -23
View File
@@ -109,6 +109,7 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <fstream> #include <fstream>
#include <js/CallArgs.h> #include <js/CallArgs.h>
#include <js/Promise.h>
#include <js/RootingAPI.h> #include <js/RootingAPI.h>
#include <js/TypeDecls.h> #include <js/TypeDecls.h>
#include <js/Value.h> #include <js/Value.h>
@@ -841,39 +842,47 @@ bool Autostart(const CmdLineArgs& args)
JS::RootedValue cmdLineArgs(rq.cx); JS::RootedValue cmdLineArgs(rq.cx);
Script::ToJSVal(rq, &cmdLineArgs, args); 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"))
// Pass the default port if undefined, to avoid duplicating it in JS. Script::SetProperty(rq, cmdLineArgs, "autostart-port", PS_DEFAULT_PORT);
if (!Script::HasProperty(rq, cmdLineArgs, "autostart-port"))
Script::SetProperty(rq, cmdLineArgs, "autostart-port", PS_DEFAULT_PORT);
JS::RootedValue global(rq.cx, rq.globalValue()); JS::RootedValue resultValue{rq.cx};
if (!ScriptFunction::CallVoid(rq, global, args.Has("autostart-client") ? "autostartClient" : "autostartHost", cmdLineArgs, true)) JS::RootedValue global(rq.cx, rq.globalValue());
return false; if (!ScriptFunction::Call(rq, global,
args.Has("autostart-client") ? "autostartClient" : "autostartHost", &resultValue,
bool shouldQuit = false; cmdLineArgs, args.Has("autostart-host")) && !resultValue.isObject())
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 global(rq.cx, rq.globalValue()); return false;
if (!ScriptFunction::CallVoid(rq, global, "autostartHost", cmdLineArgs, 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")) if (args.Has("autostart-nonvisual"))
{ {
PS::Loader::NonprogressiveLoad(); PS::Loader::NonprogressiveLoad();
g_Game->ReallyStartGame(); 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; return true;
} }