1
0
forked from mirrors/0ad

Use promises to fetch net messages

Refs: #5585
This commit is contained in:
phosit
2025-05-07 11:24:39 +02:00
parent 56107e4e39
commit 262c5c037e
13 changed files with 159 additions and 96 deletions
+7 -2
View File
@@ -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)
{
+35 -8
View File
@@ -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;
+26 -6
View File
@@ -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)
+1
View File
@@ -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));