Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -1287,6 +1287,7 @@ initSettingsTabButtons(); initHotkeys(); initSPTips(); + initNetMessages(); loadPersistMatchSettings(); updateGameAttributes(); @@ -2083,8 +2084,6 @@ initGUIObjects(); ++g_LoadingState; } - else if (g_LoadingState == 2) - handleNetMessages(); updateTimers(); @@ -2098,24 +2097,17 @@ /** * Handles all pending messages sent by the net client. */ -function handleNetMessages() +function initNetMessages() { - if (!g_IsNetworked) - return; - - while (true) - { - let message = Engine.PollNetworkClient(); - if (!message) - break; + Engine.GetGUIObjectByName("setupWindow").onNetworkMessage = message => { log("Net message: " + uneval(message)); if (g_NetMessageTypes[message.type]) g_NetMessageTypes[message.type](message); else - error("Unrecognised net message type " + message.type); - } + error("Unrecognized net message type " + message.type); + }; } /** Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -136,23 +136,12 @@ g_IsConnecting = true; g_IsRejoining = false; Engine.GetGUIObjectByName("connectionStatus").caption = translate("Connecting to server..."); + initNetMessages(); } -function onTick() +function initNetMessages() { - if (!g_IsConnecting) - return; - - pollAndHandleNetworkClient(); -} - -function pollAndHandleNetworkClient() -{ - while (true) - { - var message = Engine.PollNetworkClient(); - if (!message) - break; + Engine.GetGUIObjectByName("multiplayerPages").onNetworkMessage = message => { log(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) })); Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml @@ -10,10 +10,6 @@ - - onTick(); - - Multiplayer Index: binaries/data/mods/public/gui/session/messages.js =================================================================== --- binaries/data/mods/public/gui/session/messages.js +++ binaries/data/mods/public/gui/session/messages.js @@ -373,13 +373,9 @@ * Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer. * Saves the received object to mainlog.html. */ -function handleNetMessages() +function initNetMessages() { - while (true) - { - let msg = Engine.PollNetworkClient(); - if (!msg) - return; + Engine.GetGUIObjectByName("netStatus").onNetworkMessage = msg => { log("Net message: " + uneval(msg)); @@ -387,7 +383,7 @@ g_NetMessageTypes[msg.type](msg); else error("Unrecognised net message type '" + msg.type + "'"); - } + }; } function handleNetStatusMessage(message) Index: binaries/data/mods/public/gui/session/session.js =================================================================== --- binaries/data/mods/public/gui/session/session.js +++ binaries/data/mods/public/gui/session/session.js @@ -293,6 +293,7 @@ initBatchTrain(); initSelectionPanels(); + initNetMessages(); LoadModificationTemplates(); updatePlayerData(); initializeMusic(); // before changing the perspective @@ -577,8 +578,6 @@ let tickLength = now - g_LastTickTime; g_LastTickTime = now; - handleNetMessages(); - updateCursorAndTooltip(); if (g_Selection.dirty) Index: source/gui/CGUI.cpp =================================================================== --- source/gui/CGUI.cpp +++ source/gui/CGUI.cpp @@ -268,6 +268,7 @@ void CGUI::SendEventToAll(const CStr& EventName, const JS::HandleValueArray& paramData) { + // TODO: Keep a map of objects that subscribe to this event const CStr EventNameLower = EventName.LowerCase(); m_BaseObject.RecurseObject(nullptr, &IGUIObject::ScriptEvent, EventNameLower, paramData); } Index: source/gui/GUIManager.cpp =================================================================== --- source/gui/GUIManager.cpp +++ source/gui/GUIManager.cpp @@ -340,7 +340,6 @@ for (const SGUIPage& p : pageStack) p.gui->SendEventToAll(eventName); - } void CGUIManager::SendEventToAll(const CStr& eventName, JS::HandleValueArray paramData) const @@ -349,7 +348,23 @@ PageStackType pageStack = m_PageStack; for (const SGUIPage& p : pageStack) - p.gui->SendEventToAll(eventName, paramData); + { + JSContext* cx = p.gui->GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + JS::AutoValueVector paramDataClone(cx); + for (size_t i = 0; i < paramData.length(); ++i) + { + JS::RootedValue paramClone(cx); + if (JS_StructuredClone(cx, paramData[i], ¶mClone, nullptr, nullptr)) + paramDataClone.append(paramClone); + else + { + LOGERROR("CGUIManager::SendEventToAll could not clone paramData"); + return; + } + } + p.gui->SendEventToAll(eventName, paramDataClone); + } } void CGUIManager::TickObjects() Index: source/main.cpp =================================================================== --- source/main.cpp +++ source/main.cpp @@ -380,7 +380,10 @@ } if (g_NetClient) + { g_NetClient->Poll(); + g_NetClient->FlushGUIMessages(*g_GUI); + } ogl_WarnIfError(); Index: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -18,13 +18,13 @@ #ifndef NETCLIENT_H #define NETCLIENT_H +#include "gui/GUIManager.h" #include "network/fsm.h" #include "network/NetFileTransfer.h" #include "network/NetHost.h" -#include "scriptinterface/ScriptVal.h" -#include "scriptinterface/ScriptInterface.h" - #include "ps/CStr.h" +#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/ScriptVal.h" #include @@ -135,22 +135,6 @@ void Flush(); /** - * Retrieves the next queued GUI message, and removes it from the queue. - * The returned value is in the GetScriptInterface() JS context. - * - * 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 structure of the messages is { "type": "...", ... }. - * 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 - */ - void GuiPoll(JS::MutableHandleValue); - - /** * Add a message to the queue, to be read by GuiPoll. * The script value must be in the GetScriptInterface() JS context. */ @@ -166,10 +150,9 @@ } /** - * Return a concatenation of all messages in the GUI queue, - * for test cases to easily verify the queue contents. + * Sends all GUI messages in the queue to the GUI objects subscribed to that event. */ - std::string TestReadGuiMessages(); + void FlushGUIMessages(CGUIManager& gui); /** * Get the script interface associated with this network client, @@ -297,7 +280,7 @@ /// Globally unique identifier to distinguish users beyond the lifetime of a single network session CStr m_GUID; - /// Queue of messages for GuiPoll + /// Queue of messages std::deque> m_GuiMessageQueue; /// Serialized game state received when joining an in-progress game Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -19,14 +19,13 @@ #include "NetClient.h" -#include "NetClientTurnManager.h" -#include "NetMessage.h" -#include "NetSession.h" - #include "lib/byte_order.h" #include "lib/external_libraries/enet.h" #include "lib/sysdep/sysdep.h" #include "lobby/IXmppClient.h" +#include "network/NetClientTurnManager.h" +#include "network/NetMessage.h" +#include "network/NetSession.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/Compress.h" @@ -36,7 +35,7 @@ #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" -CNetClient *g_NetClient = NULL; +CNetClient *g_NetClient = nullptr; /** * Async task for receiving the initial game state when rejoining an @@ -199,6 +198,22 @@ m_Session->Poll(); } +void CNetClient::FlushGUIMessages(CGUIManager& gui) +{ + JSContext* cx = GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + + while (!m_GuiMessageQueue.empty()) + { + JS::RootedValue message(cx, m_GuiMessageQueue.front()); + m_GuiMessageQueue.pop_front(); + + JS::AutoValueArray<1> paramData(cx); + paramData[0].set(message); + gui.SendEventToAll("NetworkMessage", paramData); + } +} + void CNetClient::CheckServerConnection() { // Trigger local warnings if the connection to the server is bad. @@ -237,35 +252,6 @@ m_Session->Flush(); } -void CNetClient::GuiPoll(JS::MutableHandleValue ret) -{ - if (m_GuiMessageQueue.empty()) - { - ret.setUndefined(); - return; - } - - ret.set(m_GuiMessageQueue.front()); - m_GuiMessageQueue.pop_front(); -} - -std::string CNetClient::TestReadGuiMessages() -{ - JSContext* cx = GetScriptInterface().GetContext(); - JSAutoRequest rq(cx); - - std::string r; - JS::RootedValue msg(cx); - while (true) - { - GuiPoll(&msg); - if (msg.isUndefined()) - break; - r += GetScriptInterface().ToString(&msg) + "\n"; - } - return r; -} - const ScriptInterface& CNetClient::GetScriptInterface() { return m_Game->GetSimulation2()->GetScriptInterface(); Index: source/network/scripting/JSInterface_Network.h =================================================================== --- source/network/scripting/JSInterface_Network.h +++ source/network/scripting/JSInterface_Network.h @@ -33,7 +33,6 @@ void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID); JS::Value FindStunEndpoint(ScriptInterface::CxPrivate* pCxPrivate, int port); void DisconnectNetworkGame(ScriptInterface::CxPrivate* pCxPrivate); - JS::Value PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate); CStr GetPlayerGUID(ScriptInterface::CxPrivate* pCxPrivate); void KickPlayer(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, bool ban); void AssignNetworkPlayer(ScriptInterface::CxPrivate* pCxPrivate, int playerID, const CStr& guid); Index: source/network/scripting/JSInterface_Network.cpp =================================================================== --- source/network/scripting/JSInterface_Network.cpp +++ source/network/scripting/JSInterface_Network.cpp @@ -149,19 +149,6 @@ return g_NetClient->GetGUID(); } -JS::Value JSI_Network::PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate) -{ - if (!g_NetClient) - return JS::UndefinedValue(); - - // Convert from net client context to GUI script context - JSContext* cxNet = g_NetClient->GetScriptInterface().GetContext(); - JSAutoRequest rqNet(cxNet); - JS::RootedValue pollNet(cxNet); - g_NetClient->GuiPoll(&pollNet); - return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), pollNet); -} - void JSI_Network::SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs1) { ENSURE(g_NetClient); @@ -233,7 +220,6 @@ scriptInterface.RegisterFunction("StartNetworkJoin"); scriptInterface.RegisterFunction("DisconnectNetworkGame"); scriptInterface.RegisterFunction("GetPlayerGUID"); - scriptInterface.RegisterFunction("PollNetworkClient"); scriptInterface.RegisterFunction("SetNetworkGameAttributes"); scriptInterface.RegisterFunction("AssignNetworkPlayer"); scriptInterface.RegisterFunction("KickPlayer"); Index: source/network/tests/test_Net.h =================================================================== --- source/network/tests/test_Net.h +++ source/network/tests/test_Net.h @@ -170,7 +170,6 @@ clients.push_back(&client3); connect(server, clients); - debug_printf("%s", client1.TestReadGuiMessages().c_str()); server.StartGame(); SDL_Delay(100); @@ -254,7 +253,6 @@ clients.push_back(&client3); connect(server, clients); - debug_printf("%s", client1.TestReadGuiMessages().c_str()); server.StartGame(); SDL_Delay(100);