discord-rpc/src/discord-rpc.cpp

251 lines
7.4 KiB
C++
Raw Normal View History

2017-06-30 23:18:54 +00:00
#include "discord-rpc.h"
2017-07-18 18:10:39 +00:00
#include "backoff.h"
#include "rpc_connection.h"
#include "serialization.h"
2017-06-30 23:18:54 +00:00
#include <atomic>
#include <chrono>
2017-07-18 18:10:39 +00:00
#ifndef DISCORD_DISABLE_IO_THREAD
#include <condition_variable>
#include <thread>
2017-07-18 18:10:39 +00:00
#endif
constexpr size_t MaxMessageSize{16 * 1024};
constexpr size_t MessageQueueSize{8};
2017-07-17 22:42:49 +00:00
struct QueuedMessage {
size_t length;
char buffer[MaxMessageSize];
};
static RpcConnection* Connection{nullptr};
2017-06-30 23:18:54 +00:00
static char ApplicationId[64]{};
static DiscordEventHandlers Handlers{};
static std::atomic_bool WasJustConnected{false};
static std::atomic_bool WasJustDisconnected{false};
2017-07-24 21:58:53 +00:00
static std::atomic_bool GotErrorMessage{false};
static std::atomic_bool WasPresenceRequested{false};
static std::atomic_bool WasJoinGame{false};
static std::atomic_bool WasSpectateGame{false};
static char JoinGameSecret[256];
static char SpectateGameSecret[256];
2017-07-17 22:42:49 +00:00
static int LastErrorCode{0};
2017-07-13 15:32:08 +00:00
static char LastErrorMessage[256];
2017-07-24 21:58:53 +00:00
static int LastDisconnectErrorCode{0};
static char LastDisconnectErrorMessage[256];
2017-07-17 22:42:49 +00:00
static QueuedMessage SendQueue[MessageQueueSize]{};
static std::atomic_uint SendQueueNextAdd{0};
static std::atomic_uint SendQueueNextSend{0};
static std::atomic_uint SendQueuePendingSends{0};
2017-07-18 18:10:39 +00:00
static Backoff ReconnectTimeMs(500, 60 * 1000);
static auto NextConnect{std::chrono::system_clock::now()};
static int Pid{0};
static int Nonce{1};
2017-07-17 22:42:49 +00:00
2017-07-18 16:47:33 +00:00
#ifndef DISCORD_DISABLE_IO_THREAD
static std::atomic_bool KeepRunning{true};
2017-07-18 16:47:33 +00:00
static std::mutex WaitForIOMutex;
static std::condition_variable WaitForIOActivity;
static std::thread IoThread;
#endif // DISCORD_DISABLE_IO_THREAD
2017-07-18 18:10:39 +00:00
static void UpdateReconnectTime()
{
NextConnect = std::chrono::system_clock::now() + std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
}
2017-07-17 22:42:49 +00:00
static QueuedMessage* SendQueueGetNextAddMessage() {
// if we are falling behind, bail
if (SendQueuePendingSends.load() >= MessageQueueSize) {
return nullptr;
}
auto index = (SendQueueNextAdd++) % MessageQueueSize;
return &SendQueue[index];
}
static QueuedMessage* SendQueueGetNextSendMessage() {
auto index = (SendQueueNextSend++) % MessageQueueSize;
return &SendQueue[index];
}
static void SendQueueCommitMessage() {
SendQueuePendingSends++;
}
2017-07-18 16:47:33 +00:00
extern "C" void Discord_UpdateConnection()
{
if (!Connection->IsOpen()) {
2017-07-18 18:10:39 +00:00
if (std::chrono::system_clock::now() >= NextConnect) {
UpdateReconnectTime();
Connection->Open();
}
}
else {
// reads
2017-07-24 17:54:47 +00:00
JsonDocument message;
while (Connection->Read(message)) {
auto nonce = message.FindMember("nonce");
if (nonce != message.MemberEnd() && nonce->value.IsString()) {
// in responses only -- should use to match up response when needed.
//auto cmd = message.FindMember("cmd"); needed?
}
else {
// should have evt == name of event, optional data
auto evt = message.FindMember("evt");
if (evt != message.MemberEnd() && evt->value.IsString()) {
const char* evtName = evt->value.GetString();
// todo ug
if (strcmp(evtName, "PRESENCE_REQUESTED") == 0) {
WasPresenceRequested.store(true);
}
else if (strcmp(evtName, "JOIN_GAME") == 0) {
auto data = message.FindMember("data");
auto secret = data->value["secret"].GetString();
StringCopy(JoinGameSecret, secret);
WasJoinGame.store(true);
}
else if (strcmp(evtName, "SPECTATE_GAME") == 0) {
auto data = message.FindMember("data");
auto secret = data->value["secret"].GetString();
StringCopy(SpectateGameSecret, secret);
WasSpectateGame.store(true);
}
}
}
}
2017-07-17 22:42:49 +00:00
// writes
while (SendQueuePendingSends.load()) {
auto qmessage = SendQueueGetNextSendMessage();
Connection->Write(qmessage->buffer, qmessage->length);
--SendQueuePendingSends;
}
}
}
2017-07-18 16:47:33 +00:00
#ifndef DISCORD_DISABLE_IO_THREAD
void DiscordRpcIo()
{
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
while (KeepRunning.load()) {
Discord_UpdateConnection();
std::unique_lock<std::mutex> lock(WaitForIOMutex);
WaitForIOActivity.wait_for(lock, maxWait);
}
}
2017-07-18 16:47:33 +00:00
#endif
void SignalIOActivity()
{
2017-07-18 16:47:33 +00:00
#ifndef DISCORD_DISABLE_IO_THREAD
WaitForIOActivity.notify_all();
2017-07-18 16:47:33 +00:00
#endif
}
2017-06-30 23:18:54 +00:00
bool RegisterForEvent(const char* evtName)
{
auto qmessage = SendQueueGetNextAddMessage();
if (qmessage) {
qmessage->length = JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
SendQueueCommitMessage();
SignalIOActivity();
return true;
}
return false;
}
2017-07-07 21:00:29 +00:00
extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers)
2017-06-30 23:18:54 +00:00
{
2017-07-20 21:59:32 +00:00
Pid = GetProcessId();
2017-06-30 23:18:54 +00:00
if (handlers) {
Handlers = *handlers;
}
else {
Handlers = {};
}
Connection = RpcConnection::Create(applicationId);
Connection->onConnect = []() {
WasJustConnected.exchange(true);
2017-07-18 18:10:39 +00:00
ReconnectTimeMs.reset();
if (Handlers.presenceRequested) {
RegisterForEvent("PRESENCE_REQUESTED");
}
if (Handlers.joinGame) {
RegisterForEvent("JOIN_GAME");
}
if (Handlers.spectateGame) {
RegisterForEvent("SPECTATE_GAME");
}
};
Connection->onDisconnect = [](int err, const char* message) {
2017-07-24 21:58:53 +00:00
LastDisconnectErrorCode = err;
StringCopy(LastDisconnectErrorMessage, message);
WasJustDisconnected.exchange(true);
2017-07-18 18:10:39 +00:00
UpdateReconnectTime();
2017-07-17 16:28:54 +00:00
};
2017-07-18 16:47:33 +00:00
#ifndef DISCORD_DISABLE_IO_THREAD
IoThread = std::thread(DiscordRpcIo);
2017-07-18 16:47:33 +00:00
#endif
2017-06-30 23:18:54 +00:00
}
2017-07-07 21:00:29 +00:00
extern "C" void Discord_Shutdown()
2017-06-30 23:18:54 +00:00
{
Connection->onConnect = nullptr;
Connection->onDisconnect = nullptr;
2017-06-30 23:18:54 +00:00
Handlers = {};
2017-07-18 16:47:33 +00:00
#ifndef DISCORD_DISABLE_IO_THREAD
KeepRunning.exchange(false);
SignalIOActivity();
if (IoThread.joinable()) {
IoThread.join();
}
2017-07-18 16:47:33 +00:00
#endif
RpcConnection::Destroy(Connection);
2017-06-30 23:18:54 +00:00
}
2017-07-07 21:00:29 +00:00
extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence)
2017-06-30 23:18:54 +00:00
{
2017-07-17 22:42:49 +00:00
auto qmessage = SendQueueGetNextAddMessage();
if (qmessage) {
2017-07-20 22:08:34 +00:00
qmessage->length = JsonWriteRichPresenceObj(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, Pid, presence);
2017-07-17 22:42:49 +00:00
SendQueueCommitMessage();
SignalIOActivity();
}
2017-06-30 23:18:54 +00:00
}
2017-07-07 16:41:20 +00:00
2017-07-18 16:47:33 +00:00
extern "C" void Discord_RunCallbacks()
2017-07-07 16:41:20 +00:00
{
2017-07-24 21:58:53 +00:00
if (GotErrorMessage.exchange(false) && Handlers.errored) {
Handlers.errored(LastErrorCode, LastErrorMessage);
}
if (WasJustDisconnected.exchange(false) && Handlers.disconnected) {
2017-07-24 21:58:53 +00:00
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
2017-07-07 16:41:20 +00:00
}
if (WasJustConnected.exchange(false) && Handlers.ready) {
2017-07-07 16:41:20 +00:00
Handlers.ready();
}
if (WasPresenceRequested.exchange(false) && Handlers.presenceRequested) {
Handlers.presenceRequested();
}
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
Handlers.joinGame(JoinGameSecret);
}
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
Handlers.spectateGame(SpectateGameSecret);
}
2017-07-07 16:41:20 +00:00
}