302 lines
9.4 KiB
C++
302 lines
9.4 KiB
C++
#pragma once
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// IMPORTANT: This is a header-only library that is included
|
|
// by the graphics engine as well. It cannot contain references
|
|
// to anything else in the engine.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cassert>
|
|
#include <string_view>
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// SimpleDynamic
|
|
//
|
|
// A struct that holds a dynamically typed value.
|
|
// This can hold a string, number, vector, or boolean.
|
|
//
|
|
// The type is stored in the 'type' field.
|
|
//
|
|
// If it's a STRING, the value is in the field s
|
|
// If it's a NUMBER, the value is in the field x
|
|
// If it's a BOOLEAN, it's true if (x==1.0)
|
|
// If it's a VECTOR, the value is in x,y,z
|
|
//
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
enum class SimpleDynamicTag {
|
|
UNINITIALIZED,
|
|
STRING,
|
|
NUMBER,
|
|
BOOLEAN,
|
|
VECTOR,
|
|
};
|
|
|
|
template<class STRING>
|
|
struct SimpleDynamic {
|
|
using string = STRING;
|
|
SimpleDynamicTag type;
|
|
double x, y, z;
|
|
string s;
|
|
|
|
SimpleDynamic() {
|
|
type = SimpleDynamicTag::UNINITIALIZED;
|
|
x=y=z=0;
|
|
}
|
|
|
|
static const char *type_name_of(SimpleDynamicTag t) {
|
|
switch (t) {
|
|
case SimpleDynamicTag::UNINITIALIZED: return "uninitialized";
|
|
case SimpleDynamicTag::BOOLEAN: return "boolean";
|
|
case SimpleDynamicTag::NUMBER: return "number";
|
|
case SimpleDynamicTag::STRING: return "string";
|
|
case SimpleDynamicTag::VECTOR: return "vector";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
const char *type_name() const {
|
|
return type_name_of(type);
|
|
}
|
|
|
|
void set_uninitialized() {
|
|
type=SimpleDynamicTag::UNINITIALIZED; s.clear(); x=y=z=0;
|
|
}
|
|
|
|
void set_string(std::string_view is) {
|
|
type=SimpleDynamicTag::STRING; s=is; x=y=z=0;
|
|
}
|
|
|
|
void set_number(double n) {
|
|
type = SimpleDynamicTag::NUMBER; s.clear(); x=n; y=z=0;
|
|
}
|
|
|
|
void set_boolean(bool b) {
|
|
type = SimpleDynamicTag::BOOLEAN; s.clear(); x=(b?1:0); y=z=0;
|
|
}
|
|
|
|
void set_vector(double ix, double iy, double iz) {
|
|
type = SimpleDynamicTag::VECTOR; s.clear(); x=ix; y=iy; z=iz;
|
|
}
|
|
|
|
void copy_value(const SimpleDynamic &other) {
|
|
type = other.type;
|
|
s=other.s; x=other.x; y=other.y; z=other.z;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// BaseWriter
|
|
//
|
|
// This base class provides the following methods:
|
|
//
|
|
// void write_uint8(uint64_t data)
|
|
// void write_uint16(uint64_t data)
|
|
// void write_uint32(uint64_t data)
|
|
// void write_uint64(uint64_t data)
|
|
// void write_int8(int64_t data)
|
|
// void write_int16(int64_t data)
|
|
// void write_int32(int64_t data)
|
|
// void write_int64(int64_t data)
|
|
// void write_char(char c)
|
|
// void write_float(float data)
|
|
// void write_double(double data)
|
|
// void write_length(size_t data)
|
|
// void write_string(std::string_view data)
|
|
// void write_simple_dynamic(const SimpleDynamic &sd);
|
|
//
|
|
// You should derive from BaseWriter using the CRTP pattern:
|
|
//
|
|
// class DerivedWriter : public BaseWriter<DerivedWriter>
|
|
//
|
|
// You must provide two methods in the derived class:
|
|
//
|
|
// write_bytes(const char *n, size_t size)
|
|
// raise_truncated()
|
|
//
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
template<class Derived>
|
|
class BaseWriter {
|
|
protected:
|
|
template<class T>
|
|
void write_value_core(T arg) {
|
|
static_cast<Derived*>(this)->write_bytes((const char *)&arg, sizeof(arg));
|
|
}
|
|
|
|
template<class T, class XT>
|
|
void write_int_core(XT arg) {
|
|
T reduced = arg;
|
|
if (XT(reduced) != arg) static_cast<Derived*>(this)->raise_truncated();
|
|
write_value_core(reduced);
|
|
}
|
|
|
|
public:
|
|
|
|
void write_uint8(uint64_t data) { write_int_core<uint8_t, uint64_t>(data); }
|
|
void write_uint16(uint64_t data) { write_int_core<uint16_t, uint64_t>(data); }
|
|
void write_uint32(uint64_t data) { write_int_core<uint32_t, uint64_t>(data); }
|
|
void write_uint64(uint64_t data) { write_int_core<uint64_t, uint64_t>(data); }
|
|
void write_int8(int64_t data) { write_int_core<int8_t, int64_t>(data); }
|
|
void write_int16(int64_t data) { write_int_core<int16_t, int64_t>(data); }
|
|
void write_int32(int64_t data) { write_int_core<int32_t, int64_t>(data); }
|
|
void write_int64(int64_t data) { write_int_core<int64_t, int64_t>(data); }
|
|
|
|
void write_bool(bool b) { write_uint8(b ? 1:0); }
|
|
void write_char(char c) { write_value_core(c); }
|
|
void write_float(float arg) { write_value_core(arg); }
|
|
void write_double(double arg) { write_value_core(arg); }
|
|
|
|
void write_length(size_t len) {
|
|
if (len >= 255) {
|
|
write_uint8(0xFF);
|
|
write_uint64(len);
|
|
} else {
|
|
write_uint8(len);
|
|
}
|
|
}
|
|
|
|
void write_string(std::string_view s) {
|
|
write_length(s.size());
|
|
static_cast<Derived*>(this)->write_bytes(s.data(), s.size());
|
|
}
|
|
|
|
template<class STRING>
|
|
void write_simple_dynamic(const SimpleDynamic<STRING> &sd) {
|
|
write_uint8(uint8_t(sd.type));
|
|
switch(sd.type) {
|
|
case SimpleDynamicTag::NUMBER: write_double(sd.x); break;
|
|
case SimpleDynamicTag::BOOLEAN: write_bool(sd.x == 1.0); break;
|
|
case SimpleDynamicTag::VECTOR: write_double(sd.x); write_double(sd.y); write_double(sd.z); break;
|
|
case SimpleDynamicTag::STRING: write_string(sd.s); break;
|
|
default: assert(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// BaseReader
|
|
//
|
|
// This base class provides the following methods:
|
|
//
|
|
// uint8_t read_uint8();
|
|
// uint16_t read_uint16();
|
|
// uint32_t read_uint32();
|
|
// uint64_t read_uint64();
|
|
// int8_t read_int8();
|
|
// int16_t read_int16();
|
|
// int32_t read_int32();
|
|
// int64_t read_int64();
|
|
// bool read_bool();
|
|
// char read_char();
|
|
// float read_float();
|
|
// double read_double();
|
|
// size_t read_length();
|
|
// String read_string_limit(uint64_t size);
|
|
// String read_string();
|
|
// SimpleDynamic read_simple_dynamic();
|
|
//
|
|
// You should derive from BaseReader using the CRTP pattern:
|
|
//
|
|
// class DerivedReader : public BaseReader<DerivedReader>
|
|
//
|
|
// The derived class must provide:
|
|
//
|
|
// using read_string_type = std::string; // or compatible
|
|
// void read_bytes_into(char *n, size_t size)
|
|
// void handle_string_too_long();
|
|
//
|
|
// Error Handling:
|
|
//
|
|
// It is up to the derived class whether it wants
|
|
// to report errors using exceptions or flags.
|
|
//
|
|
// If read_bytes_into discovers there's not enough bytes,
|
|
// there are two valid options: throw an exception, OR,
|
|
// set an error flag and fill the buffer with zeros.
|
|
//
|
|
// If read_string discovers that the string is longer than
|
|
// the allowed limit, it will call handle_string_too_long.
|
|
// This function may either throw an exception, or set an
|
|
// error flag.
|
|
//
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
template<class Derived>
|
|
class BaseReader {
|
|
protected:
|
|
template<class T>
|
|
T read_value_core() {
|
|
T result;
|
|
Derived *dthis = static_cast<Derived*>(this);
|
|
dthis->read_bytes_into((char *)(&result), sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
public:
|
|
|
|
uint8_t read_uint8() { return read_value_core<uint8_t>(); }
|
|
uint16_t read_uint16() { return read_value_core<uint16_t>(); }
|
|
uint32_t read_uint32() { return read_value_core<uint32_t>(); }
|
|
uint64_t read_uint64() { return read_value_core<uint64_t>(); }
|
|
int8_t read_int8() { return read_value_core<int8_t>(); }
|
|
int16_t read_int16() { return read_value_core<int16_t>(); }
|
|
int32_t read_int32() { return read_value_core<int32_t>(); }
|
|
int64_t read_int64() { return read_value_core<int64_t>(); }
|
|
|
|
bool read_bool() { return (bool)read_uint8(); }
|
|
char read_char() { return read_value_core<char>(); }
|
|
float read_float() { return read_value_core<float>(); }
|
|
double read_double() { return read_value_core<double>(); }
|
|
|
|
size_t read_length() {
|
|
uint64_t len = read_uint8();
|
|
if (len == 255) {
|
|
len = read_uint64();
|
|
}
|
|
return len;
|
|
}
|
|
|
|
auto read_string_limit(uint64_t limit) {
|
|
size_t len = read_length();
|
|
Derived *dthis = static_cast<Derived*>(this);
|
|
if (len > limit) {
|
|
dthis->raise_string_too_long();
|
|
len = 0;
|
|
}
|
|
typename Derived::read_string_type result(len, ' ');
|
|
dthis->read_bytes_into(&(result[0]), len);
|
|
return result;
|
|
}
|
|
|
|
auto read_string() { return read_string_limit(0x1000000); } // 16MB limit default
|
|
|
|
template<class STRING>
|
|
void read_simple_dynamic(SimpleDynamic<STRING> *result) {
|
|
SimpleDynamicTag type = SimpleDynamicTag(read_uint8());
|
|
switch (type) {
|
|
case SimpleDynamicTag::NUMBER: result->set_number(read_double()); break;
|
|
case SimpleDynamicTag::BOOLEAN: result->set_boolean(read_bool()); break;
|
|
case SimpleDynamicTag::VECTOR: {
|
|
double x=read_double();
|
|
double y=read_double();
|
|
double z=read_double();
|
|
result->set_vector(x,y,z);
|
|
break;
|
|
}
|
|
case SimpleDynamicTag::STRING: result->set_string(read_string()); break;
|
|
default: assert(false);
|
|
}
|
|
}
|
|
};
|
|
|