Let's use rapidjson instead of roll-your-own json. Added helpers to keep allocations minimized.

This commit is contained in:
Chris Marsh 2017-07-20 13:24:18 -07:00
parent 12054246a2
commit 79d70b8bae
6 changed files with 234 additions and 224 deletions

View file

@ -1,6 +1,6 @@
include_directories(${PROJECT_SOURCE_DIR}/include) 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) if(WIN32)
add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win.cpp) add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win.cpp)

View file

@ -1,10 +1,8 @@
#include "discord-rpc.h" #include "discord-rpc.h"
#include "rpc_connection.h"
#include "yolojson.h"
#include "backoff.h" #include "backoff.h"
#include "rpc_connection.h"
#include "rapidjson/document.h" #include "serialization.h"
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
@ -126,7 +124,7 @@ extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandle
}; };
Connection->onDisconnect = [](int err, const char* message) { Connection->onDisconnect = [](int err, const char* message) {
LastErrorCode = err; LastErrorCode = err;
StringCopy(LastErrorMessage, message, sizeof(LastErrorMessage)); StringCopy(LastErrorMessage, message);
WasJustDisconnected.exchange(true); WasJustDisconnected.exchange(true);
UpdateReconnectTime(); UpdateReconnectTime();
}; };
@ -155,9 +153,7 @@ extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence)
{ {
auto qmessage = SendQueueGetNextAddMessage(); auto qmessage = SendQueueGetNextAddMessage();
if (qmessage) { if (qmessage) {
char* jsonWrite = qmessage->buffer; qmessage->length = JsonWriteRichPresenceObj(qmessage->buffer, sizeof(qmessage->buffer), presence);
JsonWriteRichPresenceObj(jsonWrite, presence);
qmessage->length = jsonWrite - qmessage->buffer;
SendQueueCommitMessage(); SendQueueCommitMessage();
SignalIOActivity(); SignalIOActivity();
} }

View file

@ -1,5 +1,5 @@
#include "rpc_connection.h" #include "rpc_connection.h"
#include "yolojson.h" #include "serialization.h"
#include <atomic> #include <atomic>
@ -9,7 +9,7 @@ static RpcConnection Instance;
/*static*/ RpcConnection* RpcConnection::Create(const char* applicationId) /*static*/ RpcConnection* RpcConnection::Create(const char* applicationId)
{ {
Instance.connection = BaseConnection::Create(); Instance.connection = BaseConnection::Create();
StringCopy(Instance.appId, applicationId, sizeof(Instance.appId)); StringCopy(Instance.appId, applicationId);
return &Instance; return &Instance;
} }
@ -35,9 +35,7 @@ void RpcConnection::Open()
} }
sendFrame.opcode = Opcode::Handshake; sendFrame.opcode = Opcode::Handshake;
char* json = sendFrame.message; sendFrame.length = JsonWriteHandshakeObj(sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId);
JsonWriteHandshakeObj(json, RpcVersion, appId);
sendFrame.length = json - sendFrame.message;
if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) { if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) {
state = State::Connected; state = State::Connected;
@ -97,7 +95,7 @@ bool RpcConnection::Read(rapidjson::Document& message)
message.ParseInsitu(readFrame.message); message.ParseInsitu(readFrame.message);
lastErrorCode = message["code"].GetInt(); lastErrorCode = message["code"].GetInt();
const auto& m = message["message"]; const auto& m = message["message"];
StringCopy(lastErrorMessage, m.GetString(), sizeof(lastErrorMessage)); StringCopy(lastErrorMessage, m.GetString());
Close(); Close();
return false; return false;
} }
@ -105,13 +103,11 @@ bool RpcConnection::Read(rapidjson::Document& message)
message.ParseInsitu(readFrame.message); message.ParseInsitu(readFrame.message);
return true; return true;
case Opcode::Ping: case Opcode::Ping:
{
readFrame.opcode = Opcode::Pong; readFrame.opcode = Opcode::Pong;
if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) { if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) {
Close(); Close();
} }
break; break;
}
case Opcode::Pong: case Opcode::Pong:
break; break;
default: default:

202
src/serialization.cpp Normal file
View 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
View 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);

View file

@ -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;
}