diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f485074..560914b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ include_directories(${PROJECT_SOURCE_DIR}/include) -set(BASE_RPC_SRC ${PROJECT_SOURCE_DIR}/include/discord-rpc.h discord-rpc.cpp rpc_connection.h rpc_connection.cpp yolojson.h connection.h backoff.h) +set(BASE_RPC_SRC ${PROJECT_SOURCE_DIR}/include/discord-rpc.h discord-rpc.cpp rpc_connection.h rpc_connection.cpp serialization.h serialization.cpp connection.h backoff.h) if(WIN32) add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win.cpp) diff --git a/src/discord-rpc.cpp b/src/discord-rpc.cpp index d45854a..4487651 100644 --- a/src/discord-rpc.cpp +++ b/src/discord-rpc.cpp @@ -1,10 +1,8 @@ #include "discord-rpc.h" -#include "rpc_connection.h" -#include "yolojson.h" #include "backoff.h" - -#include "rapidjson/document.h" +#include "rpc_connection.h" +#include "serialization.h" #include #include @@ -126,7 +124,7 @@ extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandle }; Connection->onDisconnect = [](int err, const char* message) { LastErrorCode = err; - StringCopy(LastErrorMessage, message, sizeof(LastErrorMessage)); + StringCopy(LastErrorMessage, message); WasJustDisconnected.exchange(true); UpdateReconnectTime(); }; @@ -155,9 +153,7 @@ extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence) { auto qmessage = SendQueueGetNextAddMessage(); if (qmessage) { - char* jsonWrite = qmessage->buffer; - JsonWriteRichPresenceObj(jsonWrite, presence); - qmessage->length = jsonWrite - qmessage->buffer; + qmessage->length = JsonWriteRichPresenceObj(qmessage->buffer, sizeof(qmessage->buffer), presence); SendQueueCommitMessage(); SignalIOActivity(); } diff --git a/src/rpc_connection.cpp b/src/rpc_connection.cpp index ec75aff..12dfdc6 100644 --- a/src/rpc_connection.cpp +++ b/src/rpc_connection.cpp @@ -1,5 +1,5 @@ #include "rpc_connection.h" -#include "yolojson.h" +#include "serialization.h" #include @@ -9,7 +9,7 @@ static RpcConnection Instance; /*static*/ RpcConnection* RpcConnection::Create(const char* applicationId) { Instance.connection = BaseConnection::Create(); - StringCopy(Instance.appId, applicationId, sizeof(Instance.appId)); + StringCopy(Instance.appId, applicationId); return &Instance; } @@ -35,9 +35,7 @@ void RpcConnection::Open() } sendFrame.opcode = Opcode::Handshake; - char* json = sendFrame.message; - JsonWriteHandshakeObj(json, RpcVersion, appId); - sendFrame.length = json - sendFrame.message; + sendFrame.length = JsonWriteHandshakeObj(sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId); if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) { state = State::Connected; @@ -97,7 +95,7 @@ bool RpcConnection::Read(rapidjson::Document& message) message.ParseInsitu(readFrame.message); lastErrorCode = message["code"].GetInt(); const auto& m = message["message"]; - StringCopy(lastErrorMessage, m.GetString(), sizeof(lastErrorMessage)); + StringCopy(lastErrorMessage, m.GetString()); Close(); return false; } @@ -105,13 +103,11 @@ bool RpcConnection::Read(rapidjson::Document& message) message.ParseInsitu(readFrame.message); return true; case Opcode::Ping: - { readFrame.opcode = Opcode::Pong; if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) { Close(); } break; - } case Opcode::Pong: break; default: diff --git a/src/serialization.cpp b/src/serialization.cpp new file mode 100644 index 0000000..6ff01b7 --- /dev/null +++ b/src/serialization.cpp @@ -0,0 +1,202 @@ +#include "connection.h" +#include "discord-rpc.h" + +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" + +// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need to supply some of +// your own allocators for stuff rather than use the defaults + +class LinearAllocator { +public: + char* buffer_; + char* end_; + LinearAllocator() { + assert(0); // needed for some default case in rapidjson, should not use + } + LinearAllocator(char* buffer, size_t size) : buffer_(buffer), end_(buffer + size) {} + static const bool kNeedFree = false; + void* Malloc(size_t size) + { + char* res = buffer_; + buffer_ += size; + if (buffer_ > end_) { + buffer_ = res; + return nullptr; + } + return res; + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) + { + if (newSize == 0) { + return nullptr; + } + // allocate how much you need in the first place + assert(!originalPtr && !originalSize); + return Malloc(newSize); + } + static void Free(void* ptr) { /* shrug */ } +}; + +template +class FixedLinearAllocator : public LinearAllocator { +public: + char fixedBuffer_[Size]; + FixedLinearAllocator() : LinearAllocator(fixedBuffer_, Size) {} + static const bool kNeedFree = false; +}; + +// wonder why this isn't a thing already, maybe I missed it +class DirectStringBuffer { +public: + typedef typename char Ch; + char* buffer_; + char* end_; + char* current_; + + DirectStringBuffer(char* buffer, size_t maxLen) + : buffer_(buffer) + , end_(buffer + maxLen) + , current_(buffer) + {} + + void Put(char c) + { + if (current_ < end_) { + *current_++ = c; + } + } + void Flush() {} + size_t GetSize() const + { + return current_ - buffer_; + } +}; + +using Encoding = rapidjson::UTF8; +// Writer appears to need about 16 bytes per nested object level (with 64bit size_t) +using WriterAllocator = FixedLinearAllocator<2048>; +constexpr size_t WriterNestingLevels = 2048 / 16; +using JsonWriter = rapidjson::Writer; + +// it's ever so slightly faster to not have to strlen the key +template +void WriteKey(JsonWriter& w, T& k) { + w.Key(k, sizeof(T) - 1); +} + +template +void WriteOptionalString(JsonWriter& w, T& k, const char* value) { + if (value) { + w.Key(k, sizeof(T) - 1); + w.String(value); + } +} + +size_t JsonWriteRichPresenceObj(char* dest, size_t maxLen, const DiscordRichPresence* presence) +{ + DirectStringBuffer sb(dest, maxLen); + WriterAllocator wa; + JsonWriter writer(sb, &wa, WriterNestingLevels); + + // const args = {pid, activity}; + // this.socket.write(encode(OPCODES.FRAME, { nonce: uuid(), cmd : 'SET_ACTIVITY', args }) + + writer.StartObject(); + + WriteKey(writer, "args"); + writer.StartObject(); + + WriteKey(writer, "activity"); + writer.StartObject(); + + WriteOptionalString(writer, "state", presence->state); + WriteOptionalString(writer, "details", presence->details); + + if (presence->startTimestamp || presence->endTimestamp) { + WriteKey(writer, "timestamps"); + writer.StartObject(); + + if (presence->startTimestamp) { + WriteKey(writer, "start"); + writer.Int64(presence->startTimestamp); + } + + if (presence->endTimestamp) { + WriteKey(writer, "end"); + writer.Int64(presence->endTimestamp); + } + + writer.EndObject(); + } + + if (presence->largeImageKey || presence->largeImageText || presence->smallImageKey || presence->smallImageText) { + WriteKey(writer, "assets"); + writer.StartObject(); + + WriteOptionalString(writer, "large_image", presence->largeImageKey); + WriteOptionalString(writer, "large_text", presence->largeImageText); + WriteOptionalString(writer, "small_image", presence->smallImageKey); + WriteOptionalString(writer, "small_text", presence->smallImageText); + + writer.EndObject(); + } + + if (presence->partyId || presence->partySize || presence->partyMax) { + WriteKey(writer, "party"); + writer.StartObject(); + + WriteOptionalString(writer, "id", presence->partyId); + if (presence->partySize) { + writer.StartArray(); + + writer.Int(presence->partySize); + if (0 < presence->partyMax) { + writer.Int(presence->partyMax); + } + + writer.EndArray(); + } + + writer.EndObject(); + } + + if (presence->matchSecret || presence->joinSecret || presence->spectateSecret) { + WriteKey(writer, "secrets"); + writer.StartObject(); + + WriteOptionalString(writer, "match", presence->matchSecret); + WriteOptionalString(writer, "join", presence->joinSecret); + WriteOptionalString(writer, "spectate", presence->spectateSecret); + + writer.EndObject(); + } + + writer.Key("instance"); + writer.Bool(presence->instance != 0); + + writer.EndObject(); // activity + + writer.EndObject(); // args + + writer.EndObject(); // top level + + return sb.GetSize(); +} + +size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId) +{ + DirectStringBuffer sb(dest, maxLen); + WriterAllocator wa; + JsonWriter writer(sb, &wa, WriterNestingLevels); + + writer.StartObject(); + WriteKey(writer, "v"); + writer.Int(version); + WriteKey(writer, "client_id"); + writer.String(applicationId); + writer.EndObject(); + + return sb.GetSize(); +} + diff --git a/src/serialization.h b/src/serialization.h new file mode 100644 index 0000000..73d0bf9 --- /dev/null +++ b/src/serialization.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +// if only there was a standard library function for this +template +inline size_t StringCopy(char (&dest)[Len], const char* src) { + if (!dest || !src || !Len) { + return 0; + } + size_t copied; + char* out = dest; + for (copied = 1; *src && copied < Len; ++copied) { + *out++ = *src++; + } + *out = 0; + return copied - 1; +} + +struct DiscordRichPresence; + +size_t JsonWriteRichPresenceObj(char* dest, size_t maxLen, const DiscordRichPresence* presence); +size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId); diff --git a/src/yolojson.h b/src/yolojson.h deleted file mode 100644 index 0dc74e0..0000000 --- a/src/yolojson.h +++ /dev/null @@ -1,207 +0,0 @@ -#pragma once - -#include "connection.h" -#include "discord-rpc.h" - -/* - This is as simple of a json writing thing as possible; does not try to keep you - from overflowing buffer, so make sure you have room. -*/ - -// if only there was a standard library function for this -inline size_t StringCopy(char* dest, const char* src, size_t maxBytes = UINT32_MAX) { - if (!dest || !src || !maxBytes) { - return 0; - } - size_t copied; - for (copied = 1; *src && copied < maxBytes; ++copied) { - *dest++ = *src++; - } - *dest = 0; - return copied - 1; -} - -inline void JsonWriteEscapedString(char*& dest, const char* src) -{ - for (char c = *src++; c; c = *src++) { - switch (c) { - case '\"': - case '\\': - *dest++ = '\\'; - *dest++ = c; - break; - case '\b': - *dest++ = '\\'; - *dest++ = 'b'; - break; - case '\f': - *dest++ = '\\'; - *dest++ = 'f'; - break; - case '\n': - *dest++ = '\\'; - *dest++ = 'n'; - break; - case '\r': - *dest++ = '\\'; - *dest++ = 'r'; - break; - case '\t': - *dest++ = '\\'; - *dest++ = 't'; - break; - default: - *dest++ = c; - break; - } - } -} - -template void JsonWriteNumber(char*& dest, T number) -{ - if (!number) { - *dest++ = '0'; - return; - } - if (number < 0) { - *dest++ = '-'; - number = -number; - } - char temp[32]; - int place = 0; - while (number) { - auto digit = number % 10; - number = number / 10; - temp[place++] = '0' + (char)digit; - } - for (--place; place >= 0; --place) { - *dest++ = temp[place]; - } - *dest = 0; -} - -inline void JsonWritePropName(char*& dest, const char* name) -{ - *dest++ = '"'; - dest += StringCopy(dest, name); - *dest++ = '"'; - *dest++ = ':'; - *dest++ = ' '; -} - -inline void JsonWritePropSep(char*& dest) -{ - *dest++ = ','; - *dest++ = ' '; -} - -inline void JsonWriteStringProp(char*& dest, const char* name, const char* value) -{ - JsonWritePropName(dest, name); - *dest++ = '"'; - JsonWriteEscapedString(dest, value); - *dest++ = '"'; - JsonWritePropSep(dest); -} - -template -void JsonWriteNumberAsStringProp(char*& dest, const char* name, T value) -{ - JsonWritePropName(dest, name); - *dest++ = '"'; - JsonWriteNumber(dest, value); - *dest++ = '"'; - JsonWritePropSep(dest); -} - -template -void JsonWriteNumberProp(char*& dest, const char* name, T value) -{ - JsonWritePropName(dest, name); - JsonWriteNumber(dest, value); - JsonWritePropSep(dest); -} - -inline void JsonWriteBoolProp(char*& dest, const char* name, bool value) -{ - JsonWritePropName(dest, name); - dest += StringCopy(dest, value ? "true" : "false"); - JsonWritePropSep(dest); -} - -inline void JsonWriteRichPresenceObj(char*& dest, const DiscordRichPresence* presence) -{ - *dest++ = '{'; - - if (presence->state) { - JsonWriteStringProp(dest, "state", presence->state); - } - - if (presence->details) { - JsonWriteStringProp(dest, "details", presence->details); - } - - if (presence->startTimestamp) { - JsonWriteNumberAsStringProp(dest, "start_timestamp", presence->startTimestamp); - } - - if (presence->endTimestamp) { - JsonWriteNumberAsStringProp(dest, "end_timestamp", presence->endTimestamp); - } - - if (presence->largeImageKey) { - JsonWriteStringProp(dest, "large_image_key", presence->largeImageKey); - } - - if (presence->largeImageText) { - JsonWriteStringProp(dest, "large_image_text", presence->largeImageText); - } - - if (presence->smallImageKey) { - JsonWriteStringProp(dest, "small_image_key", presence->smallImageKey); - } - - if (presence->smallImageText) { - JsonWriteStringProp(dest, "small_image_text", presence->smallImageText); - } - - if (presence->partyId) { - JsonWriteStringProp(dest, "party_id", presence->partyId); - } - - if (presence->partyMax) { - JsonWriteNumberProp(dest, "party_size", presence->partySize); - JsonWriteNumberProp(dest, "party_max", presence->partyMax); - } - - if (presence->matchSecret) { - JsonWriteStringProp(dest, "match_secret", presence->matchSecret); - } - - if (presence->joinSecret) { - JsonWriteStringProp(dest, "join_secret", presence->joinSecret); - } - - if (presence->spectateSecret) { - JsonWriteStringProp(dest, "spectate_secret", presence->spectateSecret); - } - - JsonWriteBoolProp(dest, "instance", presence->instance != 0); - - dest -= 1; - *(dest - 1) = '}'; - *dest = 0; -} - -inline void JsonWriteHandshakeObj(char*& dest, int version, const char* applicationId) -{ - *dest++ = '{'; - - JsonWriteNumberProp(dest, "v", version); - JsonWriteStringProp(dest, "client_id", applicationId); - - dest -= 1; - *(dest - 1) = '}'; - *dest = 0; -} -