#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 #include #include #include #include /////////////////////////////////////////////////////////////// // // 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, }; struct SimpleDynamic { SimpleDynamicTag type; double x, y, z; std::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_string(std::string_view is) { type=SimpleDynamicTag::STRING; s=is; x=0; y=0; z=0; } void set_number(double n) { type = SimpleDynamicTag::NUMBER; s.clear(); x=n; y=0; z=0; } void set_boolean(bool b) { type = SimpleDynamicTag::BOOLEAN; s.clear(); x=(b?1:0); y=0; 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 // // You must provide two methods in the derived class: // // write_bytes(const char *n, size_t size) // raise_truncated() // /////////////////////////////////////////////////////////////// template class BaseWriter { protected: template void write_value_core(T arg) { static_cast(this)->write_bytes((const char *)&arg, sizeof(arg)); } template void write_int_core(XT arg) { T reduced = arg; if (XT(reduced) != arg) static_cast(this)->raise_truncated(); write_value_core(reduced); } public: void write_uint8(uint64_t data) { write_int_core(data); } void write_uint16(uint64_t data) { write_int_core(data); } void write_uint32(uint64_t data) { write_int_core(data); } void write_uint64(uint64_t data) { write_int_core(data); } void write_int8(int64_t data) { write_int_core(data); } void write_int16(int64_t data) { write_int_core(data); } void write_int32(int64_t data) { write_int_core(data); } void write_int64(int64_t data) { write_int_core(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(this)->write_bytes(s.data(), s.size()); } void write_simple_dynamic(const SimpleDynamic &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 // // 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 BaseReader { protected: template T read_value_core() { T result; Derived *dthis = static_cast(this); dthis->read_bytes_into((char *)(&result), sizeof(result)); return result; } public: uint8_t read_uint8() { return read_value_core(); } uint16_t read_uint16() { return read_value_core(); } uint32_t read_uint32() { return read_value_core(); } uint64_t read_uint64() { return read_value_core(); } int8_t read_int8() { return read_value_core(); } int16_t read_int16() { return read_value_core(); } int32_t read_int32() { return read_value_core(); } int64_t read_int64() { return read_value_core(); } bool read_bool() { return (bool)read_uint8(); } char read_char() { return read_value_core(); } float read_float() { return read_value_core(); } double read_double() { return read_value_core(); } 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(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 void read_simple_dynamic(SimpleDynamic *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); } } };