HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
pickle.hpp
1// Copyright 2019 Pokitec
2// All rights reserved.
3
4#pragma once
5
6#include "TTauri/Foundation/exceptions.hpp"
7#include "TTauri/Foundation/URL.hpp"
8#include "TTauri/Foundation/memory.hpp"
9#include "TTauri/Foundation/vec.hpp"
10#include <string>
11#include <vector>
12#include <map>
13#include <unordered_map>
14
15namespace tt {
16
17enum class pickle_type_t {
18 EndMark,
19 Null,
20 Boolean,
21 Integer,
22 String,
23 Object,
24 Map,
25 Vector,
26 Double,
27 Vec,
28 URL,
29 Reserved
30};
31
32constexpr uint8_t PICKLE_SMALL_NATURAL_MIN = 0x00;
33constexpr uint8_t PICKLE_SMALL_NATURAL_MAX = 0x3f;
34
35constexpr uint8_t PICKLE_SMALL_STRING_MIN = 0xc0;
36constexpr uint8_t PICKLE_SMALL_STRING_MAX = 0xdf;
37
38constexpr uint8_t PICKLE_END_MARK = 0xff;
39constexpr uint8_t PICKLE_NULL = 0xfe;
40constexpr uint8_t PICKLE_TRUE = 0xfd;
41constexpr uint8_t PICKLE_FALSE = 0xfc;
42constexpr uint8_t PICKLE_STRING = 0xfb;
43constexpr uint8_t PICKLE_OBJECT = 0xfa;
44constexpr uint8_t PICKLE_MAP = 0xf9;
45constexpr uint8_t PICKLE_VECTOR = 0xf8;
46constexpr uint8_t PICKLE_DOUBLE = 0xf7;
47constexpr uint8_t PICKLE_VEC = 0xf6;
48constexpr uint8_t PICKLE_URL = 0xf5;
49
50constexpr uint8_t PICKLE_RESERVED_MIN = 0x40;
51constexpr uint8_t PICKLE_RESERVED_MAX = 0xf4;
52
53template<typename Iter>
54inline pickle_type_t pickle_type(Iter &i, Iter const &end)
55{
56 if (i == end) {
57 TTAURI_THROW(parse_error("End of stream"));
58 }
59
60 switch (ttlet c_ = static_cast<uint8_t>(*i)) {
61 case PICKLE_END_MARK: return pickle_type_t::EndMark;
62 case PICKLE_NULL: return pickle_type_t::Null;
63 case PICKLE_TRUE: return pickle_type_t::Boolean;
64 case PICKLE_FALSE: return pickle_type_t::Boolean;
65 case PICKLE_STRING: return pickle_type_t::String;
66 case PICKLE_OBJECT: return pickle_type_t::Object;
67 case PICKLE_MAP: return pickle_type_t::Map;
68 case PICKLE_VECTOR: return pickle_type_t::Vector;
69 case PICKLE_DOUBLE: return pickle_type_t::Double;
70 case PICKLE_VEC: return pickle_type_t::Vec;
71 case PICKLE_URL: return pickle_type_t::URL;
72 default:
73 if (c_ >= PICKLE_SMALL_STRING_MIN && c_ <= PICKLE_SMALL_STRING_MAX) {
74 return pickle_type_t::String;
75 } else if (c_ >= PICKLE_RESERVED_MIN && c_ <= PICKLE_RESERVED_MAX) {
76 return pickle_type_t::Reserved;
77 } else {
78 return pickle_type_t::Integer;
79 }
80 }
81}
82
83template<typename R, typename Iter>
84inline R unpickle(Iter &&i, Iter &&end)
85{
86 tt_not_implemented;
87}
88
89template<typename Iter>
90inline long long unpickle(Iter &i, Iter const &end)
91{
92 // Type conversions.
93 switch (pickle_type(i, end)) {
94 case pickle_type_t::Null:
95 i++;
96 return 0;
97
98 case pickle_type_t::Boolean:
99 return (static_cast<uint8_t>(*(i++)) == PICKLE_TRUE) ? 1 : 0;
100
101 case pickle_type_t::Double:
102 return numeric_cast<int64_t>(unpickle<double>(i, end));
103
104 case pickle_type_t::Integer:
105 goto impl;
106
107 default:
108 TTAURI_THROW(parse_error("Unexpected type in stream."));
109 }
110
111impl:
112 size_t nr_bits = 0;
113 uint64_t u64value = 0;
114 uint8_t c = 0;
115 do {
116 c = *(i++);
117
118 nr_bits += 7;
119 u64value <<= 7;
120 u64value |= (c & 0x7f);
121
122 // Continue until stop-bit is set.
123 } while (c & 0x80);
124
125 // Sign extent by left shifting the unsigned value to align to the
126 // MSB. Then converting to an signed value and shift right (which does
127 // sign extending).
128 tt_assert(nr_bits < 64);
129 size_t sign_extent_shift = 64 - nr_bits;
130 u64value <<= sign_extent_shift;
131
132 auto i64value = bit_cast<int64_t>(u64value);
133 return i64value >> sign_extent_shift;
134}
135
136template<typename Iter>
137inline unsigned long long unpickle(Iter &i, Iter const &end)
138{
139 // Type conversions.
140 switch (pickle_type(i, end)) {
141 case pickle_type_t::Null:
142 i++;
143 return 0;
144
145 case pickle_type_t::Boolean:
146 return (static_cast<uint8_t>(*(i++)) == PICKLE_TRUE) ? 1 : 0;
147
148 case pickle_type_t::Double:
149 return numeric_cast<uint64_t>(unpickle<double>(i, end));
150
151 case pickle_type_t::Integer:
152 goto impl;
153
154 default:
155 TTAURI_THROW(parse_error("Unexpected type in stream."));
156 }
157
158impl:
159 size_t nr_bits = 0;
160 uint64_t u64value = 0;
161 uint8_t c = 0;
162 do {
163 c = *(i++);
164
165 nr_bits += 7;
166 u64value <<= 7;
167 u64value |= (c & 0x7f);
168
169 // Continue until stop-bit is set.
170 } while (c & 0x80);
171 return u64value;
172}
173
174template<typename Iter>
175inline double unpickle(Iter &i, Iter const &end)
176{
177 // Type conversions.
178 switch (pickle_type(i, end)) {
179 case pickle_type_t::Null:
180 i++;
181 return 0.0;
182
183 case pickle_type_t::Boolean:
184 return (static_cast<uint8_t>(*(i++)) == PICKLE_TRUE) ? 1.0 : 0.0;
185
186 case pickle_type_t::Double:
187 goto impl;
188
189 case pickle_type_t::Integer:
190 return numeric_cast<double>(unpickle<int64_t>(i, end));
191
192 default:
193 TTAURI_THROW(parse_error("Unexpected type in stream."));
194 }
195
196impl:
197 i++;
198
199 uint64_t u64value = 0;
200 if ((end - i) < sizeof(u64value)) {
201 TTAURI_THROW(parse_error("End of stream"));
202 }
203
204 for (size_t j = 0; j < sizeof(u64value); j++) {
205 u64value <<= 8;
206 u64value |= static_cast<uint8_t>(*(i++));
207 }
208
209 return bit_cast<double>(u64value);
210}
211
212template<typename Iter> inline unsigned long unpickle(Iter &i, Iter const &end) { return numeric_cast<unsigned long>(unpickle<unsigned long long>(i, end)); }
213template<typename Iter> inline unsigned int unpickle(Iter &i, Iter const &end) { return numeric_cast<unsigned int>(unpickle<unsigned long long>(i, end)); }
214template<typename Iter> inline unsigned short unpickle(Iter &i, Iter const &end) { return numeric_cast<unsigned short>(unpickle<unsigned long long>(i, end)); }
215template<typename Iter> inline unsigned char unpickle(Iter &i, Iter const &end) { return numeric_cast<unsigned char>(unpickle<unsigned long long>(i, end)); }
216
217template<typename Iter> inline signed long unpickle(Iter &i, Iter const &end) { return numeric_cast<signed long>(unpickle<signed long long>(i, end)); }
218template<typename Iter> inline signed int unpickle(Iter &i, Iter const &end) { return numeric_cast<signed int>(unpickle<signed long long>(i, end)); }
219template<typename Iter> inline signed short unpickle(Iter &i, Iter const &end) { return numeric_cast<signed short>(unpickle<signed long long>(i, end)); }
220template<typename Iter> inline signed char unpickle(Iter &i, Iter const &end) { return numeric_cast<signed char>(unpickle<signed long long>(i, end)); }
221
222template<typename Iter> inline float unpickle(Iter &i, Iter const &end) { return numeric_cast<float>(unpickle<double>(i, end)); }
223
224template<typename Iter>
225inline std::string unpickle(Iter &i, Iter const &end)
226{
227 // Type conversions.
228 switch (pickle_type(i, end)) {
229 case pickle_type_t::String:
230 goto impl;
231 case pickle_type_t::URL:
232 goto impl;
233 default:
234 TTAURI_THROW(parse_error("Unexpected type in stream."));
235 }
236
237impl:
238 size_t stringLength = 0;
239 ttlet c = static_cast<uint8_t>(*i);
240 if (c == PICKLE_STRING || c == PICKLE_URL) {
241 i++;
242 stringLength = unpickle<size_t>(i, end);
243 } else {
244 stringLength = c - PICKLE_SMALL_STRING_MIN;
245 }
246
247 ttlet beginOfString = i;
248 ttlet endOfString = beginOfString + stringLength;
249 if (end - i < stringLength) {
250 TTAURI_THROW(parse_error("End of stream"));
251 }
252
253 i = endOfString;
254 return {beginOfString, endOfString};
255}
256
257template<typename Iter>
258inline URL unpickle(Iter &i, Iter const &end)
259{
260 return URL{unpickle<std::string>(i, end)};
261}
262
263template<typename Iter>
264inline bool unpickle(Iter &i, Iter const &end)
265{
266 switch (pickle_type(i, end)) {
267 case pickle_type_t::Null:
268 i++;
269 return false;
270
271 case pickle_type_t::Boolean:
272 return static_cast<uint8_t>(*(i++)) == PICKLE_TRUE;
273
274 case pickle_type_t::Double:
275 return unpickle<double>(i, end) > 0.0;
276
277 case pickle_type_t::Integer:
278 return unpickle<int64_t>(i, end) > 0;
279
280 default:
281 TTAURI_THROW(parse_error("Unexpected type in stream."));
282 }
283}
284
285template<typename T, typename Iter>
286inline std::vector<T> unpickle(Iter &i, Iter const &end)
287{
288 switch (pickle_type(i, end)) {
289 case pickle_type_t::Vector: {
290 i++; // Skip over vector-opcode.
291
293 while (pickle_type(i, end) != pickle_type_t::EndMark) {
294 r.push_back(unpickle<T>(i, end));
295 }
296
297 i++; // Skip over end-mark.
298 return r;
299 }
300
301 default:
302 TTAURI_THROW(parse_error("Unexpected type in stream."));
303 }
304}
305
306template<typename K, typename T, typename Iter>
307inline std::map<K,T> unpickle(Iter &i, Iter const &end)
308{
309 switch (pickle_type(i, end)) {
310 case pickle_type_t::Vector: {
311 i++; // Skip over vector-opcode.
312
314 while (pickle_type(i, end) != pickle_type_t::EndMark) {
315 r.emplace(unpickle<K>(i, end), unpickle<T>(i, end));
316 }
317
318 i++; // Skip over end-mark.
319 return r;
320 }
321
322 default:
323 TTAURI_THROW(parse_error("Unexpected type in stream."));
324 }
325}
326
327template<typename K, typename T, typename Iter>
328inline std::unordered_map<K,T> unpickle(Iter &i, Iter const &end)
329{
330 switch (pickle_type(i, end)) {
331 case pickle_type_t::Vector: {
332 i++; // Skip over vector-opcode.
333
335 while (pickle_type(i, end) != pickle_type_t::EndMark) {
336 r.emplace(unpickle<K>(i, end), unpickle<T>(i, end));
337 }
338
339 i++; // Skip over end-mark.
340 return r;
341 }
342
343 default:
344 TTAURI_THROW(parse_error("Unexpected type in stream."));
345 }
346}
347
348template<typename R>
349inline R unpickle(std::string const &stream)
350{
351 return unpickle<R>(stream.begin(), stream.end());
352}
353
354inline void pickleAppend(std::string &lhs, bool rhs) noexcept
355{
356 lhs.push_back(rhs ? PICKLE_TRUE : PICKLE_FALSE);
357}
358
359inline void pickleAppend(std::string &lhs, std::nullptr_t rhs) noexcept
360{
361 lhs.push_back(PICKLE_NULL);
362}
363
364inline void pickleAppend(std::string &lhs, double rhs) noexcept
365{
366 lhs.push_back(PICKLE_DOUBLE);
367
368 auto u64rhs = bit_cast<uint64_t>(rhs);
369 for (size_t i = 0; i < sizeof(u64rhs); i++) {
370 lhs.push_back(static_cast<char>(u64rhs & 0xff));
371 u64rhs >>= 8;
372 }
373}
374
375inline void pickleAppend(std::string &lhs, unsigned long long rhs) noexcept
376{
377 while (true) {
378 uint8_t const last_value = rhs & 0x7f;
379 rhs >>= 7;
380
381 if (rhs == 0 && last_value < 0x40) {
382 // rhs is fully shifted in, and the sign-bit is clear.
383 // Add a stop bit to mark the last byte.
384 lhs.push_back(last_value | 0x80);
385 return;
386 } else {
387 lhs.push_back(last_value);
388 }
389 }
390}
391
398inline void pickleAppend(std::string &lhs, signed long long rhs) noexcept
399{
400 if (rhs >= 0) {
401 return pickleAppend(lhs, static_cast<uint64_t>(rhs));
402 }
403
404 lhs.push_back(rhs & 0x7f);
405 rhs >>= 7;
406
407 while (true) {
408 uint8_t const last_value = rhs & 0x7f;
409 rhs >>= 7;
410
411 if (rhs == -1 && last_value >= 0x40) {
412 // rhs is fully shifted in, and the sign-bit is set.
413 // Add a stop bit to mark the last byte.
414 lhs.push_back(last_value | 0x80);
415 return;
416 } else {
417 lhs.push_back(last_value);
418 }
419 }
420}
421
422inline void pickleAppend(std::string &lhs, unsigned long rhs) noexcept { return pickleAppend(lhs, static_cast<unsigned long long>(rhs)); }
423inline void pickleAppend(std::string &lhs, unsigned int rhs) noexcept { return pickleAppend(lhs, static_cast<unsigned long long>(rhs)); }
424inline void pickleAppend(std::string &lhs, unsigned short rhs) noexcept { return pickleAppend(lhs, static_cast<unsigned long long>(rhs)); }
425inline void pickleAppend(std::string &lhs, unsigned char rhs) noexcept { return pickleAppend(lhs, static_cast<unsigned long long>(rhs)); }
426
427inline void pickleAppend(std::string &lhs, signed long rhs) noexcept { return pickleAppend(lhs, static_cast<signed long long>(rhs)); }
428inline void pickleAppend(std::string &lhs, signed int rhs) noexcept { return pickleAppend(lhs, static_cast<signed long long>(rhs)); }
429inline void pickleAppend(std::string &lhs, signed short rhs) noexcept { return pickleAppend(lhs, static_cast<signed long long>(rhs)); }
430inline void pickleAppend(std::string &lhs, signed char rhs) noexcept { return pickleAppend(lhs, static_cast<signed long long>(rhs)); }
431
432inline void pickleAppend(std::string &lhs, void *rhs) noexcept { return pickleAppend(lhs, reinterpret_cast<size_t>(rhs)); }
433
434inline void pickleAppend(std::string &lhs, URL const &rhs) noexcept
435{
436 auto s = to_string(rhs);
437
438 lhs.push_back(PICKLE_URL);
439 pickleAppend(lhs, s.size());
440 lhs += s;
441}
442
445inline void pickleAppend(std::string &lhs, std::string_view const &rhs) noexcept
446{
447 if (rhs.size() <= 0x1f) {
448 lhs.push_back(static_cast<uint8_t>(rhs.size()) | PICKLE_SMALL_STRING_MIN);
449 } else {
450 lhs.push_back(PICKLE_STRING);
451 pickleAppend(lhs, rhs.size());
452 }
453
454 lhs += rhs;
455}
456
457inline void pickleAppend(std::string &lhs, char const rhs[]) noexcept
458{
459 return pickleAppend(lhs, std::string_view(rhs));
460}
461
462inline void pickleAppend(std::string &lhs, std::string const &rhs) noexcept
463{
464 return pickleAppend(lhs, std::string_view(rhs));
465}
466
467inline void pickleAppend(std::string &lhs, vec const &rhs) noexcept
468{
469 lhs.push_back(PICKLE_VEC);
470
471 pickleAppend(lhs, rhs.x());
472 pickleAppend(lhs, rhs.y());
473 if (rhs.z() != 0.0 || rhs.w() != 0.0) {
474 pickleAppend(lhs, rhs.z());
475 }
476 if (rhs.w() != 0.0) {
477 pickleAppend(lhs, rhs.w());
478 }
479
480 lhs.push_back(PICKLE_END_MARK);
481}
482
483template<typename T>
484inline void pickleAppend(std::string &lhs, std::vector<T> const &rhs) noexcept
485{
486 lhs.push_back(PICKLE_VECTOR);
487
488 for (ttlet &item: rhs) {
489 pickleAppend(lhs, item);
490 }
491
492 lhs.push_back(PICKLE_END_MARK);
493}
494
495template<typename K, typename V>
496inline void pickleAppend(std::string &lhs, std::map<K,V> const &rhs) noexcept
497{
498 lhs.push_back(PICKLE_MAP);
499
500 for (ttlet &item: rhs) {
501 pickleAppend(lhs, item->first);
502 pickleAppend(lhs, item->second);
503 }
504
505 lhs.push_back(PICKLE_END_MARK);
506}
507
508template<typename K, typename V>
509inline void pickleAppend(std::string &lhs, std::unordered_map<K,V> const &rhs) noexcept
510{
511 lhs.push_back(PICKLE_MAP);
512
513 for (ttlet &item: rhs) {
514 pickleAppend(lhs, item->first);
515 pickleAppend(lhs, item->second);
516 }
517
518 lhs.push_back(PICKLE_END_MARK);
519}
520
521template<typename T, typename U, typename... Args>
522inline void pickleAppend(std::string& dst, T&& firstArg, U&& secondArg, Args&&... args) noexcept
523{
524 pickleAppend(dst, firstArg);
525 pickleAppend(dst, secondArg);
526
527 if constexpr (sizeof...(args) > 0) {
528 pickleAppend(dst, args...);
529 }
530}
531
532template<typename... Args>
533inline void clearAndPickleAppend(std::string &dst, Args&&... args) noexcept
534{
535 dst.clear();
536 pickleAppend(dst, args...);
537}
538
539template<typename... Args>
540[[nodiscard]] inline std::string pickle(Args&&... args) noexcept
541{
542 auto dst = std::string{};
543 pickleAppend(dst, args...);
544 return dst;
545}
546
547
548};
Definition range_map.hpp:119
T begin(T... args)
T emplace(T... args)
T end(T... args)
T push_back(T... args)
T to_string(T... args)