Let's use rapidjson instead of roll-your-own json. Added helpers to keep allocations minimized.
This commit is contained in:
parent
12054246a2
commit
79d70b8bae
6 changed files with 234 additions and 224 deletions
|
@ -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)
|
||||
|
|
|
@ -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 <atomic>
|
||||
#include <chrono>
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "rpc_connection.h"
|
||||
#include "yolojson.h"
|
||||
#include "serialization.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
|
@ -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:
|
||||
|
|
202
src/serialization.cpp
Normal file
202
src/serialization.cpp
Normal file
|
@ -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<int Size>
|
||||
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<char>;
|
||||
// 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<DirectStringBuffer, Encoding, Encoding, WriterAllocator, rapidjson::kWriteNoFlags>;
|
||||
|
||||
// it's ever so slightly faster to not have to strlen the key
|
||||
template<typename T>
|
||||
void WriteKey(JsonWriter& w, T& k) {
|
||||
w.Key(k, sizeof(T) - 1);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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();
|
||||
}
|
||||
|
23
src/serialization.h
Normal file
23
src/serialization.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// if only there was a standard library function for this
|
||||
template<size_t Len>
|
||||
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);
|
207
src/yolojson.h
207
src/yolojson.h
|
@ -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<typename T> 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<typename T>
|
||||
void JsonWriteNumberAsStringProp(char*& dest, const char* name, T value)
|
||||
{
|
||||
JsonWritePropName(dest, name);
|
||||
*dest++ = '"';
|
||||
JsonWriteNumber(dest, value);
|
||||
*dest++ = '"';
|
||||
JsonWritePropSep(dest);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in a new issue