Yet more refactors in basebuffer.
This commit is contained in:
@@ -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<MyWriter>
|
||||
// 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 Derived>
|
||||
class BaseWriteMethods {
|
||||
protected:
|
||||
template<class OutputDevice>
|
||||
class DataSerializer {
|
||||
private:
|
||||
OutputDevice *output_;
|
||||
|
||||
template<class T>
|
||||
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>
|
||||
void write_int_core(XT arg) {
|
||||
T reduced = arg;
|
||||
if (XT(reduced) != arg) static_cast<Derived*>(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<uint8_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) {
|
||||
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
|
||||
// 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<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:
|
||||
//
|
||||
// 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 Derived>
|
||||
class BaseReadMethods {
|
||||
protected:
|
||||
using string_type = typename Derived::string_type;
|
||||
template<class InputDevice, class StringType = std::string>
|
||||
class DataDeserializer {
|
||||
private:
|
||||
InputDevice *input_;
|
||||
|
||||
template<class T>
|
||||
T read_value_core() {
|
||||
T result;
|
||||
Derived *dthis = static_cast<Derived*>(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<uint8_t>(); }
|
||||
uint16_t read_uint16() { return read_value_core<uint16_t>(); }
|
||||
@@ -260,33 +231,32 @@ public:
|
||||
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;
|
||||
}
|
||||
|
||||
string_type read_string_limit(uint64_t limit) {
|
||||
StringType 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();
|
||||
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 BaseBufferConfig>
|
||||
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<BaseBuffer<BaseBufferConfig>>;
|
||||
using DD = DataDeserializer<BaseBuffer<BaseBufferConfig>, 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<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); }
|
||||
|
||||
// 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<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>(); }
|
||||
|
||||
// Read other primitive types.
|
||||
//
|
||||
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>(); }
|
||||
|
||||
// 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<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>
|
||||
void overwrite_int_core(int64_t write_count_after, XT vv) {
|
||||
T v = vv;
|
||||
|
||||
Reference in New Issue
Block a user