phosit
3a5ad160f7
The context doesn't have to be converted to `void*` and back. Differential Revision: https://code.wildfiregames.com/D5253 This was SVN commit r28074.
353 lines
10 KiB
C++
353 lines
10 KiB
C++
/* Copyright (C) 2024 Wildfire Games.
|
|
* This file is part of 0 A.D.
|
|
*
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef NETCLIENT_H
|
|
#define NETCLIENT_H
|
|
|
|
#include "network/FSM.h"
|
|
#include "network/NetFileTransfer.h"
|
|
#include "network/NetHost.h"
|
|
#include "scriptinterface/Object.h"
|
|
|
|
#include "ps/CStr.h"
|
|
|
|
#include <ctime>
|
|
#include <deque>
|
|
#include <thread>
|
|
|
|
class CGame;
|
|
class CNetClientSession;
|
|
class CNetClientTurnManager;
|
|
class ScriptInterface;
|
|
|
|
typedef struct _ENetHost ENetHost;
|
|
|
|
// NetClient session FSM states
|
|
enum
|
|
{
|
|
NCS_UNCONNECTED,
|
|
NCS_CONNECT,
|
|
NCS_HANDSHAKE,
|
|
NCS_AUTHENTICATE,
|
|
NCS_PREGAME,
|
|
NCS_LOADING,
|
|
NCS_JOIN_SYNCING,
|
|
NCS_INGAME
|
|
};
|
|
|
|
/**
|
|
* Network client.
|
|
* This code is run by every player (including the host, if they are not
|
|
* a dedicated server).
|
|
* It provides an interface between the GUI, the network (via CNetClientSession),
|
|
* and the game (via CGame and CNetClientTurnManager).
|
|
*/
|
|
class CNetClient : public CFsm<CNetClient>
|
|
{
|
|
NONCOPYABLE(CNetClient);
|
|
|
|
public:
|
|
/**
|
|
* Construct a client associated with the given game object.
|
|
* The game must exist for the lifetime of this object.
|
|
*/
|
|
CNetClient(CGame* game);
|
|
|
|
virtual ~CNetClient();
|
|
|
|
/**
|
|
* We assume that adding a tracing function that's only called
|
|
* during GC is better for performance than using a
|
|
* PersistentRooted<T> where each value needs to be added to
|
|
* the root set.
|
|
*/
|
|
static void Trace(JSTracer *trc, void *data)
|
|
{
|
|
reinterpret_cast<CNetClient*>(data)->TraceMember(trc);
|
|
}
|
|
|
|
void TraceMember(JSTracer *trc);
|
|
|
|
/**
|
|
* Set the user's name that will be displayed to all players.
|
|
* This must not be called after the connection setup.
|
|
*/
|
|
void SetUserName(const CStrW& username);
|
|
|
|
/**
|
|
* Store the JID of the host.
|
|
* This is needed for the secure lobby authentication.
|
|
*/
|
|
void SetHostJID(const CStr& jid);
|
|
|
|
void SetControllerSecret(const std::string& secret);
|
|
|
|
bool IsController() const { return m_IsController; }
|
|
|
|
/**
|
|
* Set the game password.
|
|
* Must be called after SetUserName, as that is used to hash further.
|
|
*/
|
|
void SetGamePassword(const CStr& hashedPassword);
|
|
|
|
/**
|
|
* Returns the GUID of the local client.
|
|
* Used for distinguishing observers.
|
|
*/
|
|
CStr GetGUID() const { return m_GUID; }
|
|
|
|
/**
|
|
* Set connection data to the remote networked server.
|
|
* @param address IP address or host name to connect to
|
|
*/
|
|
void SetupServerData(CStr address, u16 port, bool stun);
|
|
|
|
/**
|
|
* Set up a connection to the remote networked server.
|
|
* Must call SetupServerData first.
|
|
* @return true on success, false on connection failure
|
|
*/
|
|
bool SetupConnection(ENetHost* enetClient);
|
|
|
|
/**
|
|
* Request connection information over the lobby.
|
|
*/
|
|
void SetupConnectionViaLobby();
|
|
|
|
/**
|
|
* Connect to the remote networked server using lobby.
|
|
* Push netstatus messages on failure.
|
|
* @param localNetwork - if true, assume we are trying to connect on the local network.
|
|
* @return true on success, false on connection failure
|
|
*/
|
|
bool TryToConnect(const CStr& hostJID, bool localNetwork);
|
|
|
|
/**
|
|
* Destroy the connection to the server.
|
|
* This client probably cannot be used again.
|
|
*/
|
|
void DestroyConnection();
|
|
|
|
/**
|
|
* Poll the connection for messages from the server and process them, and send
|
|
* any queued messages.
|
|
* This must be called frequently (i.e. once per frame).
|
|
*/
|
|
void Poll();
|
|
|
|
/**
|
|
* Locally triggers a GUI message if the connection to the server is being lost or has bad latency.
|
|
*/
|
|
void CheckServerConnection();
|
|
|
|
/**
|
|
* 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 <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
|
|
*/
|
|
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.
|
|
*/
|
|
template<typename... Args>
|
|
void PushGuiMessage(Args const&... args)
|
|
{
|
|
ScriptRequest rq(GetScriptInterface());
|
|
|
|
JS::RootedValue message(rq.cx);
|
|
Script::CreateObject(rq, &message, args...);
|
|
m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
|
|
}
|
|
|
|
/**
|
|
* Return a concatenation of all messages in the GUI queue,
|
|
* for test cases to easily verify the queue contents.
|
|
*/
|
|
std::string TestReadGuiMessages();
|
|
|
|
/**
|
|
* Get the script interface associated with this network client,
|
|
* which is equivalent to the one used by the CGame in the constructor.
|
|
*/
|
|
const ScriptInterface& GetScriptInterface();
|
|
|
|
/**
|
|
* Send a message to the server.
|
|
* @param message message to send
|
|
* @return true on success
|
|
*/
|
|
bool SendMessage(const CNetMessage* message);
|
|
|
|
/**
|
|
* Call when the network connection has been successfully initiated.
|
|
*/
|
|
void HandleConnect();
|
|
|
|
/**
|
|
* Call when the network connection has been lost.
|
|
*/
|
|
void HandleDisconnect(u32 reason);
|
|
|
|
/**
|
|
* Call when a message has been received from the network.
|
|
*/
|
|
bool HandleMessage(CNetMessage* message);
|
|
|
|
/**
|
|
* Call when the game has started and all data files have been loaded,
|
|
* to signal to the server that we are ready to begin the game.
|
|
*/
|
|
void LoadFinished();
|
|
|
|
void SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface);
|
|
|
|
void SendAssignPlayerMessage(const int playerID, const CStr& guid);
|
|
|
|
void SendChatMessage(const std::wstring& text);
|
|
|
|
void SendReadyMessage(const int status);
|
|
|
|
void SendClearAllReadyMessage();
|
|
|
|
void SendStartGameMessage(const CStr& initAttribs);
|
|
|
|
/**
|
|
* Call when the client has rejoined a running match and finished
|
|
* the loading screen.
|
|
*/
|
|
void SendRejoinedMessage();
|
|
|
|
/**
|
|
* Call to kick/ban a client
|
|
*/
|
|
void SendKickPlayerMessage(const CStrW& playerName, bool ban);
|
|
|
|
/**
|
|
* Call when the client has paused or unpaused the game.
|
|
*/
|
|
void SendPausedMessage(bool pause);
|
|
|
|
/**
|
|
* @return Whether the NetClient is shutting down.
|
|
*/
|
|
bool ShouldShutdown() const;
|
|
|
|
/**
|
|
* Called when fetching connection data from the host failed, to inform JS code.
|
|
*/
|
|
void HandleGetServerDataFailed(const CStr& error);
|
|
private:
|
|
|
|
void SendAuthenticateMessage();
|
|
|
|
// Net message / FSM transition handlers
|
|
static bool OnConnect(CNetClient* client, CFsmEvent* event);
|
|
static bool OnHandshake(CNetClient* client, CFsmEvent* event);
|
|
static bool OnHandshakeResponse(CNetClient* client, CFsmEvent* event);
|
|
static bool OnAuthenticateRequest(CNetClient* client, CFsmEvent* event);
|
|
static bool OnAuthenticate(CNetClient* client, CFsmEvent* event);
|
|
static bool OnChat(CNetClient* client, CFsmEvent* event);
|
|
static bool OnReady(CNetClient* client, CFsmEvent* event);
|
|
static bool OnGameSetup(CNetClient* client, CFsmEvent* event);
|
|
static bool OnPlayerAssignment(CNetClient* client, CFsmEvent* event);
|
|
static bool OnInGame(CNetClient* client, CFsmEvent* event);
|
|
static bool OnGameStart(CNetClient* client, CFsmEvent* event);
|
|
static bool OnJoinSyncStart(CNetClient* client, CFsmEvent* event);
|
|
static bool OnJoinSyncEndCommandBatch(CNetClient* client, CFsmEvent* event);
|
|
static bool OnRejoined(CNetClient* client, CFsmEvent* event);
|
|
static bool OnKicked(CNetClient* client, CFsmEvent* event);
|
|
static bool OnClientTimeout(CNetClient* client, CFsmEvent* event);
|
|
static bool OnClientPerformance(CNetClient* client, CFsmEvent* event);
|
|
static bool OnClientsLoading(CNetClient* client, CFsmEvent* event);
|
|
static bool OnClientPaused(CNetClient* client, CFsmEvent* event);
|
|
static bool OnLoadedGame(CNetClient* client, CFsmEvent* event);
|
|
|
|
/**
|
|
* Take ownership of a session object, and use it for all network communication.
|
|
*/
|
|
void SetAndOwnSession(CNetClientSession* session);
|
|
|
|
/**
|
|
* Push a message onto the GUI queue listing the current player assignments.
|
|
*/
|
|
void PostPlayerAssignmentsToScript();
|
|
|
|
CGame *m_Game;
|
|
CStrW m_UserName;
|
|
|
|
CStr m_HostJID;
|
|
CStr m_ServerAddress;
|
|
u16 m_ServerPort;
|
|
bool m_UseSTUN;
|
|
|
|
/**
|
|
* Password to join the game.
|
|
*/
|
|
CStr m_Password;
|
|
|
|
/// The 'secret' used to identify the controller of the game.
|
|
std::string m_ControllerSecret;
|
|
|
|
/// Note that this is just a "gui hint" with no actual impact on being controller.
|
|
bool m_IsController = false;
|
|
|
|
/// Current network session (or NULL if not connected)
|
|
CNetClientSession* m_Session;
|
|
|
|
std::thread m_PollingThread;
|
|
|
|
/// Turn manager associated with the current game (or NULL if we haven't started the game yet)
|
|
CNetClientTurnManager* m_ClientTurnManager;
|
|
|
|
/// Unique-per-game identifier of this client, used to identify the sender of simulation commands
|
|
u32 m_HostID;
|
|
|
|
/// True if the player is currently rejoining or has already rejoined the game.
|
|
bool m_Rejoin;
|
|
|
|
/// Latest copy of player assignments heard from the server
|
|
PlayerAssignmentMap m_PlayerAssignments;
|
|
|
|
/// Globally unique identifier to distinguish users beyond the lifetime of a single network session
|
|
CStr m_GUID;
|
|
|
|
/// Queue of messages for GuiPoll
|
|
std::deque<JS::Heap<JS::Value>> m_GuiMessageQueue;
|
|
|
|
/// Serialized game state received when joining an in-progress game
|
|
std::string m_JoinSyncBuffer;
|
|
|
|
/// Time when the server was last checked for timeouts and bad latency
|
|
std::time_t m_LastConnectionCheck;
|
|
};
|
|
|
|
/// Global network client for the standard game
|
|
extern CNetClient *g_NetClient;
|
|
|
|
#endif // NETCLIENT_H
|