diff --git a/Source/Integration/StringDecoder.h b/Source/Integration/StringDecoder.h index 36ce091f..6f660a02 100644 --- a/Source/Integration/StringDecoder.h +++ b/Source/Integration/StringDecoder.h @@ -8,7 +8,7 @@ private: bool err_eof_on_read_; bool err_string_too_long_; bool err_integer_truncated_; -protected: +public: using string_type = std::string; void *basebuffer_malloc(size_t size) { return malloc(size); } void basebuffer_free(void *p) { free(p); } diff --git a/luprex/cpp/core/drivenengine.cpp b/luprex/cpp/core/drivenengine.cpp index 9dfde5ff..f4832267 100644 --- a/luprex/cpp/core/drivenengine.cpp +++ b/luprex/cpp/core/drivenengine.cpp @@ -226,14 +226,21 @@ enum DrvAction { ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// -class PlayLogfile : public BaseWriteMethods, public std::ofstream { +class PlayLogfile : public std::ofstream { using std::ofstream::ofstream; + using DS = DataSerializer; public: void write_bytes(const char *n, size_t size) { write(n, size); } void raise_integer_truncated() { fprintf(stderr, "number exceeds allowable size\n"); std::abort(); } + void write_uint8(uint64_t data) { DS(this).write_uint8(data); } + void write_uint32(uint64_t data) { DS(this).write_uint32(data); } + void write_uint64(uint64_t data) { DS(this).write_uint64(data); } + void write_int64(int64_t data) { DS(this).write_int64(data); } + void write_double(double data) { DS(this).write_double(data); } + void write_string(std::string_view s){ DS(this).write_string(s); } void write_short_string(std::string_view v) { assert(v.size() < DRV_SHORTSTRING_SIZE); write_string(v); @@ -244,10 +251,10 @@ public: } }; -class ReplayLogfile : public BaseReadMethods, public std::ifstream { +class ReplayLogfile : public std::ifstream { using std::ifstream::ifstream; + using DD = DataDeserializer; public: - using read_string_type = std::string; void read_bytes_into(char *n, size_t size) { read(n, size); if (!good()) { @@ -258,6 +265,13 @@ public: fprintf(stderr, "string in logfile is too long"); std::abort(); } + uint8_t read_uint8() { return DD(this).read_uint8(); } + uint32_t read_uint32() { return DD(this).read_uint32(); } + uint64_t read_uint64() { return DD(this).read_uint64(); } + int64_t read_int64() { return DD(this).read_int64(); } + double read_double() { return DD(this).read_double(); } + size_t read_length() { return DD(this).read_length(); } + std::string read_string() { return DD(this).read_string(); } std::string_view read_short_string(EngineWrapper *w) { size_t size = read_length(); assert(size <= DRV_SHORTSTRING_SIZE); diff --git a/luprex/cpp/core/streambuffer.hpp b/luprex/cpp/core/streambuffer.hpp index d54e183c..71271dd7 100644 --- a/luprex/cpp/core/streambuffer.hpp +++ b/luprex/cpp/core/streambuffer.hpp @@ -246,7 +246,7 @@ public: using LuaValue = BaseLuaValue; class StreamBufferConfig { -protected: +public: using string_type = eng::string; void *basebuffer_malloc(size_t size) { return eng::malloc(size); } void basebuffer_free(void *p) { eng::free(p); } diff --git a/luprex/ext/base-buffer.hpp b/luprex/ext/base-buffer.hpp index d87da073..4dfeaea2 100644 --- a/luprex/ext/base-buffer.hpp +++ b/luprex/ext/base-buffer.hpp @@ -101,60 +101,53 @@ struct BaseLuaValue { /////////////////////////////////////////////////////////////// // -// BaseWriteMethods +// DataSerializer // -// Suppose you've written a class MyWriter that contains a -// write_bytes method. Now you want to add write_int, -// write_string, and so forth. By deriving from -// BaseWriteMethods, you add all of the following methods: +// DataSerializer is an object that can serialize ints, +// strings, floats, and other basic types. It provides a +// consistent standard for the byte formats. // -// 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) +// To serialize, first construct a DataSerializer, passing +// in a pointer to an output device. An output device is +// any class that has these methods: // -// Here is how you should derive from BaseWriteMethods: +// void write_bytes(char *data, size_t len); // -// class MyWriter : public BaseWriteMethods +// void raise_integer_truncated(); // -// Your class MyWriter must implement these two methods: -// -// write_bytes(const char *n, size_t size) -// raise_integer_truncated() -// -// If you call write_uint8(1000), that is an error, because -// 1000 doesn't fit in a uint8. So, write_uint8 will call -// raise_integer_truncated. That function can throw an -// exception, set a flag, or otherwise handle the error - -// you can implement it to do whatever you wish. +// After constructing the DataSerializer, call write_int, +// write_float, write_string, or the like. The data will be +// written to the output device using write_bytes. If +// there's an error, a 'raise' method may be called on the +// output device. // +// It is intended that the compiler will optimize this +// process to such a degree that it costs no more than +// simply calling write_bytes directly on the output +// device. In other words, it is our intent that the +// use of a DataSerializer should be free. +// /////////////////////////////////////////////////////////////// -template -class BaseWriteMethods { -protected: +template +class DataSerializer { +private: + OutputDevice *output_; + template void write_value_core(T arg) { - static_cast(this)->write_bytes((const char *)&arg, sizeof(arg)); + output_->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_integer_truncated(); - write_value_core(reduced); + if (XT(reduced) != arg) output_->raise_integer_truncated(); + output_->write_bytes((const char *)&reduced, sizeof(reduced)); } public: + DataSerializer(OutputDevice *o) : output_(o) {} void write_uint8(uint64_t data) { write_int_core(data); } void write_uint16(uint64_t data) { write_int_core(data); } @@ -181,76 +174,54 @@ public: void write_string(std::string_view s) { write_length(s.size()); - static_cast(this)->write_bytes(s.data(), s.size()); + output_->write_bytes(s.data(), s.size()); } }; /////////////////////////////////////////////////////////////// // -// BaseReadMethods +// DataDeserializer // -// Suppose you've written a class MyReader that contains a -// read_bytes method. Now you want to add read_int, -// read_string, and so forth. By deriving from -// BaseReadMethods, you add all of the following methods: +// DataDeserializer is an object that can deserialize ints, +// strings, floats, and other basic types. It provides a +// consistent standard for the byte formats. // -// 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(); +// To deserialize, first construct a DataDeserializer, passing +// in a pointer to an input device. An input device is +// any class that has these methods: // -// Here is how you should derive from BaseReadMethods: +// void read_bytes_into(char *data, size_t len); +// void raise_string_too_long(); // -// class MyReader : public BaseReadMethods +// After constructing the DataDeserializer, call read_int, +// read_float, read_string, or the like. The data will be +// read from the input device using read_bytes_into. If +// there's an error, a 'raise' method may be called on the +// input device. // -// Your class MyReader must implement these: -// -// using string_type = std::string; // or compatible -// void read_bytes_into(char *n, size_t size) -// void raise_string_too_long(); -// -// The read_string function will return a string. -// Usually, you want it to return std::string, but -// you might be using some other string type. Supply -// string_type = std::string or any other type -// with a (bytes, len) constructor, and read_string -// will return that type. -// -// If you call read_string_limit(100), and this finds -// a string of length 200 in the incoming data, then -// read_string_limit will call raise_string_too_long. -// That function can throw an exception, set a flag, -// or otherwise handle the error - you can implement it -// to do whatever you wish. +// It is intended that the compiler will optimize this +// process to such a degree that it costs no more than +// simply calling read_bytes_into directly on the input +// device. In other words, it is our intent that the +// use of a DataDeserializer should be free. // /////////////////////////////////////////////////////////////// -template -class BaseReadMethods { -protected: - using string_type = typename Derived::string_type; +template +class DataDeserializer { +private: + InputDevice *input_; template T read_value_core() { T result; - Derived *dthis = static_cast(this); - dthis->read_bytes_into((char *)(&result), sizeof(result)); + input_->read_bytes_into((char *)(&result), sizeof(result)); return result; } public: + DataDeserializer(InputDevice *i) : input_(i) {} uint8_t read_uint8() { return read_value_core(); } uint16_t read_uint16() { return read_value_core(); } @@ -260,33 +231,32 @@ public: 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; } - string_type read_string_limit(uint64_t limit) { + StringType read_string_limit(uint64_t limit) { size_t len = read_length(); - Derived *dthis = static_cast(this); if (len > limit) { - dthis->raise_string_too_long(); + input_->raise_string_too_long(); len = 0; } - string_type result(len, ' '); - dthis->read_bytes_into(&(result[0]), len); + StringType result(len, ' '); + input_->read_bytes_into(&(result[0]), len); return result; } - string_type read_string() { + StringType read_string() { return read_string_limit(0x1000000); // 16MB limit default - } + } }; /////////////////////////////////////////////////////////////// @@ -307,8 +277,6 @@ public: template class BaseBuffer : public BaseBufferConfig { private: - // Used for all strings in BaseBuffer. - using string_type = typename BaseBufferConfig::string_type; // True if we own this buffer. bool owned_; @@ -327,7 +295,13 @@ private: // Number of bytes read before buffer was last aligned. int64_t pre_read_count_; +public: + using string_type = typename BaseBufferConfig::string_type; + private: + using DS = DataSerializer>; + using DD = DataDeserializer, string_type>; + void init(bool fixed, bool owned, char *buf, int64_t size) { BaseBufferConfig::clear_error_flags(); owned_ = owned; @@ -340,6 +314,7 @@ private: } public: + // Construct an empty buffer. // BaseBuffer() { @@ -467,44 +442,22 @@ public: write_bytes(s.data(), s.size()); } - // Write integers. + // Write methods — delegate to DataSerializer. // - 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); } - - // Write other primitive types. - // - 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); } - - // Write lengths. - // - // Lengths are usually short, so we have a special way of storing - // lengths that minimizes the number of bytes when the length is short. - // - void write_length(size_t len) { - if (len >= 255) { - write_uint8(0xFF); - write_uint64(len); - } else { - write_uint8(len); - } - } - - // Write a string. - // - void write_string(std::string_view s) { - write_length(s.size()); - write_bytes(s); - } + void write_uint8(uint64_t data) { DS(this).write_uint8(data); } + void write_uint16(uint64_t data) { DS(this).write_uint16(data); } + void write_uint32(uint64_t data) { DS(this).write_uint32(data); } + void write_uint64(uint64_t data) { DS(this).write_uint64(data); } + void write_int8(int64_t data) { DS(this).write_int8(data); } + void write_int16(int64_t data) { DS(this).write_int16(data); } + void write_int32(int64_t data) { DS(this).write_int32(data); } + void write_int64(int64_t data) { DS(this).write_int64(data); } + void write_bool(bool b) { DS(this).write_bool(b); } + void write_char(char c) { DS(this).write_char(c); } + void write_float(float arg) { DS(this).write_float(arg); } + void write_double(double arg) { DS(this).write_double(arg); } + void write_length(size_t len) { DS(this).write_length(len); } + void write_string(std::string_view s) { DS(this).write_string(s); } // Write a LuaValueType. // @@ -530,6 +483,23 @@ public: } } + // Read bytes into a caller-supplied buffer. + // + // This is the primitive read operation used by DataDeserializer. + // If there aren't enough bytes, calls raise_eof_on_read and + // fills the buffer with zeros. + // + void read_bytes_into(char *data, size_t size) { + int64_t avail = write_cursor_ - read_cursor_; + if (avail < int64_t(size)) { + BaseBufferConfig::raise_eof_on_read(); + memset(data, 0, size); + return; + } + memcpy(data, read_cursor_, size); + read_cursor_ += size; + } + // Read a block of bytes from the buffer. // // Caution: the pointer returned is a pointer to the stream's buffer. @@ -546,42 +516,29 @@ public: read_cursor_ += bytes; return data; } - - // Read integers. - // - 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(); } - - // Read other primitive types. - // - 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(); } - // Read a length. + // Read methods — delegate to DataDeserializer. // - size_t read_length() { - uint64_t len = read_uint8(); - if (len == 255) { - len = read_uint64(); - } - return len; - } + uint8_t read_uint8() { return DD(this).read_uint8(); } + uint16_t read_uint16() { return DD(this).read_uint16(); } + uint32_t read_uint32() { return DD(this).read_uint32(); } + uint64_t read_uint64() { return DD(this).read_uint64(); } + int8_t read_int8() { return DD(this).read_int8(); } + int16_t read_int16() { return DD(this).read_int16(); } + int32_t read_int32() { return DD(this).read_int32(); } + int64_t read_int64() { return DD(this).read_int64(); } + bool read_bool() { return DD(this).read_bool(); } + char read_char() { return DD(this).read_char(); } + float read_float() { return DD(this).read_float(); } + double read_double() { return DD(this).read_double(); } + size_t read_length() { return DD(this).read_length(); } + string_type read_string_limit(uint64_t limit) { return DD(this).read_string_limit(limit); } + string_type read_string() { return DD(this).read_string(); } // Read a string as a string_view. // - // If the string in the buffer is longer than the limit, - // calls 'raise_string_too_long' and returns an empty string. - // - // If the buffer doesn't contain a complete string, calls - // 'raise_eof_on_read' and returns an empty string. + // This is BaseBuffer-specific — it returns a view directly into + // the buffer, avoiding a copy. // std::string_view read_string_view_limit(uint64_t limit) { size_t length = read_length(); @@ -604,31 +561,6 @@ public: std::string_view read_string_view() { return read_string_view_limit(0x1000000); } - - // Read a string. - // - string_type read_string_limit(uint64_t limit) { - size_t len = read_length(); - if (len > limit) { - BaseBufferConfig::raise_string_too_long(); - return string_type(); - } - int64_t avail = write_cursor_ - read_cursor_; - if (avail < int64_t(len)) { - BaseBufferConfig::raise_eof_on_read(); - return string_type(); - } - string_type result(len, ' '); - memcpy(&result[0], read_cursor_, len); - read_cursor_ += len; - return result; - } - - // Read a string. - // - string_type read_string() { - return read_string_limit(0x1000000); - } // Read a LuaValueType // @@ -728,33 +660,6 @@ private: write_cursor_ = buf_lo_ + data_size; } - template - void write_value_core(T arg) { - make_space(sizeof(arg)); - memcpy(write_cursor_, &arg, sizeof(arg)); - write_cursor_ += sizeof(arg); - } - - template - void write_int_core(XT arg) { - T reduced = arg; - if (XT(reduced) != arg) BaseBufferConfig::raise_integer_truncated(); - write_value_core(reduced); - } - - template - T read_value_core() { - T result; - int64_t avail = write_cursor_ - read_cursor_; - if (avail < int64_t(sizeof(result))) { - BaseBufferConfig::raise_eof_on_read(); - return 0; - } - memcpy(&result, read_cursor_, sizeof(result)); - read_cursor_ += sizeof(result); - return result; - } - template void overwrite_int_core(int64_t write_count_after, XT vv) { T v = vv;