2017-06-30 23:18:54 +00:00
|
|
|
#include "discord-rpc.h"
|
|
|
|
|
2017-07-18 18:10:39 +00:00
|
|
|
#include "backoff.h"
|
2017-07-27 20:29:24 +00:00
|
|
|
#include "discord-register.h"
|
2017-07-20 20:24:18 +00:00
|
|
|
#include "rpc_connection.h"
|
|
|
|
#include "serialization.h"
|
2017-06-30 23:18:54 +00:00
|
|
|
|
2017-07-17 21:49:31 +00:00
|
|
|
#include <atomic>
|
|
|
|
#include <chrono>
|
2017-07-28 17:59:32 +00:00
|
|
|
#include <mutex>
|
2017-07-18 18:10:39 +00:00
|
|
|
|
|
|
|
#ifndef DISCORD_DISABLE_IO_THREAD
|
2017-07-17 21:49:31 +00:00
|
|
|
#include <condition_variable>
|
|
|
|
#include <thread>
|
2017-07-18 18:10:39 +00:00
|
|
|
#endif
|
2017-07-11 22:59:14 +00:00
|
|
|
|
2017-07-21 22:42:59 +00:00
|
|
|
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];
|
2017-07-28 17:59:32 +00:00
|
|
|
|
|
|
|
void Copy(const QueuedMessage& other)
|
|
|
|
{
|
|
|
|
length = other.length;
|
|
|
|
if (length) {
|
|
|
|
memcpy(buffer, other.buffer, length);
|
|
|
|
}
|
|
|
|
}
|
2017-07-17 22:42:49 +00:00
|
|
|
};
|
|
|
|
|
2017-07-17 21:49:31 +00:00
|
|
|
static RpcConnection* Connection{nullptr};
|
2017-06-30 23:18:54 +00:00
|
|
|
static DiscordEventHandlers Handlers{};
|
2017-07-17 21:49:31 +00:00
|
|
|
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};
|
2017-07-21 22:42:59 +00:00
|
|
|
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-28 17:59:32 +00:00
|
|
|
static std::mutex PresenceMutex;
|
|
|
|
static QueuedMessage QueuedPresence{};
|
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-28 16:45:11 +00:00
|
|
|
// 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
|
2017-07-18 18:10:39 +00:00
|
|
|
static Backoff ReconnectTimeMs(500, 60 * 1000);
|
|
|
|
static auto NextConnect{std::chrono::system_clock::now()};
|
2017-07-21 22:42:59 +00:00
|
|
|
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
|
2017-07-21 22:42:59 +00:00
|
|
|
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()
|
|
|
|
{
|
2017-07-25 16:27:48 +00:00
|
|
|
NextConnect = std::chrono::system_clock::now() +
|
|
|
|
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
|
2017-07-18 18:10:39 +00:00
|
|
|
}
|
|
|
|
|
2017-07-25 16:27:48 +00:00
|
|
|
static QueuedMessage* SendQueueGetNextAddMessage()
|
|
|
|
{
|
2017-07-28 16:45:53 +00:00
|
|
|
// if we are not connected, let's not batch up stale messages for later
|
|
|
|
if (!Connection || !Connection->IsOpen()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-07-17 22:42:49 +00:00
|
|
|
// if we are falling behind, bail
|
|
|
|
if (SendQueuePendingSends.load() >= MessageQueueSize) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
auto index = (SendQueueNextAdd++) % MessageQueueSize;
|
|
|
|
return &SendQueue[index];
|
|
|
|
}
|
2017-07-25 16:27:48 +00:00
|
|
|
static QueuedMessage* SendQueueGetNextSendMessage()
|
|
|
|
{
|
2017-07-17 22:42:49 +00:00
|
|
|
auto index = (SendQueueNextSend++) % MessageQueueSize;
|
|
|
|
return &SendQueue[index];
|
|
|
|
}
|
2017-07-25 16:27:48 +00:00
|
|
|
static void SendQueueCommitMessage()
|
|
|
|
{
|
2017-07-17 22:42:49 +00:00
|
|
|
SendQueuePendingSends++;
|
|
|
|
}
|
2017-07-17 21:49:31 +00:00
|
|
|
|
2017-07-18 16:47:33 +00:00
|
|
|
extern "C" void Discord_UpdateConnection()
|
2017-07-17 21:49:31 +00:00
|
|
|
{
|
2017-09-07 16:23:35 +00:00
|
|
|
if (!Connection) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-17 21:49:31 +00:00
|
|
|
if (!Connection->IsOpen()) {
|
2017-07-18 18:10:39 +00:00
|
|
|
if (std::chrono::system_clock::now() >= NextConnect) {
|
|
|
|
UpdateReconnectTime();
|
|
|
|
Connection->Open();
|
|
|
|
}
|
2017-07-17 21:49:31 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// reads
|
2017-07-24 21:59:45 +00:00
|
|
|
|
|
|
|
for (;;) {
|
2017-07-25 16:06:48 +00:00
|
|
|
JsonDocument message;
|
2017-07-24 21:59:45 +00:00
|
|
|
|
|
|
|
if (!Connection->Read(message)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-07-28 17:03:05 +00:00
|
|
|
const char* evtName = GetStrMember(&message, "evt");
|
|
|
|
const char* nonce = GetStrMember(&message, "nonce");
|
2017-07-24 21:59:45 +00:00
|
|
|
|
2017-07-28 17:03:05 +00:00
|
|
|
if (nonce) {
|
2017-07-21 22:42:59 +00:00
|
|
|
// in responses only -- should use to match up response when needed.
|
2017-07-24 21:59:45 +00:00
|
|
|
|
|
|
|
if (evtName && strcmp(evtName, "ERROR") == 0) {
|
2017-07-28 17:03:05 +00:00
|
|
|
auto data = GetObjMember(&message, "data");
|
|
|
|
LastErrorCode = GetIntMember(data, "code");
|
|
|
|
StringCopy(LastErrorMessage, GetStrMember(data, "message", ""));
|
2017-07-24 21:59:45 +00:00
|
|
|
GotErrorMessage.store(true);
|
|
|
|
}
|
2017-07-21 22:42:59 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// should have evt == name of event, optional data
|
2017-07-24 21:59:45 +00:00
|
|
|
if (evtName == nullptr) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-07-28 07:19:10 +00:00
|
|
|
if (strcmp(evtName, "GAME_JOIN") == 0) {
|
2017-07-28 17:03:05 +00:00
|
|
|
auto data = GetObjMember(&message, "data");
|
|
|
|
auto secret = GetStrMember(data, "secret");
|
|
|
|
if (secret) {
|
|
|
|
StringCopy(JoinGameSecret, secret);
|
|
|
|
WasJoinGame.store(true);
|
|
|
|
}
|
2017-07-24 21:59:45 +00:00
|
|
|
}
|
2017-07-28 07:19:10 +00:00
|
|
|
else if (strcmp(evtName, "GAME_SPECTATE") == 0) {
|
2017-07-28 17:03:05 +00:00
|
|
|
auto data = GetObjMember(&message, "data");
|
|
|
|
auto secret = GetStrMember(data, "secret");
|
|
|
|
if (secret) {
|
|
|
|
StringCopy(SpectateGameSecret, secret);
|
|
|
|
WasSpectateGame.store(true);
|
|
|
|
}
|
2017-07-21 22:42:59 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-17 21:49:31 +00:00
|
|
|
}
|
2017-07-17 22:42:49 +00:00
|
|
|
|
|
|
|
// writes
|
2017-07-28 17:59:32 +00:00
|
|
|
if (QueuedPresence.length) {
|
|
|
|
QueuedMessage local;
|
|
|
|
PresenceMutex.lock();
|
|
|
|
local.Copy(QueuedPresence);
|
|
|
|
QueuedPresence.length = 0;
|
|
|
|
PresenceMutex.unlock();
|
|
|
|
if (!Connection->Write(local.buffer, local.length)) {
|
|
|
|
// if we fail to send, requeue
|
|
|
|
PresenceMutex.lock();
|
|
|
|
QueuedPresence.Copy(local);
|
|
|
|
PresenceMutex.unlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-17 22:42:49 +00:00
|
|
|
while (SendQueuePendingSends.load()) {
|
|
|
|
auto qmessage = SendQueueGetNextSendMessage();
|
|
|
|
Connection->Write(qmessage->buffer, qmessage->length);
|
|
|
|
--SendQueuePendingSends;
|
|
|
|
}
|
2017-07-17 21:49:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-18 16:47:33 +00:00
|
|
|
#ifndef DISCORD_DISABLE_IO_THREAD
|
2017-07-17 21:49:31 +00:00
|
|
|
void DiscordRpcIo()
|
|
|
|
{
|
|
|
|
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
|
2017-07-25 16:27:48 +00:00
|
|
|
|
2017-07-17 21:49:31 +00:00
|
|
|
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
|
2017-07-17 21:49:31 +00:00
|
|
|
|
|
|
|
void SignalIOActivity()
|
|
|
|
{
|
2017-07-18 16:47:33 +00:00
|
|
|
#ifndef DISCORD_DISABLE_IO_THREAD
|
2017-07-17 21:49:31 +00:00
|
|
|
WaitForIOActivity.notify_all();
|
2017-07-18 16:47:33 +00:00
|
|
|
#endif
|
2017-07-17 21:49:31 +00:00
|
|
|
}
|
2017-06-30 23:18:54 +00:00
|
|
|
|
2017-07-21 22:42:59 +00:00
|
|
|
bool RegisterForEvent(const char* evtName)
|
|
|
|
{
|
|
|
|
auto qmessage = SendQueueGetNextAddMessage();
|
|
|
|
if (qmessage) {
|
2017-07-25 16:27:48 +00:00
|
|
|
qmessage->length =
|
|
|
|
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
2017-07-21 22:42:59 +00:00
|
|
|
SendQueueCommitMessage();
|
|
|
|
SignalIOActivity();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-07-27 20:52:37 +00:00
|
|
|
extern "C" void Discord_Initialize(const char* applicationId,
|
|
|
|
DiscordEventHandlers* handlers,
|
2017-08-30 22:17:47 +00:00
|
|
|
int autoRegister,
|
|
|
|
const char* optionalSteamId)
|
2017-06-30 23:18:54 +00:00
|
|
|
{
|
2017-07-27 20:29:24 +00:00
|
|
|
if (autoRegister) {
|
2017-08-30 22:17:47 +00:00
|
|
|
if (optionalSteamId && optionalSteamId[0]) {
|
|
|
|
Discord_RegisterSteamGame(applicationId, optionalSteamId);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Discord_Register(applicationId, nullptr);
|
|
|
|
}
|
2017-07-27 20:29:24 +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 = {};
|
|
|
|
}
|
|
|
|
|
2017-08-03 17:47:27 +00:00
|
|
|
if (Connection) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-17 21:49:31 +00:00
|
|
|
Connection = RpcConnection::Create(applicationId);
|
|
|
|
Connection->onConnect = []() {
|
|
|
|
WasJustConnected.exchange(true);
|
2017-07-18 18:10:39 +00:00
|
|
|
ReconnectTimeMs.reset();
|
2017-07-21 22:42:59 +00:00
|
|
|
|
|
|
|
if (Handlers.joinGame) {
|
2017-07-28 07:19:10 +00:00
|
|
|
RegisterForEvent("GAME_JOIN");
|
2017-07-21 22:42:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (Handlers.spectateGame) {
|
2017-07-28 07:19:10 +00:00
|
|
|
RegisterForEvent("GAME_SPECTATE");
|
2017-07-21 22:42:59 +00:00
|
|
|
}
|
2017-07-17 21:49:31 +00:00
|
|
|
};
|
|
|
|
Connection->onDisconnect = [](int err, const char* message) {
|
2017-07-24 21:58:53 +00:00
|
|
|
LastDisconnectErrorCode = err;
|
|
|
|
StringCopy(LastDisconnectErrorMessage, message);
|
2017-07-17 21:49:31 +00:00
|
|
|
WasJustDisconnected.exchange(true);
|
2017-07-18 18:10:39 +00:00
|
|
|
UpdateReconnectTime();
|
2017-07-17 16:28:54 +00:00
|
|
|
};
|
2017-07-17 21:49:31 +00:00
|
|
|
|
2017-07-18 16:47:33 +00:00
|
|
|
#ifndef DISCORD_DISABLE_IO_THREAD
|
2017-08-03 17:47:27 +00:00
|
|
|
KeepRunning.store(true);
|
2017-07-17 21:49:31 +00:00
|
|
|
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
|
|
|
{
|
2017-08-03 17:47:27 +00:00
|
|
|
if (!Connection) {
|
|
|
|
return;
|
|
|
|
}
|
2017-07-17 21:49:31 +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
|
2017-07-17 21:49:31 +00:00
|
|
|
KeepRunning.exchange(false);
|
|
|
|
SignalIOActivity();
|
|
|
|
if (IoThread.joinable()) {
|
|
|
|
IoThread.join();
|
|
|
|
}
|
2017-07-18 16:47:33 +00:00
|
|
|
#endif
|
2017-07-17 21:49:31 +00:00
|
|
|
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-28 17:59:32 +00:00
|
|
|
PresenceMutex.lock();
|
|
|
|
QueuedPresence.length = JsonWriteRichPresenceObj(
|
|
|
|
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
|
|
|
PresenceMutex.unlock();
|
|
|
|
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-08-02 17:44:55 +00:00
|
|
|
// 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.
|
2017-07-24 21:58:53 +00:00
|
|
|
|
2017-09-07 16:23:35 +00:00
|
|
|
if (!Connection) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:44:55 +00:00
|
|
|
bool wasDisconnected = WasJustDisconnected.exchange(false);
|
|
|
|
bool isConnected = Connection->IsOpen();
|
|
|
|
|
|
|
|
if (isConnected) {
|
|
|
|
// if we are connected, disconnect cb first
|
|
|
|
if (wasDisconnected && Handlers.disconnected) {
|
|
|
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
|
|
|
}
|
2017-07-07 16:41:20 +00:00
|
|
|
}
|
|
|
|
|
2017-07-17 21:49:31 +00:00
|
|
|
if (WasJustConnected.exchange(false) && Handlers.ready) {
|
2017-07-07 16:41:20 +00:00
|
|
|
Handlers.ready();
|
|
|
|
}
|
2017-07-21 22:42:59 +00:00
|
|
|
|
2017-08-02 17:44:55 +00:00
|
|
|
if (GotErrorMessage.exchange(false) && Handlers.errored) {
|
|
|
|
Handlers.errored(LastErrorCode, LastErrorMessage);
|
|
|
|
}
|
|
|
|
|
2017-07-21 22:42:59 +00:00
|
|
|
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
|
|
|
|
Handlers.joinGame(JoinGameSecret);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
|
|
|
|
Handlers.spectateGame(SpectateGameSecret);
|
|
|
|
}
|
2017-08-02 17:44:55 +00:00
|
|
|
|
|
|
|
if (!isConnected) {
|
|
|
|
// if we are not connected, disconnect message last
|
|
|
|
if (wasDisconnected && Handlers.disconnected) {
|
|
|
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
|
|
|
}
|
|
|
|
}
|
2017-07-07 16:41:20 +00:00
|
|
|
}
|