mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
parent
56107e4e39
commit
262c5c037e
13 changed files with 159 additions and 96 deletions
|
|
@ -1,5 +1,6 @@
|
|||
class AutoStartClient
|
||||
{
|
||||
done = false;
|
||||
constructor(cmdLineArgs)
|
||||
{
|
||||
this.playerAssignments = {};
|
||||
|
|
@ -16,30 +17,33 @@ class AutoStartClient
|
|||
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()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const message = Engine.PollNetworkClient();
|
||||
if (!message)
|
||||
break;
|
||||
|
||||
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.
|
||||
return true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return this.done;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
class AutoStartHost
|
||||
{
|
||||
done = false;
|
||||
constructor(cmdLineArgs)
|
||||
{
|
||||
this.launched = false;
|
||||
|
|
@ -21,52 +22,57 @@ class AutoStartHost
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a simple implementation of player assignments.
|
||||
* Should not need be overloaded in mods unless you want to change that logic.
|
||||
*/
|
||||
onTick()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const message = Engine.PollNetworkClient();
|
||||
if (!message)
|
||||
break;
|
||||
|
||||
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":
|
||||
return true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.launched && Object.keys(this.playerAssignments).length == this.maxPlayers)
|
||||
{
|
||||
for (const uid in this.playerAssignments)
|
||||
if (this.playerAssignments[uid].player == -1 || this.playerAssignments[uid].status == 0)
|
||||
return false;
|
||||
this.onLaunch();
|
||||
}
|
||||
return false;
|
||||
return this.done;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -27,13 +27,11 @@ class NetMessages
|
|||
error("Unknown net message type: " + uneval(messageType));
|
||||
}
|
||||
|
||||
pollPendingMessages()
|
||||
async pollPendingMessages()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const message = Engine.PollNetworkClient();
|
||||
if (!message)
|
||||
break;
|
||||
const message = await Engine.PollNetworkClient();
|
||||
|
||||
log("Net message: " + uneval(message));
|
||||
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ class SetupWindow
|
|||
|
||||
onTick()
|
||||
{
|
||||
this.controls.netMessages.pollPendingMessages();
|
||||
updateTimers();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ var g_SetupWindow;
|
|||
function init(initData, hotloadData)
|
||||
{
|
||||
g_SetupWindow = new SetupWindow(initData, hotloadData);
|
||||
return g_IsNetworked ? g_SetupWindow.controls.netMessages.pollPendingMessages() :
|
||||
new Promise(() => {});
|
||||
}
|
||||
|
||||
function getHotloadData()
|
||||
|
|
|
|||
|
|
@ -243,13 +243,13 @@ function reportConnectionFail(reason)
|
|||
);
|
||||
}
|
||||
|
||||
function pollAndHandleNetworkClient(loadSavedGame)
|
||||
async function pollAndHandleNetworkClient(loadSavedGame)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var message = Engine.PollNetworkClient();
|
||||
if (!message)
|
||||
return false;
|
||||
const message = await Engine.PollNetworkClient();
|
||||
if (!g_IsConnecting)
|
||||
continue;
|
||||
|
||||
log(sprintf("Net message: %(message)s", { "message": uneval(message) }));
|
||||
// If we're rejoining an active game, we don't want to actually display
|
||||
|
|
|
|||
|
|
@ -417,13 +417,11 @@ function updateTutorial(notification)
|
|||
* Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
|
||||
* Saves the received object to mainlog.html.
|
||||
*/
|
||||
function handleNetMessages()
|
||||
async function handleNetMessages()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const msg = Engine.PollNetworkClient();
|
||||
if (!msg)
|
||||
return;
|
||||
const msg = await Engine.PollNetworkClient();
|
||||
|
||||
log("Net message: " + uneval(msg));
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,8 @@ function init(initData, hotloadData)
|
|||
|
||||
setTimeout(displayGamestateNotifications, 1000);
|
||||
|
||||
if (g_IsNetworked)
|
||||
return Promise.race([promise, handleNetMessages()]);
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
|
@ -630,8 +632,6 @@ function onTick()
|
|||
const tickLength = now - g_LastTickTime;
|
||||
g_LastTickTime = now;
|
||||
|
||||
handleNetMessages();
|
||||
|
||||
updateCursorAndTooltip();
|
||||
updateTimers();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
#include "lib/timer.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "maths/Size2D.h"
|
||||
#include "network/NetClient.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Errors.h"
|
||||
#include "ps/Filesystem.h"
|
||||
|
|
@ -104,7 +105,11 @@ CGUI::CGUI(ScriptContext& context)
|
|||
m_ScriptInterface->LoadGlobalScripts();
|
||||
}
|
||||
|
||||
CGUI::~CGUI() = default;
|
||||
CGUI::~CGUI()
|
||||
{
|
||||
if (g_NetClient)
|
||||
g_NetClient->Unregister(*m_ScriptInterface);
|
||||
}
|
||||
|
||||
InReaction CGUI::HandleEvent(const SDL_Event_* ev)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "ps/Profile.h"
|
||||
#include "ps/Threading.h"
|
||||
#include "scriptinterface/JSON.h"
|
||||
#include "scriptinterface/ScriptContext.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
#include "simulation2/system/TurnManager.h"
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <js/GCAPI.h>
|
||||
#include <js/Promise.h>
|
||||
#include <js/TracingAPI.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
|
@ -323,6 +325,7 @@ void CNetClient::Poll()
|
|||
|
||||
CheckServerConnection();
|
||||
m_Session->ProcessPolledMessages();
|
||||
FetchMessage();
|
||||
}
|
||||
|
||||
void CNetClient::CheckServerConnection()
|
||||
|
|
@ -357,15 +360,35 @@ void CNetClient::CheckServerConnection()
|
|||
}
|
||||
}
|
||||
|
||||
JS::Value CNetClient::GuiPoll(const ScriptRequest& rq)
|
||||
JSObject* CNetClient::GetNextGUIMessage(const ScriptInterface& guiInterface)
|
||||
{
|
||||
if (m_GuiMessageQueue.empty())
|
||||
return JS::UndefinedValue();
|
||||
const ScriptRequest rq{guiInterface};
|
||||
m_GuiMessagePoll.emplace(GuiPollData{guiInterface, {rq.cx, JS::NewPromiseObject(rq.cx, nullptr)}});
|
||||
|
||||
JS::RootedValue ret{rq.cx};
|
||||
Script::ReadStructuredClone(rq, m_GuiMessageQueue.front(), &ret);
|
||||
FetchMessage();
|
||||
return m_GuiMessagePoll.value().promise;
|
||||
}
|
||||
|
||||
void CNetClient::Unregister(const ScriptInterface& guiInterface)
|
||||
{
|
||||
if (m_GuiMessagePoll.has_value() && &m_GuiMessagePoll.value().interface == &guiInterface)
|
||||
m_GuiMessagePoll.reset();
|
||||
}
|
||||
|
||||
void CNetClient::FetchMessage()
|
||||
{
|
||||
if (m_GuiMessageQueue.empty() || !m_GuiMessagePoll.has_value() ||
|
||||
JS::GetPromiseState(m_GuiMessagePoll.value().promise) != JS::PromiseState::Pending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const ScriptRequest rq{m_GuiMessagePoll.value().interface};
|
||||
JS::RootedValue message{rq.cx};
|
||||
Script::ReadStructuredClone(rq, std::move(m_GuiMessageQueue.front()), &message);
|
||||
m_GuiMessageQueue.pop_front();
|
||||
return ret;
|
||||
|
||||
JS::ResolvePromise(rq.cx, m_GuiMessagePoll.value().promise, message);
|
||||
}
|
||||
|
||||
std::string CNetClient::TestReadGuiMessages()
|
||||
|
|
@ -375,9 +398,13 @@ std::string CNetClient::TestReadGuiMessages()
|
|||
std::string r;
|
||||
while (true)
|
||||
{
|
||||
JS::RootedValue msg{rq.cx, GuiPoll(rq)};
|
||||
if (msg.isUndefined())
|
||||
JS::RootedObject promise{rq.cx, GetNextGUIMessage(GetScriptInterface())};
|
||||
g_ScriptContext->RunJobs();
|
||||
|
||||
if (JS::GetPromiseState(promise) == JS::PromiseState::Pending)
|
||||
break;
|
||||
|
||||
JS::RootedValue msg{rq.cx, JS::GetPromiseResult(promise)};
|
||||
r += Script::ToString(rq, &msg) + "\n";
|
||||
}
|
||||
return r;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -144,19 +144,25 @@ public:
|
|||
|
||||
/**
|
||||
* Retrieves the next queued GUI message, and removes it from the queue.
|
||||
* The returned value is in the GetScriptInterface() JS context.
|
||||
* The returned value is in the JS context of the provided
|
||||
* @c ScriptInterface.
|
||||
*
|
||||
* This is the only mechanism for the networking code to send messages to
|
||||
* the GUI - it is pull-based (instead of push) so the engine code does not
|
||||
* need to know anything about the code structure of the GUI scripts.
|
||||
* the GUI.
|
||||
*
|
||||
* The structure of the messages is <code>{ "type": "...", ... }</code>.
|
||||
* The exact types and associated data are not specified anywhere - the
|
||||
* implementation and GUI scripts must make the same assumptions.
|
||||
*
|
||||
* @return next message, or the value 'undefined' if the queue is empty
|
||||
* @return a promise resolving to the next message.
|
||||
*/
|
||||
JS::Value GuiPoll(const ScriptRequest& rq);
|
||||
JSObject* GetNextGUIMessage(const ScriptInterface& guiInterface);
|
||||
|
||||
/**
|
||||
* Has to be called bevore the @c ScriptInterface gets destroied so that
|
||||
* no future messages are sent to it.
|
||||
*/
|
||||
void Unregister(const ScriptInterface& guiInterface);
|
||||
|
||||
/**
|
||||
* Add a message to the queue, to be read by GuiPoll.
|
||||
|
|
@ -305,6 +311,8 @@ private:
|
|||
*/
|
||||
void PostPlayerAssignmentsToScript();
|
||||
|
||||
void FetchMessage();
|
||||
|
||||
CGame *m_Game;
|
||||
CStrW m_UserName;
|
||||
|
||||
|
|
@ -346,6 +354,18 @@ private:
|
|||
/// Queue of messages for GuiPoll
|
||||
std::deque<Script::StructuredClone> m_GuiMessageQueue;
|
||||
|
||||
struct GuiPollData
|
||||
{
|
||||
const ScriptInterface& interface;
|
||||
/**
|
||||
* In the context of interface.
|
||||
* When the promise is pending @see Poll should fill it with a message.
|
||||
* When there it's fulfilled JavaScript code can take it.
|
||||
*/
|
||||
JS::PersistentRootedObject promise;
|
||||
};
|
||||
std::optional<GuiPollData> m_GuiMessagePoll;
|
||||
|
||||
/// Serialized game state received when joining an in-progress game
|
||||
std::string m_JoinSyncBuffer;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -196,9 +196,12 @@ CStr GetPlayerGUID()
|
|||
return g_NetClient->GetGUID();
|
||||
}
|
||||
|
||||
JS::Value PollNetworkClient(const ScriptRequest& rq)
|
||||
JS::Value PollNetworkClient(const ScriptInterface& guiInterface)
|
||||
{
|
||||
return g_NetClient ? g_NetClient->GuiPoll(rq) : JS::UndefinedValue();
|
||||
if (!g_NetClient)
|
||||
throw std::logic_error{"Network client not present"};
|
||||
|
||||
return JS::ObjectValue(*g_NetClient->GetNextGUIMessage(guiInterface));
|
||||
}
|
||||
|
||||
void SendGameSetupMessage(const ScriptInterface& scriptInterface, JS::HandleValue attribs1)
|
||||
|
|
|
|||
|
|
@ -855,6 +855,7 @@ bool Autostart(const CmdLineArgs& args)
|
|||
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));
|
||||
|
|
|
|||
Loading…
Reference in a new issue