mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
LiveAPI: initial WebSocket implementation
Working WebSocket implementation (foundation for game LiveAPI).
This commit is contained in:
parent
aff556732e
commit
2d084db3a3
@ -35,6 +35,7 @@
|
|||||||
#include "engine/cmodel_bsp.h"
|
#include "engine/cmodel_bsp.h"
|
||||||
#ifndef CLIENT_DLL
|
#ifndef CLIENT_DLL
|
||||||
#include "engine/server/server.h"
|
#include "engine/server/server.h"
|
||||||
|
#include "rtech/liveapi/liveapi.h"
|
||||||
#endif // !CLIENT_DLL
|
#endif // !CLIENT_DLL
|
||||||
#include "rtech/stryder/stryder.h"
|
#include "rtech/stryder/stryder.h"
|
||||||
#include "rtech/playlists/playlists.h"
|
#include "rtech/playlists/playlists.h"
|
||||||
@ -156,6 +157,10 @@ void CHostState::FrameUpdate(CHostState* pHostState, double flCurrentTime, float
|
|||||||
RCONClient()->RunFrame();
|
RCONClient()->RunFrame();
|
||||||
#endif // !DEDICATED
|
#endif // !DEDICATED
|
||||||
|
|
||||||
|
#ifndef CLIENT_DLL
|
||||||
|
LiveAPISystem()->RunFrame(); // TODO[ AMOS ]: move to server frame !!!
|
||||||
|
#endif // !CLIENT_DLL
|
||||||
|
|
||||||
// Disable "warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable"
|
// Disable "warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable"
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable : 4611)
|
#pragma warning(disable : 4611)
|
||||||
@ -305,6 +310,10 @@ void CHostState::Setup(void)
|
|||||||
RCONClient()->Init();
|
RCONClient()->Init();
|
||||||
#endif // !DEDICATED
|
#endif // !DEDICATED
|
||||||
|
|
||||||
|
#ifndef CLIENT_DLL
|
||||||
|
LiveAPISystem()->Init(); // TODO[ AMOS ]: move to server frame !!!
|
||||||
|
#endif // !CLIENT_DLL
|
||||||
|
|
||||||
if (net_useRandomKey.GetBool())
|
if (net_useRandomKey.GetBool())
|
||||||
{
|
{
|
||||||
NET_GenerateKey();
|
NET_GenerateKey();
|
||||||
|
@ -34,6 +34,11 @@ add_sources( SOURCE_GROUP "Pak"
|
|||||||
"pak/paktools.h"
|
"pak/paktools.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_sources( SOURCE_GROUP "LiveAPI"
|
||||||
|
"liveapi/liveapi.cpp"
|
||||||
|
"liveapi/liveapi.h"
|
||||||
|
)
|
||||||
|
|
||||||
add_sources( SOURCE_GROUP "Public"
|
add_sources( SOURCE_GROUP "Public"
|
||||||
"${ENGINE_SOURCE_DIR}/public/rtech/iasync.h"
|
"${ENGINE_SOURCE_DIR}/public/rtech/iasync.h"
|
||||||
"${ENGINE_SOURCE_DIR}/public/rtech/ipakfile.h"
|
"${ENGINE_SOURCE_DIR}/public/rtech/ipakfile.h"
|
||||||
@ -41,6 +46,11 @@ add_sources( SOURCE_GROUP "Public"
|
|||||||
|
|
||||||
end_sources()
|
end_sources()
|
||||||
|
|
||||||
|
target_include_directories( ${PROJECT_NAME} PRIVATE
|
||||||
|
"${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/"
|
||||||
|
"${THIRDPARTY_SOURCE_DIR}/ea/"
|
||||||
|
)
|
||||||
|
|
||||||
add_module( "lib" "rson" "vpc" ${FOLDER_CONTEXT} TRUE TRUE )
|
add_module( "lib" "rson" "vpc" ${FOLDER_CONTEXT} TRUE TRUE )
|
||||||
|
|
||||||
start_sources()
|
start_sources()
|
||||||
|
258
src/rtech/liveapi/liveapi.cpp
Normal file
258
src/rtech/liveapi/liveapi.cpp
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
//===========================================================================//
|
||||||
|
//
|
||||||
|
// Purpose: LiveAPI WebSocket implementation
|
||||||
|
//
|
||||||
|
//===========================================================================//
|
||||||
|
#include "liveapi.h"
|
||||||
|
|
||||||
|
#include "DirtySDK/dirtysock.h"
|
||||||
|
#include "DirtySDK/dirtysock/netconn.h"
|
||||||
|
#include "DirtySDK/proto/protossl.h"
|
||||||
|
#include "DirtySDK/proto/protowebsocket.h"
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// console variables
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static ConVar liveapi_enabled("liveapi_enabled", "1", FCVAR_RELEASE);
|
||||||
|
static ConVar liveapi_servers("liveapi_servers", "ws://127.0.0.1:7777" , FCVAR_RELEASE, "Comma separated list of addresses to connect to", "'ws://domain.suffix:port'");
|
||||||
|
|
||||||
|
static ConVar liveapi_timeout("liveapi_timeout", "300", FCVAR_RELEASE, "WebSocket connection timeout in seconds");
|
||||||
|
static ConVar liveapi_keepalive("liveapi_keepalive", "30", FCVAR_RELEASE, "Interval of time to send Pong to any connected server");
|
||||||
|
static ConVar liveapi_lax_ssl("liveapi_lax_ssl", "1", FCVAR_RELEASE, "Skip SSL certificate validation for all WSS connections (allows the use of self-signed certificates)");
|
||||||
|
|
||||||
|
static ConVar liveapi_retry_count("liveapi_retry_count", "5", FCVAR_RELEASE, "Amount of times to retry connecting before marking the connection as unavailable");
|
||||||
|
static ConVar liveapi_retry_time("liveapi_retry_time", "30", FCVAR_RELEASE, "Amount of time between each retry");
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// constructors/destructors
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
LiveAPI::LiveAPI()
|
||||||
|
{
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Initialization of the LiveAPI system
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LiveAPI::Init()
|
||||||
|
{
|
||||||
|
if (!liveapi_enabled.GetBool())
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetConnStatus('open', 0, NULL, 0);
|
||||||
|
|
||||||
|
const int32_t startupRet = NetConnStartup("-servicename=liveapi");
|
||||||
|
|
||||||
|
if (startupRet < 0)
|
||||||
|
{
|
||||||
|
Error(eDLL_T::RTECH, 0, "LiveAPI: initialization failed! [%x]\n", startupRet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtoSSLStartup();
|
||||||
|
const vector<string> addresses = StringSplit(liveapi_servers.GetString(), ',');
|
||||||
|
|
||||||
|
for (const string& addres : addresses)
|
||||||
|
{
|
||||||
|
const ConnContext_s conn(addres);
|
||||||
|
servers.push_back(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Shutdown of the LiveAPI system
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LiveAPI::Shutdown()
|
||||||
|
{
|
||||||
|
initialized = false;
|
||||||
|
|
||||||
|
for (ConnContext_s& conn : servers)
|
||||||
|
{
|
||||||
|
conn.Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// LiveAPI state machine
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LiveAPI::RunFrame()
|
||||||
|
{
|
||||||
|
if (!IsEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double queryTime = Plat_FloatTime();
|
||||||
|
|
||||||
|
for (ConnContext_s& conn : servers)
|
||||||
|
{
|
||||||
|
if (conn.webSocket)
|
||||||
|
ProtoWebSocketUpdate(conn.webSocket);
|
||||||
|
|
||||||
|
if (conn.state == CS_CREATE || conn.state == CS_RETRY)
|
||||||
|
{
|
||||||
|
conn.Connect(queryTime);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn.state == CS_CONNECTED || conn.state == CS_LISTENING)
|
||||||
|
{
|
||||||
|
conn.Status(queryTime);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn.state == CS_DESTROYED)
|
||||||
|
{
|
||||||
|
if (conn.retryCount > liveapi_retry_count.GetInt())
|
||||||
|
{
|
||||||
|
// All retry attempts have been used; mark unavailable for deletion
|
||||||
|
conn.state = CS_UNAVAIL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Mark as retry, this will recreate the socket and reattempt
|
||||||
|
// the connection
|
||||||
|
conn.state = CS_RETRY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Delete all server connections marked unavailable
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LiveAPI::DeleteUnavailable()
|
||||||
|
{
|
||||||
|
servers.erase(std::remove_if(servers.begin(), servers.end(),
|
||||||
|
[](const ConnContext_s& conn)
|
||||||
|
{
|
||||||
|
return conn.state == CS_UNAVAIL;
|
||||||
|
}
|
||||||
|
), servers.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Send an event to all sockets
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LiveAPI::SendEvent(const char* const dataBuf, const int32_t dataSize)
|
||||||
|
{
|
||||||
|
for (ConnContext_s& conn : servers)
|
||||||
|
{
|
||||||
|
if (conn.state != CS_LISTENING)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ProtoWebSocketSend(conn.webSocket, dataBuf, dataSize) < 0)
|
||||||
|
conn.Destroy(); // Reattempt the connection for this socket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Returns whether the system is enabled and able to run
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool LiveAPI::IsEnabled() const
|
||||||
|
{
|
||||||
|
return initialized && liveapi_enabled.GetBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Connect to a socket
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool LiveAPI::ConnContext_s::Connect(const double queryTime)
|
||||||
|
{
|
||||||
|
const double retryTimeTotal = retryTime + liveapi_retry_time.GetFloat();
|
||||||
|
const double currTime = Plat_FloatTime();
|
||||||
|
|
||||||
|
if (retryTimeTotal > currTime)
|
||||||
|
return false; // Still within retry period
|
||||||
|
|
||||||
|
retryCount++;
|
||||||
|
webSocket = ProtoWebSocketCreate(LIVE_API_MAX_FRAME_BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (!webSocket)
|
||||||
|
{
|
||||||
|
state = CS_UNAVAIL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t timeOut = liveapi_timeout.GetInt();
|
||||||
|
|
||||||
|
if (timeOut > 0)
|
||||||
|
{
|
||||||
|
ProtoWebSocketControl(webSocket, 'time', timeOut, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t keepAlive = liveapi_keepalive.GetInt();
|
||||||
|
|
||||||
|
if (keepAlive > 0)
|
||||||
|
{
|
||||||
|
ProtoWebSocketControl(webSocket, 'keep', keepAlive, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtoWebSocketControl(webSocket, 'ncrt', liveapi_lax_ssl.GetInt(), 0, NULL);
|
||||||
|
ProtoWebSocketUpdate(webSocket);
|
||||||
|
|
||||||
|
if (ProtoWebSocketConnect(webSocket, address.c_str()) != NULL)
|
||||||
|
{
|
||||||
|
// Failure
|
||||||
|
Destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = CS_CONNECTED;
|
||||||
|
retryTime = queryTime;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Check the connection status and destroy if not connected (-1)
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool LiveAPI::ConnContext_s::Status(const double queryTime)
|
||||||
|
{
|
||||||
|
const int32_t status = ProtoWebSocketStatus(webSocket, 'stat', NULL, 0);
|
||||||
|
|
||||||
|
if (status == -1)
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
retryTime = queryTime;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (!status)
|
||||||
|
{
|
||||||
|
retryTime = queryTime;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
retryCount = 0;
|
||||||
|
state = CS_LISTENING;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Destroy the connection
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void LiveAPI::ConnContext_s::Destroy()
|
||||||
|
{
|
||||||
|
ProtoWebSocketDisconnect(webSocket);
|
||||||
|
ProtoWebSocketUpdate(webSocket);
|
||||||
|
ProtoWebSocketDestroy(webSocket);
|
||||||
|
|
||||||
|
webSocket = nullptr;
|
||||||
|
state = CS_DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LiveAPI s_liveApi;
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Singleton accessor
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
LiveAPI* LiveAPISystem()
|
||||||
|
{
|
||||||
|
return &s_liveApi;
|
||||||
|
}
|
70
src/rtech/liveapi/liveapi.h
Normal file
70
src/rtech/liveapi/liveapi.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#ifndef RTECH_LIVEAPI_H
|
||||||
|
#define RTECH_LIVEAPI_H
|
||||||
|
|
||||||
|
#define LIVE_API_MAX_FRAME_BUFFER_SIZE 0x8000
|
||||||
|
|
||||||
|
struct ProtoWebSocketRefT;
|
||||||
|
typedef void (*LiveAPISendCallback_t)(ProtoWebSocketRefT* webSocket);
|
||||||
|
|
||||||
|
class LiveAPI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum ConnState_e
|
||||||
|
{
|
||||||
|
CS_CREATE = 0,
|
||||||
|
|
||||||
|
CS_CONNECTED,
|
||||||
|
CS_LISTENING,
|
||||||
|
|
||||||
|
CS_DESTROYED,
|
||||||
|
|
||||||
|
CS_RETRY,
|
||||||
|
CS_UNAVAIL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnContext_s
|
||||||
|
{
|
||||||
|
ConnContext_s(const string& addr)
|
||||||
|
{
|
||||||
|
webSocket = nullptr;
|
||||||
|
address = addr;
|
||||||
|
|
||||||
|
state = CS_CREATE;
|
||||||
|
|
||||||
|
retryCount = 0;
|
||||||
|
retryTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Connect(const double queryTime);
|
||||||
|
bool Process(const double queryTime);
|
||||||
|
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
ProtoWebSocketRefT* webSocket;
|
||||||
|
ConnState_e state;
|
||||||
|
|
||||||
|
int retryCount;
|
||||||
|
double retryTime;
|
||||||
|
|
||||||
|
string address;
|
||||||
|
};
|
||||||
|
|
||||||
|
LiveAPI();
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
void RunFrame();
|
||||||
|
void DeleteUnavailable();
|
||||||
|
|
||||||
|
void SendEvent(const char* const dataBuf, const int32_t dataSize);
|
||||||
|
bool IsEnabled() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initialized;
|
||||||
|
vector<ConnContext_s> servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
LiveAPI* LiveAPISystem();
|
||||||
|
|
||||||
|
#endif // RTECH_LIVEAPI_H
|
Loading…
x
Reference in New Issue
Block a user