serialization.h
1 #pragma once 2 3 #include <stdint.h> 4 5 #ifndef __MINGW32__ 6 #pragma warning(push) 7 8 #pragma warning(disable : 4061) // enum is not explicitly handled by a case label 9 #pragma warning(disable : 4365) // signed/unsigned mismatch 10 #pragma warning(disable : 4464) // relative include path contains 11 #pragma warning(disable : 4668) // is not defined as a preprocessor macro 12 #pragma warning(disable : 6313) // Incorrect operator 13 #endif // __MINGW32__ 14 15 #include "rapidjson/document.h" 16 #include "rapidjson/stringbuffer.h" 17 #include "rapidjson/writer.h" 18 19 #ifndef __MINGW32__ 20 #pragma warning(pop) 21 #endif // __MINGW32__ 22 23 // if only there was a standard library function for this 24 template <size_t Len> 25 inline size_t StringCopy(char (&dest)[Len], const char* src) 26 { 27 if (!src || !Len) { 28 return 0; 29 } 30 size_t copied; 31 char* out = dest; 32 for (copied = 1; *src && copied < Len; ++copied) { 33 *out++ = *src++; 34 } 35 *out = 0; 36 return copied - 1; 37 } 38 39 size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId); 40 41 // Commands 42 struct DiscordRichPresence; 43 size_t JsonWriteRichPresenceObj(char* dest, 44 size_t maxLen, 45 int nonce, 46 int pid, 47 const DiscordRichPresence* presence); 48 size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); 49 50 size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); 51 52 size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce); 53 54 // I want to use as few allocations as I can get away with, and to do that with RapidJson, you need 55 // to supply some of your own allocators for stuff rather than use the defaults 56 57 class LinearAllocator { 58 public: 59 char* buffer_; 60 char* end_; 61 LinearAllocator() 62 { 63 assert(0); // needed for some default case in rapidjson, should not use 64 } 65 LinearAllocator(char* buffer, size_t size) 66 : buffer_(buffer) 67 , end_(buffer + size) 68 { 69 } 70 static const bool kNeedFree = false; 71 void* Malloc(size_t size) 72 { 73 char* res = buffer_; 74 buffer_ += size; 75 if (buffer_ > end_) { 76 buffer_ = res; 77 return nullptr; 78 } 79 return res; 80 } 81 void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) 82 { 83 if (newSize == 0) { 84 return nullptr; 85 } 86 // allocate how much you need in the first place 87 assert(!originalPtr && !originalSize); 88 // unused parameter warning 89 (void)(originalPtr); 90 (void)(originalSize); 91 return Malloc(newSize); 92 } 93 static void Free(void* ptr) 94 { 95 /* shrug */ 96 (void)ptr; 97 } 98 }; 99 100 template <size_t Size> 101 class FixedLinearAllocator : public LinearAllocator { 102 public: 103 char fixedBuffer_[Size]; 104 FixedLinearAllocator() 105 : LinearAllocator(fixedBuffer_, Size) 106 { 107 } 108 static const bool kNeedFree = false; 109 }; 110 111 // wonder why this isn't a thing already, maybe I missed it 112 class DirectStringBuffer { 113 public: 114 using Ch = char; 115 char* buffer_; 116 char* end_; 117 char* current_; 118 119 DirectStringBuffer(char* buffer, size_t maxLen) 120 : buffer_(buffer) 121 , end_(buffer + maxLen) 122 , current_(buffer) 123 { 124 } 125 126 void Put(char c) 127 { 128 if (current_ < end_) { 129 *current_++ = c; 130 } 131 } 132 void Flush() {} 133 size_t GetSize() const { return (size_t)(current_ - buffer_); } 134 }; 135 136 using MallocAllocator = rapidjson::CrtAllocator; 137 using PoolAllocator = rapidjson::MemoryPoolAllocator<MallocAllocator>; 138 using UTF8 = rapidjson::UTF8<char>; 139 // Writer appears to need about 16 bytes per nested object level (with 64bit size_t) 140 using StackAllocator = FixedLinearAllocator<2048>; 141 constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t)); 142 using JsonWriterBase = 143 rapidjson::Writer<DirectStringBuffer, UTF8, UTF8, StackAllocator, rapidjson::kWriteNoFlags>; 144 class JsonWriter : public JsonWriterBase { 145 public: 146 DirectStringBuffer stringBuffer_; 147 StackAllocator stackAlloc_; 148 149 JsonWriter(char* dest, size_t maxLen) 150 : JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels) 151 , stringBuffer_(dest, maxLen) 152 , stackAlloc_() 153 { 154 } 155 156 size_t Size() const { return stringBuffer_.GetSize(); } 157 }; 158 159 using JsonDocumentBase = rapidjson::GenericDocument<UTF8, PoolAllocator, StackAllocator>; 160 class JsonDocument : public JsonDocumentBase { 161 public: 162 static const int kDefaultChunkCapacity = 32 * 1024; 163 // json parser will use this buffer first, then allocate more if needed; I seriously doubt we 164 // send any messages that would use all of this, though. 165 char parseBuffer_[32 * 1024]; 166 MallocAllocator mallocAllocator_; 167 PoolAllocator poolAllocator_; 168 StackAllocator stackAllocator_; 169 JsonDocument() 170 : JsonDocumentBase(rapidjson::kObjectType, 171 &poolAllocator_, 172 sizeof(stackAllocator_.fixedBuffer_), 173 &stackAllocator_) 174 , poolAllocator_(parseBuffer_, sizeof(parseBuffer_), kDefaultChunkCapacity, &mallocAllocator_) 175 , stackAllocator_() 176 { 177 } 178 }; 179 180 using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>; 181 182 inline JsonValue* GetObjMember(JsonValue* obj, const char* name) 183 { 184 if (obj) { 185 auto member = obj->FindMember(name); 186 if (member != obj->MemberEnd() && member->value.IsObject()) { 187 return &member->value; 188 } 189 } 190 return nullptr; 191 } 192 193 inline int GetIntMember(JsonValue* obj, const char* name, int notFoundDefault = 0) 194 { 195 if (obj) { 196 auto member = obj->FindMember(name); 197 if (member != obj->MemberEnd() && member->value.IsInt()) { 198 return member->value.GetInt(); 199 } 200 } 201 return notFoundDefault; 202 } 203 204 inline const char* GetStrMember(JsonValue* obj, 205 const char* name, 206 const char* notFoundDefault = nullptr) 207 { 208 if (obj) { 209 auto member = obj->FindMember(name); 210 if (member != obj->MemberEnd() && member->value.IsString()) { 211 return member->value.GetString(); 212 } 213 } 214 return notFoundDefault; 215 }