Yet more refactors in basebuffer.

This commit is contained in:
2026-02-22 22:46:54 -05:00
parent 5c258be507
commit cbbe475f65
4 changed files with 139 additions and 220 deletions

View File

@@ -8,7 +8,7 @@ private:
bool err_eof_on_read_; bool err_eof_on_read_;
bool err_string_too_long_; bool err_string_too_long_;
bool err_integer_truncated_; bool err_integer_truncated_;
protected: public:
using string_type = std::string; using string_type = std::string;
void *basebuffer_malloc(size_t size) { return malloc(size); } void *basebuffer_malloc(size_t size) { return malloc(size); }
void basebuffer_free(void *p) { free(p); } void basebuffer_free(void *p) { free(p); }

View File

@@ -226,14 +226,21 @@ enum DrvAction {
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
class PlayLogfile : public BaseWriteMethods<PlayLogfile>, public std::ofstream { class PlayLogfile : public std::ofstream {
using std::ofstream::ofstream; using std::ofstream::ofstream;
using DS = DataSerializer<PlayLogfile>;
public: public:
void write_bytes(const char *n, size_t size) { write(n, size); } void write_bytes(const char *n, size_t size) { write(n, size); }
void raise_integer_truncated() { void raise_integer_truncated() {
fprintf(stderr, "number exceeds allowable size\n"); fprintf(stderr, "number exceeds allowable size\n");
std::abort(); 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) { void write_short_string(std::string_view v) {
assert(v.size() < DRV_SHORTSTRING_SIZE); assert(v.size() < DRV_SHORTSTRING_SIZE);
write_string(v); write_string(v);
@@ -244,10 +251,10 @@ public:
} }
}; };
class ReplayLogfile : public BaseReadMethods<ReplayLogfile>, public std::ifstream { class ReplayLogfile : public std::ifstream {
using std::ifstream::ifstream; using std::ifstream::ifstream;
using DD = DataDeserializer<ReplayLogfile>;
public: public:
using read_string_type = std::string;
void read_bytes_into(char *n, size_t size) { void read_bytes_into(char *n, size_t size) {
read(n, size); read(n, size);
if (!good()) { if (!good()) {
@@ -258,6 +265,13 @@ public:
fprintf(stderr, "string in logfile is too long"); fprintf(stderr, "string in logfile is too long");
std::abort(); 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) { std::string_view read_short_string(EngineWrapper *w) {
size_t size = read_length(); size_t size = read_length();
assert(size <= DRV_SHORTSTRING_SIZE); assert(size <= DRV_SHORTSTRING_SIZE);

View File

@@ -246,7 +246,7 @@ public:
using LuaValue = BaseLuaValue<eng::string>; using LuaValue = BaseLuaValue<eng::string>;
class StreamBufferConfig { class StreamBufferConfig {
protected: public:
using string_type = eng::string; using string_type = eng::string;
void *basebuffer_malloc(size_t size) { return eng::malloc(size); } void *basebuffer_malloc(size_t size) { return eng::malloc(size); }
void basebuffer_free(void *p) { eng::free(p); } void basebuffer_free(void *p) { eng::free(p); }

View File

@@ -101,60 +101,53 @@ struct BaseLuaValue {
/////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////
// //
// BaseWriteMethods // DataSerializer
// //
// Suppose you've written a class MyWriter that contains a // DataSerializer is an object that can serialize ints,
// write_bytes method. Now you want to add write_int, // strings, floats, and other basic types. It provides a
// write_string, and so forth. By deriving from // consistent standard for the byte formats.
// BaseWriteMethods, you add all of the following methods:
// //
// void write_uint8(uint64_t data) // To serialize, first construct a DataSerializer, passing
// void write_uint16(uint64_t data) // in a pointer to an output device. An output device is
// void write_uint32(uint64_t data) // any class that has these methods:
// 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)
// //
// Here is how you should derive from BaseWriteMethods: // void write_bytes(char *data, size_t len);
// //
// class MyWriter : public BaseWriteMethods<MyWriter> // void raise_integer_truncated();
// //
// Your class MyWriter must implement these two methods: // 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.
// //
// write_bytes(const char *n, size_t size) // It is intended that the compiler will optimize this
// raise_integer_truncated() // process to such a degree that it costs no more than
// // simply calling write_bytes directly on the output
// If you call write_uint8(1000), that is an error, because // device. In other words, it is our intent that the
// 1000 doesn't fit in a uint8. So, write_uint8 will call // use of a DataSerializer should be free.
// 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.
// //
/////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////
template<class Derived> template<class OutputDevice>
class BaseWriteMethods { class DataSerializer {
protected: private:
OutputDevice *output_;
template<class T> template<class T>
void write_value_core(T arg) { void write_value_core(T arg) {
static_cast<Derived*>(this)->write_bytes((const char *)&arg, sizeof(arg)); output_->write_bytes((const char *)&arg, sizeof(arg));
} }
template<class T, class XT> template<class T, class XT>
void write_int_core(XT arg) { void write_int_core(XT arg) {
T reduced = arg; T reduced = arg;
if (XT(reduced) != arg) static_cast<Derived*>(this)->raise_integer_truncated(); if (XT(reduced) != arg) output_->raise_integer_truncated();
write_value_core(reduced); output_->write_bytes((const char *)&reduced, sizeof(reduced));
} }
public: public:
DataSerializer(OutputDevice *o) : output_(o) {}
void write_uint8(uint64_t data) { write_int_core<uint8_t, uint64_t>(data); } 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_uint16(uint64_t data) { write_int_core<uint16_t, uint64_t>(data); }
@@ -181,76 +174,54 @@ public:
void write_string(std::string_view s) { void write_string(std::string_view s) {
write_length(s.size()); write_length(s.size());
static_cast<Derived*>(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 // DataDeserializer is an object that can deserialize ints,
// read_bytes method. Now you want to add read_int, // strings, floats, and other basic types. It provides a
// read_string, and so forth. By deriving from // consistent standard for the byte formats.
// BaseReadMethods, you add all of the following methods:
// //
// uint8_t read_uint8(); // To deserialize, first construct a DataDeserializer, passing
// uint16_t read_uint16(); // in a pointer to an input device. An input device is
// uint32_t read_uint32(); // any class that has these methods:
// 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();
// //
// 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<MyReader> // 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: // It is intended that the compiler will optimize this
// // process to such a degree that it costs no more than
// using string_type = std::string; // or compatible // simply calling read_bytes_into directly on the input
// void read_bytes_into(char *n, size_t size) // device. In other words, it is our intent that the
// void raise_string_too_long(); // use of a DataDeserializer should be free.
//
// 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.
// //
/////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////
template<class Derived> template<class InputDevice, class StringType = std::string>
class BaseReadMethods { class DataDeserializer {
protected: private:
using string_type = typename Derived::string_type; InputDevice *input_;
template<class T> template<class T>
T read_value_core() { T read_value_core() {
T result; T result;
Derived *dthis = static_cast<Derived*>(this); input_->read_bytes_into((char *)(&result), sizeof(result));
dthis->read_bytes_into((char *)(&result), sizeof(result));
return result; return result;
} }
public: public:
DataDeserializer(InputDevice *i) : input_(i) {}
uint8_t read_uint8() { return read_value_core<uint8_t>(); } uint8_t read_uint8() { return read_value_core<uint8_t>(); }
uint16_t read_uint16() { return read_value_core<uint16_t>(); } uint16_t read_uint16() { return read_value_core<uint16_t>(); }
@@ -272,19 +243,18 @@ public:
return len; return len;
} }
string_type read_string_limit(uint64_t limit) { StringType read_string_limit(uint64_t limit) {
size_t len = read_length(); size_t len = read_length();
Derived *dthis = static_cast<Derived*>(this);
if (len > limit) { if (len > limit) {
dthis->raise_string_too_long(); input_->raise_string_too_long();
len = 0; len = 0;
} }
string_type result(len, ' '); StringType result(len, ' ');
dthis->read_bytes_into(&(result[0]), len); input_->read_bytes_into(&(result[0]), len);
return result; return result;
} }
string_type read_string() { StringType read_string() {
return read_string_limit(0x1000000); // 16MB limit default return read_string_limit(0x1000000); // 16MB limit default
} }
}; };
@@ -307,8 +277,6 @@ public:
template<class BaseBufferConfig> template<class BaseBufferConfig>
class BaseBuffer : public BaseBufferConfig { class BaseBuffer : public BaseBufferConfig {
private: private:
// Used for all strings in BaseBuffer.
using string_type = typename BaseBufferConfig::string_type;
// True if we own this buffer. // True if we own this buffer.
bool owned_; bool owned_;
@@ -327,7 +295,13 @@ private:
// Number of bytes read before buffer was last aligned. // Number of bytes read before buffer was last aligned.
int64_t pre_read_count_; int64_t pre_read_count_;
public:
using string_type = typename BaseBufferConfig::string_type;
private: private:
using DS = DataSerializer<BaseBuffer<BaseBufferConfig>>;
using DD = DataDeserializer<BaseBuffer<BaseBufferConfig>, string_type>;
void init(bool fixed, bool owned, char *buf, int64_t size) { void init(bool fixed, bool owned, char *buf, int64_t size) {
BaseBufferConfig::clear_error_flags(); BaseBufferConfig::clear_error_flags();
owned_ = owned; owned_ = owned;
@@ -340,6 +314,7 @@ private:
} }
public: public:
// Construct an empty buffer. // Construct an empty buffer.
// //
BaseBuffer() { BaseBuffer() {
@@ -467,44 +442,22 @@ public:
write_bytes(s.data(), s.size()); write_bytes(s.data(), s.size());
} }
// Write integers. // Write methods — delegate to DataSerializer.
// //
void write_uint8(uint64_t data) { write_int_core<uint8_t, uint64_t>(data); } void write_uint8(uint64_t data) { DS(this).write_uint8(data); }
void write_uint16(uint64_t data) { write_int_core<uint16_t, uint64_t>(data); } void write_uint16(uint64_t data) { DS(this).write_uint16(data); }
void write_uint32(uint64_t data) { write_int_core<uint32_t, uint64_t>(data); } void write_uint32(uint64_t data) { DS(this).write_uint32(data); }
void write_uint64(uint64_t data) { write_int_core<uint64_t, uint64_t>(data); } void write_uint64(uint64_t data) { DS(this).write_uint64(data); }
void write_int8(int64_t data) { write_int_core<int8_t, int64_t>(data); } void write_int8(int64_t data) { DS(this).write_int8(data); }
void write_int16(int64_t data) { write_int_core<int16_t, int64_t>(data); } void write_int16(int64_t data) { DS(this).write_int16(data); }
void write_int32(int64_t data) { write_int_core<int32_t, int64_t>(data); } void write_int32(int64_t data) { DS(this).write_int32(data); }
void write_int64(int64_t data) { write_int_core<int64_t, int64_t>(data); } void write_int64(int64_t data) { DS(this).write_int64(data); }
void write_bool(bool b) { DS(this).write_bool(b); }
// Write other primitive types. void write_char(char c) { DS(this).write_char(c); }
// void write_float(float arg) { DS(this).write_float(arg); }
void write_bool(bool b) { write_uint8(b ? 1:0); } void write_double(double arg) { DS(this).write_double(arg); }
void write_char(char c) { write_value_core(c); } void write_length(size_t len) { DS(this).write_length(len); }
void write_float(float arg) { write_value_core(arg); } void write_string(std::string_view s) { DS(this).write_string(s); }
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);
}
// Write a LuaValueType. // 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. // Read a block of bytes from the buffer.
// //
// Caution: the pointer returned is a pointer to the stream's buffer. // Caution: the pointer returned is a pointer to the stream's buffer.
@@ -547,41 +517,28 @@ public:
return data; return data;
} }
// Read integers. // Read methods — delegate to DataDeserializer.
// //
uint8_t read_uint8() { return read_value_core<uint8_t>(); } uint8_t read_uint8() { return DD(this).read_uint8(); }
uint16_t read_uint16() { return read_value_core<uint16_t>(); } uint16_t read_uint16() { return DD(this).read_uint16(); }
uint32_t read_uint32() { return read_value_core<uint32_t>(); } uint32_t read_uint32() { return DD(this).read_uint32(); }
uint64_t read_uint64() { return read_value_core<uint64_t>(); } uint64_t read_uint64() { return DD(this).read_uint64(); }
int8_t read_int8() { return read_value_core<int8_t>(); } int8_t read_int8() { return DD(this).read_int8(); }
int16_t read_int16() { return read_value_core<int16_t>(); } int16_t read_int16() { return DD(this).read_int16(); }
int32_t read_int32() { return read_value_core<int32_t>(); } int32_t read_int32() { return DD(this).read_int32(); }
int64_t read_int64() { return read_value_core<int64_t>(); } int64_t read_int64() { return DD(this).read_int64(); }
bool read_bool() { return DD(this).read_bool(); }
// Read other primitive types. char read_char() { return DD(this).read_char(); }
// float read_float() { return DD(this).read_float(); }
bool read_bool() { return (bool)read_uint8(); } double read_double() { return DD(this).read_double(); }
char read_char() { return read_value_core<char>(); } size_t read_length() { return DD(this).read_length(); }
float read_float() { return read_value_core<float>(); } string_type read_string_limit(uint64_t limit) { return DD(this).read_string_limit(limit); }
double read_double() { return read_value_core<double>(); } string_type read_string() { return DD(this).read_string(); }
// Read a length.
//
size_t read_length() {
uint64_t len = read_uint8();
if (len == 255) {
len = read_uint64();
}
return len;
}
// Read a string as a string_view. // Read a string as a string_view.
// //
// If the string in the buffer is longer than the limit, // This is BaseBuffer-specific — it returns a view directly into
// calls 'raise_string_too_long' and returns an empty string. // the buffer, avoiding a copy.
//
// If the buffer doesn't contain a complete string, calls
// 'raise_eof_on_read' and returns an empty string.
// //
std::string_view read_string_view_limit(uint64_t limit) { std::string_view read_string_view_limit(uint64_t limit) {
size_t length = read_length(); size_t length = read_length();
@@ -605,31 +562,6 @@ public:
return read_string_view_limit(0x1000000); 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 // Read a LuaValueType
// //
LuaValueType read_simple_dynamic_tag() { LuaValueType read_simple_dynamic_tag() {
@@ -728,33 +660,6 @@ private:
write_cursor_ = buf_lo_ + data_size; write_cursor_ = buf_lo_ + data_size;
} }
template<class T>
void write_value_core(T arg) {
make_space(sizeof(arg));
memcpy(write_cursor_, &arg, sizeof(arg));
write_cursor_ += sizeof(arg);
}
template<class T, class XT>
void write_int_core(XT arg) {
T reduced = arg;
if (XT(reduced) != arg) BaseBufferConfig::raise_integer_truncated();
write_value_core(reduced);
}
template<class T>
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<class T, class XT> template<class T, class XT>
void overwrite_int_core(int64_t write_count_after, XT vv) { void overwrite_int_core(int64_t write_count_after, XT vv) {
T v = vv; T v = vv;