Files
integration/luprex/ext/base-buffer.hpp

761 lines
24 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 <cstring>
#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,
AUTO,
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_auto() {
type=SimpleDynamicTag::AUTO; 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());
}
};
///////////////////////////////////////////////////////////////
//
// 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
};
///////////////////////////////////////////////////////////////
//
// Class BaseBuffer
//
// You must supply a CoreHandler which must define these
// methods:
//
// void *basebuffer_malloc(size_t size);
// void basebuffer_free(void *data);
// void raise_eof_on_read();
// void raise_string_too_long();
// void raise_integer_truncated();
//
// You must also select a StringType. Typically this would
// be std::string. This only affects the return value of
// read_string. You can always use read_string_view to read
// strings into other string types.
//
///////////////////////////////////////////////////////////////
template<class CoreHandler, class StringType>
class BaseBuffer : public CoreHandler {
private:
// True if we own this buffer.
bool owned_;
// True if we're not allowed to expand this buffer.
bool fixed_size_;
// Start and end of the allocated block.
char *buf_lo_;
char *buf_hi_;
// The write and read cursors.
char *write_cursor_;
char *read_cursor_;
// Number of bytes read before buffer was last aligned.
int64_t pre_read_count_;
private:
void init(bool fixed, bool owned, char *buf, int64_t size) {
CoreHandler::clear_error_flags();
owned_ = owned;
fixed_size_ = fixed;
buf_lo_ = buf;
buf_hi_ = buf_lo_ + size;
read_cursor_ = buf_lo_;
write_cursor_ = buf_lo_;
pre_read_count_ = 0;
}
public:
using string_type = StringType;
// Construct an empty buffer.
//
BaseBuffer() {
init(false, true, 0, 0);
}
// Construct an empty buffer, preallocate the specified amount of space.
//
BaseBuffer(int64_t size, bool fixed) {
assert(size >= 0);
init(fixed, true, (char *)CoreHandler::basebuffer_malloc(size), size);
}
// Construct a streambuffer that reads from an external block of bytes.
//
BaseBuffer(std::string_view data) {
init(true, false, const_cast<char *>(data.data()), data.size());
write_cursor_ = buf_hi_;
}
// Destructor. Frees the buffer, if any.
//
~BaseBuffer() {
if (owned_ && (buf_lo_ != 0)) CoreHandler::basebuffer_free(buf_lo_);
}
// Return the total number of bytes ever read.
//
int64_t total_reads() const {
return (read_cursor_ - buf_lo_) + pre_read_count_;
}
// Return the total number of bytes ever written.
//
int64_t total_writes() const {
return (write_cursor_ - buf_lo_) + pre_read_count_;
}
// Return the total bytes in the buffer.
//
int64_t fill() const {
return write_cursor_ - read_cursor_;
}
// Checks to see if the buffer is empty.
//
bool empty() const {
return write_cursor_ == read_cursor_;
}
// Return the contents as a string_view.
//
std::string_view view() const {
return std::string_view(read_cursor_, write_cursor_ - read_cursor_);
}
// Make the specified amount of space in the buffer for writing.
//
char *make_space(int64_t bytes) {
int64_t available = buf_hi_ - write_cursor_;
if (available < bytes) make_space_slow(bytes);
return write_cursor_;
}
// Used after calling make_space then filling the space.
//
void wrote_space(int64_t bytes) {
int64_t available = buf_hi_ - write_cursor_;
assert(bytes >= 0);
assert(available >= bytes);
write_cursor_ += bytes;
}
// Rewind the read cursor to a previous position.
//
void unread_to(int64_t rd_count) {
assert(rd_count >= pre_read_count_);
assert(rd_count <= total_reads());
read_cursor_ = buf_lo_ + (rd_count - pre_read_count_);
}
// Rewind the write cursor to a previous position.
//
void unwrite_to(int64_t wr_count) {
assert(wr_count >= total_reads());
assert(wr_count <= total_writes());
write_cursor_ = buf_lo_ + (wr_count - pre_read_count_);
}
// Discard all data. Reset total read and write counts.
// Releases the allocated buffer, if any.
//
void clear() {
assert(owned_);
if ((!fixed_size_) && (buf_lo_ != nullptr) && ((buf_hi_ - buf_lo_) > 100000)) {
CoreHandler::basebuffer_free(buf_lo_);
buf_lo_ = nullptr;
buf_hi_ = nullptr;
}
owned_ = true;
read_cursor_ = buf_lo_;
write_cursor_ = buf_lo_;
pre_read_count_ = 0;
}
// Write block of bytes into the buffer.
//
void write_bytes(std::string_view s) {
int64_t len = s.size();
make_space(len);
memcpy(write_cursor_, s.data(), len);
write_cursor_ += len;
}
// Write integers.
//
void write_uint8(uint64_t data) { write_uint_core<uint8_t>(data); }
void write_uint16(uint64_t data) { write_uint_core<uint16_t>(data); }
void write_uint32(uint64_t data) { write_uint_core<uint32_t>(data); }
void write_uint64(uint64_t data) { write_uint_core<uint64_t>(data); }
void write_int8(int64_t data) { write_int_core<int8_t>(data); }
void write_int16(int64_t data) { write_int_core<int16_t>(data); }
void write_int32(int64_t data) { write_int_core<int32_t>(data); }
void write_int64(int64_t data) { write_int_core<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);
}
// Write a SimpleDynamicTag.
//
void write_simple_dynamic_tag(SimpleDynamicTag tag) {
write_uint8(uint8_t(tag));
}
// Write a SimpleDynamic value.
//
// This works regardless of what kind of string is present in the
// SimpleDynamic.
//
template<class STRING>
void write_simple_dynamic(const SimpleDynamic<STRING> &sd) {
write_simple_dynamic_tag(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);
}
}
// Read a block of bytes from the buffer.
//
// Caution: the pointer returned is a pointer to the stream's buffer.
// It is only valid until you mutate the buffer. If the bytes aren't
// there, calls 'raise_eof_on_read', and then returns nullptr.
//
const char *read_bytes(int64_t bytes) {
int64_t avail = write_cursor_ - read_cursor_;
if (avail < bytes) {
CoreHandler::raise_eof_on_read();
return nullptr;
}
char *data = read_cursor_;
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.
//
size_t read_length() {
uint64_t len = read_uint8();
if (len == 255) {
len = read_uint64();
}
return len;
}
// 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.
//
std::string_view read_string_view_limit(uint64_t limit) {
size_t length = read_length();
if (length > limit) {
CoreHandler::raise_string_too_long();
return std::string_view();
}
int64_t avail = write_cursor_ - read_cursor_;
if (avail < int64_t(length)) {
CoreHandler::raise_eof_on_read();
return std::string_view();
}
std::string_view result(read_cursor_, length);
read_cursor_ += length;
return result;
}
// Read a string as a string_view.
//
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) {
CoreHandler::raise_string_too_long();
return string_type();
}
int64_t avail = write_cursor_ - read_cursor_;
if (avail < int64_t(len)) {
CoreHandler::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 SimpleDynamicTag
//
SimpleDynamicTag read_simple_dynamic_tag() {
return SimpleDynamicTag(read_uint8());
}
// Read a SimpleDynamic
//
template<class STRING>
void read_simple_dynamic(SimpleDynamic<STRING> *result) {
SimpleDynamicTag type = read_simple_dynamic_tag();
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: result->set_uninitialized(); break;
}
}
// Attempt to do a "readline". If there is no newline in
// the buffer, returns empty string. If there is a newline,
// returns a block of text that ends in newline.
//
string_type readline() {
char *p = read_cursor_;
while ((p < write_cursor_) && (*p != '\n')) p++;
if (p == write_cursor_) {
return string_type();
} else {
p++;
string_type result(read_cursor_, p - read_cursor_);
read_cursor_ = p;
return result;
}
}
// Overwrite values previously written to the buffer.
//
// See the comment at the top of this file for an explanation.
//
void overwrite_int8(int64_t write_count_after, int64_t v) { overwrite_int_core<int8_t>(write_count_after, v); }
void overwrite_int16(int64_t write_count_after, int64_t v) { overwrite_int_core<int16_t>(write_count_after, v); }
void overwrite_int32(int64_t write_count_after, int64_t v) { overwrite_int_core<int32_t>(write_count_after, v); }
void overwrite_int64(int64_t write_count_after, int64_t v) { overwrite_int_core<int64_t>(write_count_after, v); }
void overwrite_uint8(int64_t write_count_after, uint64_t v) { overwrite_uint_core<uint8_t>(write_count_after, v); }
void overwrite_uint16(int64_t write_count_after, uint64_t v) { overwrite_uint_core<uint16_t>(write_count_after, v); }
void overwrite_uint32(int64_t write_count_after, uint64_t v) { overwrite_uint_core<uint32_t>(write_count_after, v); }
void overwrite_uint64(int64_t write_count_after, uint64_t v) { overwrite_uint_core<uint64_t>(write_count_after, v); }
// This is for unit testing.
//
bool layout_is(int64_t a, int64_t b, int64_t c) {
if (read_cursor_ - buf_lo_ != a) return false;
if (write_cursor_ - read_cursor_ != b) return false;
if (buf_hi_ - write_cursor_ != c) return false;
return true;
}
private:
void make_space_slow(int64_t bytes) {
assert(owned_ && "We don't own this buffer, can't grow it");
// Decide whether the current buffer is big enough.
int64_t data_size = (write_cursor_ - read_cursor_);
int64_t existing_size = (buf_hi_ - buf_lo_);
int64_t desired_size = 8192 + ((data_size + bytes) * 2);
// Update some simple things.
pre_read_count_ += (read_cursor_ - buf_lo_);
// Move the data to the beginning of the buffer, or to
// the beginning of a new buffer.
if (fixed_size_) {
assert((data_size + bytes <= existing_size) && "Not enough space in fixed-size buffer");
if (data_size > 0) memcpy(buf_lo_, read_cursor_, data_size);
} else if (existing_size >= desired_size) {
if (data_size > 0) memcpy(buf_lo_, read_cursor_, data_size);
} else {
char *nbuf = (char *)CoreHandler::basebuffer_malloc(desired_size);
if (data_size > 0) memcpy(nbuf, read_cursor_, data_size);
if (buf_lo_ != nullptr) CoreHandler::basebuffer_free(buf_lo_);
buf_lo_ = nbuf;
buf_hi_ = nbuf + desired_size;
}
// Update the pointers to the data region.
read_cursor_ = buf_lo_;
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>
void write_int_core(int64_t arg) {
T reduced = arg;
if (int64_t(reduced) != arg) CoreHandler::raise_integer_truncated();
write_value_core(reduced);
}
template<class T>
void write_uint_core(uint64_t arg) {
T reduced = arg;
if (uint64_t(reduced) != arg) CoreHandler::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))) {
CoreHandler::raise_eof_on_read();
return 0;
}
memcpy(&result, read_cursor_, sizeof(result));
read_cursor_ += sizeof(result);
return result;
}
template<class T>
void overwrite_int_core(int64_t write_count_after, int64_t vv) {
T v = vv;
assert(int64_t(v) == vv);
int64_t write_count_before = write_count_after - sizeof(v);
assert(write_count_before >= total_reads());
assert(write_count_after <= total_writes());
void *target = buf_lo_ + (write_count_before - pre_read_count_);
memcpy(target, &v, sizeof(v));
}
template<class T>
void overwrite_uint_core(int64_t write_count_after, uint64_t vv) {
T v = vv;
assert(uint64_t(v) == vv);
int64_t write_count_before = write_count_after - sizeof(v);
assert(write_count_before >= total_reads());
assert(write_count_after <= total_writes());
void *target = buf_lo_ + (write_count_before - pre_read_count_);
memcpy(target, &v, sizeof(v));
}
};