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"
|
||||
#ifndef CLIENT_DLL
|
||||
#include "engine/server/server.h"
|
||||
#include "rtech/liveapi/liveapi.h"
|
||||
#endif // !CLIENT_DLL
|
||||
#include "rtech/stryder/stryder.h"
|
||||
#include "rtech/playlists/playlists.h"
|
||||
@ -156,6 +157,10 @@ void CHostState::FrameUpdate(CHostState* pHostState, double flCurrentTime, float
|
||||
RCONClient()->RunFrame();
|
||||
#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"
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4611)
|
||||
@ -305,6 +310,10 @@ void CHostState::Setup(void)
|
||||
RCONClient()->Init();
|
||||
#endif // !DEDICATED
|
||||
|
||||
#ifndef CLIENT_DLL
|
||||
LiveAPISystem()->Init(); // TODO[ AMOS ]: move to server frame !!!
|
||||
#endif // !CLIENT_DLL
|
||||
|
||||
if (net_useRandomKey.GetBool())
|
||||
{
|
||||
NET_GenerateKey();
|
||||
|
@ -34,6 +34,11 @@ add_sources( SOURCE_GROUP "Pak"
|
||||
"pak/paktools.h"
|
||||
)
|
||||
|
||||
add_sources( SOURCE_GROUP "LiveAPI"
|
||||
"liveapi/liveapi.cpp"
|
||||
"liveapi/liveapi.h"
|
||||
)
|
||||
|
||||
add_sources( SOURCE_GROUP "Public"
|
||||
"${ENGINE_SOURCE_DIR}/public/rtech/iasync.h"
|
||||
"${ENGINE_SOURCE_DIR}/public/rtech/ipakfile.h"
|
||||
@ -41,6 +46,11 @@ add_sources( SOURCE_GROUP "Public"
|
||||
|
||||
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 )
|
||||
|
||||
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