diff options
Diffstat (limited to 'externals/discord-rpc/src/discord_rpc.cpp')
| -rw-r--r-- | externals/discord-rpc/src/discord_rpc.cpp | 504 |
1 files changed, 0 insertions, 504 deletions
diff --git a/externals/discord-rpc/src/discord_rpc.cpp b/externals/discord-rpc/src/discord_rpc.cpp deleted file mode 100644 index 03924538c3..0000000000 --- a/externals/discord-rpc/src/discord_rpc.cpp +++ /dev/null @@ -1,504 +0,0 @@ -#include "discord_rpc.h" - -#include "backoff.h" -#include "discord_register.h" -#include "msg_queue.h" -#include "rpc_connection.h" -#include "serialization.h" - -#include <atomic> -#include <chrono> -#include <mutex> - -#ifndef DISCORD_DISABLE_IO_THREAD -#include <condition_variable> -#include <thread> -#endif - -constexpr size_t MaxMessageSize{16 * 1024}; -constexpr size_t MessageQueueSize{8}; -constexpr size_t JoinQueueSize{8}; - -struct QueuedMessage { - size_t length; - char buffer[MaxMessageSize]; - - void Copy(const QueuedMessage& other) - { - length = other.length; - if (length) { - memcpy(buffer, other.buffer, length); - } - } -}; - -struct User { - // snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null - // terminator = 21 - char userId[32]; - // 32 unicode glyphs is max name size => 4 bytes per glyph in the worst case, +1 for null - // terminator = 129 - char username[344]; - // 4 decimal digits + 1 null terminator = 5 - char discriminator[8]; - // optional 'a_' + md5 hex digest (32 bytes) + null terminator = 35 - char avatar[128]; - // Rounded way up because I'm paranoid about games breaking from future changes in these sizes -}; - -static RpcConnection* Connection{nullptr}; -static DiscordEventHandlers QueuedHandlers{}; -static DiscordEventHandlers Handlers{}; -static std::atomic_bool WasJustConnected{false}; -static std::atomic_bool WasJustDisconnected{false}; -static std::atomic_bool GotErrorMessage{false}; -static std::atomic_bool WasJoinGame{false}; -static std::atomic_bool WasSpectateGame{false}; -static std::atomic_bool UpdatePresence{false}; -static char JoinGameSecret[256]; -static char SpectateGameSecret[256]; -static int LastErrorCode{0}; -static char LastErrorMessage[256]; -static int LastDisconnectErrorCode{0}; -static char LastDisconnectErrorMessage[256]; -static std::mutex PresenceMutex; -static std::mutex HandlerMutex; -static QueuedMessage QueuedPresence{}; -static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue; -static MsgQueue<User, JoinQueueSize> JoinAskQueue; -static User connectedUser; - -// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential -// backoff from 0.5 seconds to 1 minute -static Backoff ReconnectTimeMs(500, 60 * 1000); -static auto NextConnect = std::chrono::system_clock::now(); -static int Pid{0}; -static int Nonce{1}; - -#ifndef DISCORD_DISABLE_IO_THREAD -static void Discord_UpdateConnection(void); -class IoThreadHolder { -private: - std::atomic_bool keepRunning{true}; - std::mutex waitForIOMutex; - std::condition_variable waitForIOActivity; - std::thread ioThread; - -public: - void Start() - { - keepRunning.store(true); - ioThread = std::thread([&]() { - const std::chrono::duration<int64_t, std::milli> maxWait{500LL}; - Discord_UpdateConnection(); - while (keepRunning.load()) { - std::unique_lock<std::mutex> lock(waitForIOMutex); - waitForIOActivity.wait_for(lock, maxWait); - Discord_UpdateConnection(); - } - }); - } - - void Notify() { waitForIOActivity.notify_all(); } - - void Stop() - { - keepRunning.exchange(false); - Notify(); - if (ioThread.joinable()) { - ioThread.join(); - } - } - - ~IoThreadHolder() { Stop(); } -}; -#else -class IoThreadHolder { -public: - void Start() {} - void Stop() {} - void Notify() {} -}; -#endif // DISCORD_DISABLE_IO_THREAD -static IoThreadHolder* IoThread{nullptr}; - -static void UpdateReconnectTime() -{ - NextConnect = std::chrono::system_clock::now() + - std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()}; -} - -#ifdef DISCORD_DISABLE_IO_THREAD -extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void) -#else -static void Discord_UpdateConnection(void) -#endif -{ - if (!Connection) { - return; - } - - if (!Connection->IsOpen()) { - if (std::chrono::system_clock::now() >= NextConnect) { - UpdateReconnectTime(); - Connection->Open(); - } - } - else { - // reads - - for (;;) { - JsonDocument message; - - if (!Connection->Read(message)) { - break; - } - - const char* evtName = GetStrMember(&message, "evt"); - const char* nonce = GetStrMember(&message, "nonce"); - - if (nonce) { - // in responses only -- should use to match up response when needed. - - if (evtName && strcmp(evtName, "ERROR") == 0) { - auto data = GetObjMember(&message, "data"); - LastErrorCode = GetIntMember(data, "code"); - StringCopy(LastErrorMessage, GetStrMember(data, "message", "")); - GotErrorMessage.store(true); - } - } - else { - // should have evt == name of event, optional data - if (evtName == nullptr) { - continue; - } - - auto data = GetObjMember(&message, "data"); - - if (strcmp(evtName, "ACTIVITY_JOIN") == 0) { - auto secret = GetStrMember(data, "secret"); - if (secret) { - StringCopy(JoinGameSecret, secret); - WasJoinGame.store(true); - } - } - else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) { - auto secret = GetStrMember(data, "secret"); - if (secret) { - StringCopy(SpectateGameSecret, secret); - WasSpectateGame.store(true); - } - } - else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) { - auto user = GetObjMember(data, "user"); - auto userId = GetStrMember(user, "id"); - auto username = GetStrMember(user, "username"); - auto avatar = GetStrMember(user, "avatar"); - auto joinReq = JoinAskQueue.GetNextAddMessage(); - if (userId && username && joinReq) { - StringCopy(joinReq->userId, userId); - StringCopy(joinReq->username, username); - auto discriminator = GetStrMember(user, "discriminator"); - if (discriminator) { - StringCopy(joinReq->discriminator, discriminator); - } - if (avatar) { - StringCopy(joinReq->avatar, avatar); - } - else { - joinReq->avatar[0] = 0; - } - JoinAskQueue.CommitAdd(); - } - } - } - } - - // writes - if (UpdatePresence.exchange(false) && QueuedPresence.length) { - QueuedMessage local; - { - std::lock_guard<std::mutex> guard(PresenceMutex); - local.Copy(QueuedPresence); - } - if (!Connection->Write(local.buffer, local.length)) { - // if we fail to send, requeue - std::lock_guard<std::mutex> guard(PresenceMutex); - QueuedPresence.Copy(local); - UpdatePresence.exchange(true); - } - } - - while (SendQueue.HavePendingSends()) { - auto qmessage = SendQueue.GetNextSendMessage(); - Connection->Write(qmessage->buffer, qmessage->length); - SendQueue.CommitSend(); - } - } -} - -static void SignalIOActivity() -{ - if (IoThread != nullptr) { - IoThread->Notify(); - } -} - -static bool RegisterForEvent(const char* evtName) -{ - auto qmessage = SendQueue.GetNextAddMessage(); - if (qmessage) { - qmessage->length = - JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName); - SendQueue.CommitAdd(); - SignalIOActivity(); - return true; - } - return false; -} - -static bool DeregisterForEvent(const char* evtName) -{ - auto qmessage = SendQueue.GetNextAddMessage(); - if (qmessage) { - qmessage->length = - JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName); - SendQueue.CommitAdd(); - SignalIOActivity(); - return true; - } - return false; -} - -extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId, - DiscordEventHandlers* handlers, - int autoRegister, - const char* optionalSteamId) -{ - IoThread = new (std::nothrow) IoThreadHolder(); - if (IoThread == nullptr) { - return; - } - - if (autoRegister) { - if (optionalSteamId && optionalSteamId[0]) { - Discord_RegisterSteamGame(applicationId, optionalSteamId); - } - else { - Discord_Register(applicationId, nullptr); - } - } - - Pid = GetProcessId(); - - { - std::lock_guard<std::mutex> guard(HandlerMutex); - - if (handlers) { - QueuedHandlers = *handlers; - } - else { - QueuedHandlers = {}; - } - - Handlers = {}; - } - - if (Connection) { - return; - } - - Connection = RpcConnection::Create(applicationId); - Connection->onConnect = [](JsonDocument& readyMessage) { - Discord_UpdateHandlers(&QueuedHandlers); - if (QueuedPresence.length > 0) { - UpdatePresence.exchange(true); - SignalIOActivity(); - } - auto data = GetObjMember(&readyMessage, "data"); - auto user = GetObjMember(data, "user"); - auto userId = GetStrMember(user, "id"); - auto username = GetStrMember(user, "username"); - auto avatar = GetStrMember(user, "avatar"); - if (userId && username) { - StringCopy(connectedUser.userId, userId); - StringCopy(connectedUser.username, username); - auto discriminator = GetStrMember(user, "discriminator"); - if (discriminator) { - StringCopy(connectedUser.discriminator, discriminator); - } - if (avatar) { - StringCopy(connectedUser.avatar, avatar); - } - else { - connectedUser.avatar[0] = 0; - } - } - WasJustConnected.exchange(true); - ReconnectTimeMs.reset(); - }; - Connection->onDisconnect = [](int err, const char* message) { - LastDisconnectErrorCode = err; - StringCopy(LastDisconnectErrorMessage, message); - WasJustDisconnected.exchange(true); - UpdateReconnectTime(); - }; - - IoThread->Start(); -} - -extern "C" DISCORD_EXPORT void Discord_Shutdown(void) -{ - if (!Connection) { - return; - } - Connection->onConnect = nullptr; - Connection->onDisconnect = nullptr; - Handlers = {}; - QueuedPresence.length = 0; - UpdatePresence.exchange(false); - if (IoThread != nullptr) { - IoThread->Stop(); - delete IoThread; - IoThread = nullptr; - } - - RpcConnection::Destroy(Connection); -} - -extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence) -{ - { - std::lock_guard<std::mutex> guard(PresenceMutex); - QueuedPresence.length = JsonWriteRichPresenceObj( - QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence); - UpdatePresence.exchange(true); - } - SignalIOActivity(); -} - -extern "C" DISCORD_EXPORT void Discord_ClearPresence(void) -{ - Discord_UpdatePresence(nullptr); -} - -extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply) -{ - // if we are not connected, let's not batch up stale messages for later - if (!Connection || !Connection->IsOpen()) { - return; - } - auto qmessage = SendQueue.GetNextAddMessage(); - if (qmessage) { - qmessage->length = - JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++); - SendQueue.CommitAdd(); - SignalIOActivity(); - } -} - -extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void) -{ - // Note on some weirdness: internally we might connect, get other signals, disconnect any number - // of times inbetween calls here. Externally, we want the sequence to seem sane, so any other - // signals are book-ended by calls to ready and disconnect. - - if (!Connection) { - return; - } - - bool wasDisconnected = WasJustDisconnected.exchange(false); - bool isConnected = Connection->IsOpen(); - - if (isConnected) { - // if we are connected, disconnect cb first - std::lock_guard<std::mutex> guard(HandlerMutex); - if (wasDisconnected && Handlers.disconnected) { - Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); - } - } - - if (WasJustConnected.exchange(false)) { - std::lock_guard<std::mutex> guard(HandlerMutex); - if (Handlers.ready) { - DiscordUser du{connectedUser.userId, - connectedUser.username, - connectedUser.discriminator, - connectedUser.avatar}; - Handlers.ready(&du); - } - } - - if (GotErrorMessage.exchange(false)) { - std::lock_guard<std::mutex> guard(HandlerMutex); - if (Handlers.errored) { - Handlers.errored(LastErrorCode, LastErrorMessage); - } - } - - if (WasJoinGame.exchange(false)) { - std::lock_guard<std::mutex> guard(HandlerMutex); - if (Handlers.joinGame) { - Handlers.joinGame(JoinGameSecret); - } - } - - if (WasSpectateGame.exchange(false)) { - std::lock_guard<std::mutex> guard(HandlerMutex); - if (Handlers.spectateGame) { - Handlers.spectateGame(SpectateGameSecret); - } - } - - // Right now this batches up any requests and sends them all in a burst; I could imagine a world - // where the implementer would rather sequentially accept/reject each one before the next invite - // is sent. I left it this way because I could also imagine wanting to process these all and - // maybe show them in one common dialog and/or start fetching the avatars in parallel, and if - // not it should be trivial for the implementer to make a queue themselves. - while (JoinAskQueue.HavePendingSends()) { - auto req = JoinAskQueue.GetNextSendMessage(); - { - std::lock_guard<std::mutex> guard(HandlerMutex); - if (Handlers.joinRequest) { - DiscordUser du{req->userId, req->username, req->discriminator, req->avatar}; - Handlers.joinRequest(&du); - } - } - JoinAskQueue.CommitSend(); - } - - if (!isConnected) { - // if we are not connected, disconnect message last - std::lock_guard<std::mutex> guard(HandlerMutex); - if (wasDisconnected && Handlers.disconnected) { - Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); - } - } -} - -extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers) -{ - if (newHandlers) { -#define HANDLE_EVENT_REGISTRATION(handler_name, event) \ - if (!Handlers.handler_name && newHandlers->handler_name) { \ - RegisterForEvent(event); \ - } \ - else if (Handlers.handler_name && !newHandlers->handler_name) { \ - DeregisterForEvent(event); \ - } - - std::lock_guard<std::mutex> guard(HandlerMutex); - HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN") - HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE") - HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST") - -#undef HANDLE_EVENT_REGISTRATION - - Handlers = *newHandlers; - } - else { - std::lock_guard<std::mutex> guard(HandlerMutex); - Handlers = {}; - } - return; -} |
