From 6ed74b6efbb826aefa66b9d519d3495e849ea4ee Mon Sep 17 00:00:00 2001 From: ImBenji Date: Thu, 18 Sep 2025 03:02:14 +0100 Subject: [PATCH] stuff --- cpp/binary_table.h | 1735 ++++++++++++++++++++++++-------------------- 1 file changed, 950 insertions(+), 785 deletions(-) diff --git a/cpp/binary_table.h b/cpp/binary_table.h index 908853e..c3f60ad 100644 --- a/cpp/binary_table.h +++ b/cpp/binary_table.h @@ -1,32 +1,36 @@ -/* - /$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$$$ /$$ /$$ /$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$$ -|_ $$_/| $$$ /$$$| $$__ $$| $$_____/| $$$ | $$ |__ $$|_ $$_/ | $$$ | $$| $$_____/|__ $$__/ - | $$ | $$$$ /$$$$| $$ \ $$| $$ | $$$$| $$ | $$ | $$ | $$$$| $$| $$ | $$ - | $$ | $$ $$/$$ $$| $$$$$$$ | $$$$$ | $$ $$ $$ | $$ | $$ | $$ $$ $$| $$$$$ | $$ - | $$ | $$ $$$| $$| $$__ $$| $$__/ | $$ $$$$ /$$ | $$ | $$ | $$ $$$$| $$__/ | $$ - | $$ | $$\ $ | $$| $$ \ $$| $$ | $$\ $$$| $$ | $$ | $$ | $$\ $$$| $$ | $$ - /$$$$$$| $$ \/ | $$| $$$$$$$/| $$$$$$$$| $$ \ $$| $$$$$$/ /$$$$$$ /$$| $$ \ $$| $$$$$$$$ | $$ -|______/|__/ |__/|_______/ |________/|__/ \__/ \______/ |______/|__/|__/ \__/|________/ |__/ - -© 2025-26 by Benjamin Watt of IMBENJI.NET LIMITED - All rights reserved. - -Use of this source code is governed by a MIT license that can be found in the LICENSE file. - -This file is part of the SweepStore (formerly Binary Table) package for C++. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -enum class BT_Type : uint8_t { +// +// SweepStore (formerly Binary Table) - C++ port +// © 2025-26 by Benjamin Watt of IMBENJI.NET LIMITED - All rights reserved. +// MIT License +// + +// Utility endian enum +enum class Endian { Little, Big }; + +// Forward declarations +struct BT_Pointer; +class BinaryTable; +class BT_UniformArray; + +// Type identifiers matching Dart enum ordinals +enum class BT_TypeId : uint8_t { POINTER = 0, ADDRESS_TABLE = 1, INTEGER = 2, @@ -36,816 +40,963 @@ enum class BT_Type : uint8_t { FLOAT_ARRAY = 6 }; -constexpr int getBT_TypeSize(BT_Type type) { - switch (type) { - case BT_Type::POINTER: return 8; - case BT_Type::INTEGER: return 4; - case BT_Type::FLOAT: return 4; +// Type metadata +struct BT_TypeInfo { + int size; // Size in bytes, -1 = variable + bool arrayType; // Is this an array type +}; + +inline const BT_TypeInfo& typeInfo(BT_TypeId t) { + static const BT_TypeInfo kInfo[] = { + /* POINTER */ {8, false}, + /* ADDRESS_TABLE */ {-1, false}, + /* INTEGER */ {4, false}, + /* FLOAT */ {4, false}, + /* STRING */ {-1, false}, + /* INTEGER_ARRAY */ {-1, true}, + /* FLOAT_ARRAY */ {-1, true}, + }; + return kInfo[static_cast(t)]; +} + +inline BT_TypeId typeFromId(uint8_t id) { + if (id > static_cast(BT_TypeId::FLOAT_ARRAY)) { + throw std::invalid_argument("Invalid BT_Type id"); } - default: return -1; // Variable size + return static_cast(id); } -constexpr bool isBT_TypeArray(BT_Type type) { -} +// Pointer wrapper +struct BT_Pointer { + int64_t address{-1}; -return type == BT_Type::INTEGER_ARRAY || type == BT_Type::FLOAT_ARRAY; -template -constexpr BT_Type getBT_TypeFromValue() { - if constexpr (std::is_same_v) { - return BT_Type::INTEGER; - } else if constexpr (std::is_same_v) { - return BT_Type::FLOAT; - } else if constexpr (std::is_same_v) { - return BT_Type::STRING; - } else if constexpr (std::is_same_v>) { - return BT_Type::INTEGER_ARRAY; - } else if constexpr (std::is_same_v>) { - return BT_Type::FLOAT_ARRAY; - } else { - static_assert(sizeof(T) == 0, "Unsupported type"); - } -} - -template -std::vector encodeValue(const T& value) { - std::vector buffer; - BT_Type valueType = getBT_TypeFromValue(); - buffer.push_back(static_cast(valueType)); - - if constexpr (std::is_same_v) { - uint32_t val = static_cast(value); - buffer.push_back(val & 0xFF); - buffer.push_back((val >> 8) & 0xFF); - buffer.push_back((val >> 16) & 0xFF); - buffer.push_back((val >> 24) & 0xFF); - } else if constexpr (std::is_same_v) { - union { float f; uint32_t i; } converter; - converter.f = value; - uint32_t val = converter.i; - buffer.push_back(val & 0xFF); - buffer.push_back((val >> 8) & 0xFF); - buffer.push_back((val >> 16) & 0xFF); - buffer.push_back((val >> 24) & 0xFF); - } else if constexpr (std::is_same_v) { - uint32_t len = static_cast(value.length()); - buffer.push_back(len & 0xFF); - buffer.push_back((len >> 8) & 0xFF); - buffer.push_back((len >> 16) & 0xFF); - buffer.push_back((len >> 24) & 0xFF); - for (char c : value) { - buffer.push_back(static_cast(c)); - } - } else if constexpr (std::is_same_v>) { - uint32_t len = static_cast(value.size()); - buffer.push_back(len & 0xFF); - buffer.push_back((len >> 8) & 0xFF); - buffer.push_back((len >> 16) & 0xFF); - buffer.push_back((len >> 24) & 0xFF); - for (const auto& item : value) { - auto itemBuffer = encodeValue(item); - buffer.insert(buffer.end(), itemBuffer.begin(), itemBuffer.end()); - } - } else if constexpr (std::is_same_v>) { - uint32_t len = static_cast(value.size()); - buffer.push_back(len & 0xFF); - buffer.push_back((len >> 8) & 0xFF); - buffer.push_back((len >> 16) & 0xFF); - buffer.push_back((len >> 24) & 0xFF); - for (const auto& item : value) { - auto itemBuffer = encodeValue(item); - buffer.insert(buffer.end(), itemBuffer.begin(), itemBuffer.end()); - } - } - - return buffer; -} - -class BT_Pointer { -public: - int64_t address; - - BT_Pointer(int64_t addr = -1) : address(addr) {} + BT_Pointer() = default; + explicit BT_Pointer(int64_t addr) : address(addr) {} bool isNull() const { return address == -1; } - bool operator==(const BT_Pointer& other) const { - return address == other.address; - } - - bool operator!=(const BT_Pointer& other) const { - return !(*this == other); - } + bool operator==(const BT_Pointer& other) const { return address == other.address; } + bool operator!=(const BT_Pointer& other) const { return !(*this == other); } std::string toString() const { - std::stringstream ss; - ss << "0x" << std::hex << address << " (" << std::dec << address << ")"; - return ss.str(); + std::ostringstream oss; + oss << "0x" << std::hex << std::uppercase << address << " (" << std::dec << address << ")"; + return oss.str(); + } +}; +static const BT_Pointer BT_Null{-1}; + +// Random access file wrapper +class RandomAccessFile { +public: + explicit RandomAccessFile(const std::string& path) + : path_(path) { + open(); + } + + void setPosition(int64_t pos) { + fs_.flush(); + fs_.clear(); + fs_.seekg(pos, std::ios::beg); + fs_.seekp(pos, std::ios::beg); + } + + int64_t length() const { + return static_cast(std::filesystem::file_size(path_)); + } + + void truncate(int64_t new_length) { + fs_.flush(); + fs_.close(); + std::filesystem::resize_file(path_, static_cast(new_length)); + open(); + } + + uint8_t readByte() { + char c = 0; + fs_.read(&c, 1); + if (!fs_) throw std::runtime_error("readByte failed"); + return static_cast(c); + } + + std::vector read(size_t n) { + std::vector buf(n); + fs_.read(reinterpret_cast(buf.data()), static_cast(n)); + if (!fs_) throw std::runtime_error("read failed"); + return buf; + } + + void write(const std::vector& data) { + fs_.write(reinterpret_cast(data.data()), static_cast(data.size())); + if (!fs_) throw std::runtime_error("write failed"); + fs_.flush(); + } + + // Read/Write Int dynamic + int64_t readInt(size_t size = 4, Endian endianness = Endian::Little) { + if (size < 1 || size > 8) throw std::invalid_argument("Size must be between 1 and 8 bytes"); + auto bytes = read(size); + + uint64_t u = 0; + if (endianness == Endian::Little) { + for (size_t i = 0; i < size; ++i) { + u |= static_cast(bytes[i]) << (8 * i); + } + } else { + for (size_t i = 0; i < size; ++i) { + u = (u << 8) | bytes[i]; + } + } + + // Sign-extend based on MSB of the given size + uint64_t sign_bit = uint64_t{1} << (size * 8 - 1); + if (u & sign_bit) { + uint64_t mask = (~uint64_t{0}) << (size * 8); + u |= mask; + } + return static_cast(u); + } + + void writeInt(int64_t value, size_t size = 4, Endian endianness = Endian::Little) { + if (size < 1 || size > 8) throw std::invalid_argument("Size must be between 1 and 8 bytes"); + std::vector bytes(size, 0); + + if (endianness == Endian::Little) { + for (size_t i = 0; i < size; ++i) { + bytes[i] = static_cast((static_cast(value) >> (i * 8)) & 0xFF); + } + } else { + for (size_t i = 0; i < size; ++i) { + bytes[size - 1 - i] = static_cast((static_cast(value) >> (i * 8)) & 0xFF); + } + } + + write(bytes); + } + + // Read/Write Pointers + BT_Pointer readPointer() { + int64_t offset = readInt(typeInfo(BT_TypeId::POINTER).size); + return BT_Pointer(offset); + } + + void writePointer(const BT_Pointer& pointer) { + writeInt(pointer.address, typeInfo(BT_TypeId::POINTER).size); + } + + // Read/Write Float32 + double readFloat32(Endian endianness = Endian::Little) { + auto bytes = read(4); + uint32_t u = 0; + if (endianness == Endian::Little) { + u = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24); + } else { + u = static_cast(bytes[3]) | + (static_cast(bytes[2]) << 8) | + (static_cast(bytes[1]) << 16) | + (static_cast(bytes[0]) << 24); + } + float f; + std::memcpy(&f, &u, sizeof(f)); + return static_cast(f); + } + + void writeFloat32(double value, Endian endianness = Endian::Little) { + float f = static_cast(value); + uint32_t u; + std::memcpy(&u, &f, sizeof(u)); + std::vector bytes(4); + if (endianness == Endian::Little) { + bytes[0] = static_cast(u & 0xFF); + bytes[1] = static_cast((u >> 8) & 0xFF); + bytes[2] = static_cast((u >> 16) & 0xFF); + bytes[3] = static_cast((u >> 24) & 0xFF); + } else { + bytes[3] = static_cast(u & 0xFF); + bytes[2] = static_cast((u >> 8) & 0xFF); + bytes[1] = static_cast((u >> 16) & 0xFF); + bytes[0] = static_cast((u >> 24) & 0xFF); + } + write(bytes); + } + + // Read/Write Float64 + double readFloat64(Endian endianness = Endian::Little) { + auto bytes = read(8); + uint64_t u = 0; + if (endianness == Endian::Little) { + for (int i = 0; i < 8; ++i) u |= static_cast(bytes[i]) << (8 * i); + } else { + for (int i = 0; i < 8; ++i) u = (u << 8) | bytes[i]; + } + double d; + std::memcpy(&d, &u, sizeof(d)); + return d; + } + + void writeFloat64(double value, Endian endianness = Endian::Little) { + uint64_t u; + std::memcpy(&u, &value, sizeof(u)); + std::vector bytes(8); + if (endianness == Endian::Little) { + for (int i = 0; i < 8; ++i) bytes[i] = static_cast((u >> (8 * i)) & 0xFF); + } else { + for (int i = 0; i < 8; ++i) bytes[7 - i] = static_cast((u >> (8 * i)) & 0xFF); + } + write(bytes); + } + +private: + std::string path_; + std::fstream fs_; + + void open() { + // Ensure file exists + if (!std::filesystem::exists(path_)) { + std::ofstream create(path_, std::ios::binary); + create.close(); + } + fs_.open(path_, std::ios::binary | std::ios::in | std::ios::out); + if (!fs_) throw std::runtime_error("Failed to open file: " + path_); } }; -const BT_Pointer BT_Null(-1); +// Helpers to append ints to buffers +inline void appendIntLE(std::vector& buf, int64_t value, size_t size) { + for (size_t i = 0; i < size; ++i) { + buf.push_back(static_cast((static_cast(value) >> (8 * i)) & 0xFF)); + } +} -class BinaryTable; +// Value input types acceptable to encodeValue +using ValueInput = std::variant, std::vector>; -class BT_Reference { -protected: - BinaryTable* _table; - BT_Pointer _pointer; +// Forward: encodeValue +std::vector encodeValue(const ValueInput& value); -public: - BT_Reference(BinaryTable* table, BT_Pointer pointer) : _table(table), _pointer(pointer) {} +// fromDynamic equivalent for ValueInput +inline BT_TypeId typeFromDynamic(const ValueInput& v) { + return std::visit([](auto&& arg) -> BT_TypeId { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return BT_TypeId::INTEGER; + } else if constexpr (std::is_same_v) { + return BT_TypeId::FLOAT; + } else if constexpr (std::is_same_v) { + return BT_TypeId::STRING; + } else if constexpr (std::is_same_v>) { + return BT_TypeId::INTEGER_ARRAY; + } else if constexpr (std::is_same_v>) { + return BT_TypeId::FLOAT_ARRAY; + } else { + throw std::invalid_argument("Unsupported type"); + } + }, v); +} - template - T decodeValue(); +// Encode a value into bytes [type byte][payload ...] +std::vector encodeValue(const ValueInput& value) { + std::vector buf; - int getSize(); + BT_TypeId valueType = typeFromDynamic(value); + buf.push_back(static_cast(valueType)); - std::string toString() const { return _pointer.toString(); } + switch (valueType) { + case BT_TypeId::INTEGER: { + int32_t v = std::get(value); + appendIntLE(buf, v, 4); + break; + } + case BT_TypeId::FLOAT: { + // Store as Float32 + float f = static_cast(std::get(value)); + uint32_t u; + std::memcpy(&u, &f, sizeof(u)); + appendIntLE(buf, static_cast(u), 4); + break; + } + case BT_TypeId::STRING: { + const std::string& s = std::get(value); + // Note: Dart used codeUnits; here we write raw bytes of the string (assumed UTF-8) and length = bytes length + appendIntLE(buf, static_cast(s.size()), 4); + buf.insert(buf.end(), s.begin(), s.end()); + break; + } + case BT_TypeId::INTEGER_ARRAY: { + const auto& list = std::get>(value); + appendIntLE(buf, static_cast(list.size()), 4); + for (auto& item : list) { + ValueInput vi = static_cast(item); + auto enc = encodeValue(vi); + buf.insert(buf.end(), enc.begin(), enc.end()); + } + break; + } + case BT_TypeId::FLOAT_ARRAY: { + const auto& list = std::get>(value); + appendIntLE(buf, static_cast(list.size()), 4); + for (auto& item : list) { + ValueInput vi = static_cast(item); + auto enc = encodeValue(vi); + buf.insert(buf.end(), enc.begin(), enc.end()); + } + break; + } + default: + throw std::invalid_argument("Unsupported type for encodeValue"); + } - BT_Pointer getPointer() const { return _pointer; } -}; + return buf; +} +// Free list entry struct BT_FreeListEntry { BT_Pointer pointer; - int size; - - BT_FreeListEntry(BT_Pointer ptr, int sz) : pointer(ptr), size(sz) {} + int32_t size; }; -template +// Utility on vector +inline void removePointer(std::vector& list, const BT_Pointer& p) { + list.erase(std::remove_if(list.begin(), list.end(), + [&](const BT_FreeListEntry& e) { return e.pointer == p; }), + list.end()); +} + +// Encode free list: [entries...] [count (4 bytes)] +inline std::vector bt_encode(const std::vector& list) { + std::vector buf; + for (const auto& e : list) { + // Pointer (8 bytes) + appendIntLE(buf, e.pointer.address, typeInfo(BT_TypeId::POINTER).size); + // Size (4 bytes) + appendIntLE(buf, e.size, 4); + } + // Entry count (4 bytes) + appendIntLE(buf, static_cast(list.size()), 4); + return buf; +} + +// 64-bit FNV-1a hash for strings +inline int64_t bt_hash(const std::string& s) { + uint64_t hash = 0xcbf29ce484222325ULL; + for (unsigned char c : s) { + hash ^= c; + hash *= 0x100000001b3ULL; + } + return static_cast(hash); +} + +// A variant for decoding results (including arrays by handle) +using DecodedValue = std::variant>; + +// Reference to a value at a file pointer +class BT_Reference { +public: + BT_Reference(BinaryTable* table, BT_Pointer pointer) + : table_(table), pointer_(pointer) {} + + DecodedValue decodeValue(); + + // Size in bytes of the stored value (minimal reads) + virtual int32_t size(); + + const BT_Pointer& pointer() const { return pointer_; } + + std::string toString() const { return pointer_.toString(); } + +protected: + BinaryTable* table_; + BT_Pointer pointer_; + + friend class BT_UniformArray; + friend class BinaryTable; +}; + +// Uniform array wrapper (random access) class BT_UniformArray : public BT_Reference { public: - BT_UniformArray(BinaryTable* table, BT_Pointer pointer) : BT_Reference(table, pointer) {} + BT_UniformArray(BinaryTable* table, BT_Pointer pointer) + : BT_Reference(table, pointer) {} - int getLength(); - T operator[](int index); - void set(int index, const T& value); - void add(const T& value); - void addAll(const std::vector& values); - BT_Type getElementType(); - int getSize(); + int32_t length(); + DecodedValue get(int index); + void set(int index, const ValueInput& value); + void add(const ValueInput& value) { addAll(std::vector{value}); } + void addAll(const std::vector& values); + + BT_TypeId elementType() const; + + int32_t size() override; // total bytes used + + std::string toString(bool readValues = false); + +private: + BT_TypeId elementTypeOrThrow() const; }; +// Binary Table class BinaryTable { -private: - std::fstream _file; - std::string _filename; - bool freeListLifted = false; - std::vector _freeListCache; - - uint64_t fnv1aHash(const std::string& str) { - uint64_t hash = 0xcbf29ce484222325ULL; // FNV offset basis - for (char c : str) { - hash ^= static_cast(c); - hash *= 0x100000001b3ULL; // FNV prime - } - return hash; - } - - int32_t readInt32(std::streampos pos) { - _file.seekg(pos); - uint8_t bytes[4]; - _file.read(reinterpret_cast(bytes), 4); - return static_cast(bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)); - } - - void writeInt32(std::streampos pos, int32_t value) { - _file.seekp(pos); - uint8_t bytes[4] = { - static_cast(value & 0xFF), - static_cast((value >> 8) & 0xFF), - static_cast((value >> 16) & 0xFF), - static_cast((value >> 24) & 0xFF) - _file.write(reinterpret_cast(bytes), 4); - }; - } - - _file.seekg(pos); - int64_t readInt64(std::streampos pos) { - uint8_t bytes[8]; - _file.read(reinterpret_cast(bytes), 8); - int64_t result = 0; - for (int i = 0; i < 8; i++) { - result |= static_cast(bytes[i]) << (i * 8); - } - return result; - } - - void writeInt64(std::streampos pos, int64_t value) { - _file.seekp(pos); - uint8_t bytes[8]; - for (int i = 0; i < 8; i++) { - bytes[i] = static_cast((value >> (i * 8)) & 0xFF); - } - _file.write(reinterpret_cast(bytes), 8); - } - - BT_Pointer readPointer(std::streampos pos) { - return BT_Pointer(readInt64(pos)); - } - - void writePointer(std::streampos pos, BT_Pointer pointer) { - writeInt64(pos, pointer.address); - } - - float readFloat32(std::streampos pos) { - _file.seekg(pos); - uint8_t bytes[4]; - _file.read(reinterpret_cast(bytes), 4); - uint32_t val = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); - union { float f; uint32_t i; } converter; - converter.i = val; - return converter.f; - } - - std::streampos getFileSize() { - auto currentPos = _file.tellg(); - _file.seekg(0, std::ios::end); - auto size = _file.tellg(); - _file.seekg(currentPos); - return size; - } - - std::map getAddressTable() { - BT_Pointer tablePtr = readPointer(0); - std::map addressTable; - - if (tablePtr.isNull()) { - return addressTable; - } - - _file.seekg(tablePtr.address + 1); // Skip type byte - int32_t tableCount = readInt32(_file.tellg()); - _file.seekg(static_cast(_file.tellg()) + 4); - - for (int i = 0; i < tableCount; i++) { - uint64_t keyHash = static_cast(readInt64(_file.tellg())); - _file.seekg(static_cast(_file.tellg()) + 8); - - BT_Pointer valuePtr = readPointer(_file.tellg()); - _file.seekg(static_cast(_file.tellg()) + 8); - - addressTable[keyHash] = valuePtr; - } - - return addressTable; - } - - void setAddressTable(const std::map& table) { - std::vector buffer; - buffer.push_back(static_cast(BT_Type::ADDRESS_TABLE)); - - // Write count - uint32_t count = static_cast(table.size()); - buffer.push_back(count & 0xFF); - buffer.push_back((count >> 8) & 0xFF); - buffer.push_back((count >> 16) & 0xFF); - buffer.push_back((count >> 24) & 0xFF); - - // Write entries - for (const auto& [key, value] : table) { - // Key hash (8 bytes) - for (int i = 0; i < 8; i++) { - buffer.push_back(static_cast((key >> (i * 8)) & 0xFF)); - } - // Value pointer (8 bytes) - for (int i = 0; i < 8; i++) { - buffer.push_back(static_cast((value.address >> (i * 8)) & 0xFF)); - } - } - - // Read old table pointer - BT_Pointer oldTablePtr = readPointer(0); - - // Allocate new space - BT_Pointer tableAddress = alloc(buffer.size()); - - // Write new table - _file.seekp(tableAddress.address); - _file.write(reinterpret_cast(buffer.data()), buffer.size()); - - // Update header - writePointer(0, tableAddress); - - // Free old table if it exists - if (!oldTablePtr.isNull() && oldTablePtr != tableAddress) { - BT_Reference oldRef(this, oldTablePtr); - free(oldTablePtr, oldRef.getSize()); - } - } - - std::vector getFreeList() { - if (freeListLifted) { - return _freeListCache; - } - - auto fileSize = getFileSize(); - int32_t entryCount = readInt32(fileSize - 4); - - if (entryCount == 0) { - return {}; - } - - int entrySize = 8 + 4; // Pointer + Size - int freeListSize = entryCount * entrySize; - - _file.seekg(fileSize - 4 - freeListSize); - - std::vector freeList; - for (int i = 0; i < entryCount; i++) { - BT_Pointer pointer = readPointer(_file.tellg()); - _file.seekg(static_cast(_file.tellg()) + 8); - - int32_t size = readInt32(_file.tellg()); - _file.seekg(static_cast(_file.tellg()) + 4); - - freeList.emplace_back(pointer, size); - } - - return freeList; - } - - void setFreeList(const std::vector& list) { - if (freeListLifted) { - _freeListCache = list; - return; - } - - auto fileSize = getFileSize(); - int32_t oldEntryCount = readInt32(fileSize - 4); - int oldListSize = (oldEntryCount * (8 + 4)) + 4; - - // Truncate old free list - _file.close(); - _file.open(_filename, std::ios::in | std::ios::out | std::ios::binary); - _file.seekp(0, std::ios::end); - auto newSize = static_cast(fileSize) - oldListSize; - _file.seekp(newSize); - - // Write new free list - for (const auto& entry : list) { - writeInt64(_file.tellp(), entry.pointer.address); - _file.seekp(static_cast(_file.tellp()) + 8); - writeInt32(_file.tellp(), entry.size); - _file.seekp(static_cast(_file.tellp()) + 4); - } - - // Write entry count - writeInt32(_file.tellp(), static_cast(list.size())); - } - public: - BinaryTable(const std::string& path) : _filename(path) { - _file.open(path, std::ios::in | std::ios::out | std::ios::binary); - if (!_file.is_open()) { - // Create file if it doesn't exist - std::ofstream createFile(path, std::ios::binary); - createFile.close(); - _file.open(path, std::ios::in | std::ios::out | std::ios::binary); - } + explicit BinaryTable(const std::string& path) + : file_(path) {} + + void initialise() { + file_.setPosition(0); + file_.writePointer(BT_Null); // Address table pointer + file_.writeInt(0, 4); // Free list entry count } - ~BinaryTable() { - if (_file.is_open()) { - _file.close(); - } - } - - void initialize() { - _file.seekp(0); - writePointer(0, BT_Null); // Address table pointer - writeInt32(8, 0); // Free list entry count - } - - void liftFreeList() { - if (freeListLifted) { - throw std::runtime_error("Free list is already lifted"); - } - - _freeListCache = getFreeList(); - - auto fileSize = getFileSize(); - int32_t oldEntryCount = readInt32(fileSize - 4); - int oldFreeListSize = oldEntryCount * (8 + 4) + 4; - - // Truncate file to remove free list - _file.close(); - _file.open(_filename, std::ios::in | std::ios::out | std::ios::binary); - _file.seekp(0, std::ios::end); - auto newSize = static_cast(fileSize) - oldFreeListSize; - _file.seekp(newSize); - - freeListLifted = true; - } - - void dropFreeList() { - if (!freeListLifted) { - throw std::runtime_error("Free list is not lifted"); - } - - _file.seekp(0, std::ios::end); - writeInt32(_file.tellp(), 0); // Placeholder - - freeListLifted = false; - setFreeList(_freeListCache); - _freeListCache.clear(); - } - - void antiFreeListScope(std::function fn) { - liftFreeList(); - try { - fn(); - } catch (...) { - dropFreeList(); - throw; - } - dropFreeList(); - } - - void free(BT_Pointer pointer, int size) { - if (!freeListLifted) { - throw std::runtime_error("Free list must be lifted before freeing memory"); - } - - if (pointer.isNull() || size <= 0) { - throw std::invalid_argument("Cannot free null pointer or zero size"); - } - - auto freeList = getFreeList(); - freeList.emplace_back(pointer, size); - - // Merge contiguous blocks - std::sort(freeList.begin(), freeList.end(), - [](const BT_FreeListEntry& a, const BT_FreeListEntry& b) { - return a.pointer.address < b.pointer.address; - }); - - std::vector merged; - for (const auto& entry : freeList) { - if (merged.empty()) { - merged.push_back(entry); - } else { - auto& last = merged.back(); - if (last.pointer.address + last.size == entry.pointer.address) { - last.size += entry.size; - } else { - merged.push_back(entry); - } - } - } - - setFreeList(merged); - } - - BT_Pointer alloc(int size) { - if (!freeListLifted) { - throw std::runtime_error("Free list must be lifted before allocation"); - } - - auto freeList = getFreeList(); - - if (freeList.empty()) { - return BT_Pointer(getFileSize()); - } - - // Find best fit - BT_FreeListEntry* bestFit = nullptr; - for (auto& entry : freeList) { - if (entry.size >= size) { - bestFit = &entry; - break; - } - } - - if (!bestFit) { - return BT_Pointer(getFileSize()); - } - - BT_Pointer allocatedPointer = bestFit->pointer; - - if (bestFit->size == size) { - // Exact fit - remove entry - freeList.erase(std::remove_if(freeList.begin(), freeList.end(), - [allocatedPointer](const BT_FreeListEntry& entry) { - return entry.pointer == allocatedPointer; - }), freeList.end()); - } else { - // Split block - bestFit->pointer = BT_Pointer(bestFit->pointer.address + size); - bestFit->size -= size; - } - - setFreeList(freeList); - return allocatedPointer; - } - - template - void set(const std::string& key, const T& value) { + // Set key = value + void set(const std::string& key, const ValueInput& value) { antiFreeListScope([&]() { auto addressTable = getAddressTable(); - uint64_t keyHash = fnv1aHash(key); - + int64_t keyHash = bt_hash(key); if (addressTable.find(keyHash) != addressTable.end()) { throw std::runtime_error("Key already exists"); } auto valueBuffer = encodeValue(value); - BT_Pointer valueAddress = alloc(valueBuffer.size()); - _file.seekp(valueAddress.address); - _file.write(reinterpret_cast(valueBuffer.data()), valueBuffer.size()); + // Allocate and write value + BT_Pointer valueAddress = alloc(static_cast(valueBuffer.size())); + file_.setPosition(valueAddress.address); + file_.write(valueBuffer); + // Update address table addressTable[keyHash] = valueAddress; setAddressTable(addressTable); }); } - template - T get(const std::string& key) { + // Get decoded value + DecodedValue get(const std::string& key) { auto addressTable = getAddressTable(); - uint64_t keyHash = fnv1aHash(key); + int64_t keyHashV = bt_hash(key); + auto it = addressTable.find(keyHashV); + if (it == addressTable.end()) throw std::runtime_error("Key does not exist"); - auto it = addressTable.find(keyHash); - if (it == addressTable.end()) { - throw std::runtime_error("Key does not exist"); - } - - BT_Reference valueRef(this, it->second); - return valueRef.decodeValue(); + BT_Pointer valuePtr = it->second; + BT_Reference ref(this, valuePtr); + return ref.decodeValue(); } - void remove(const std::string& key) { + void erase(const std::string& key) { antiFreeListScope([&]() { auto addressTable = getAddressTable(); - uint64_t keyHash = fnv1aHash(key); + int64_t keyHashV = bt_hash(key); + auto it = addressTable.find(keyHashV); + if (it == addressTable.end()) throw std::runtime_error("Key does not exist"); - auto it = addressTable.find(keyHash); - if (it == addressTable.end()) { - throw std::runtime_error("Key does not exist"); - } + BT_Pointer valuePointer = it->second; + BT_Reference valueRef(this, valuePointer); - BT_Reference valueRef(this, it->second); - free(it->second, valueRef.getSize()); + // Free the value + free(valuePointer, valueRef.size()); - addressTable.erase(it); + // Remove from address table + addressTable.erase(keyHashV); setAddressTable(addressTable); }); } - // Friend declarations for template access - template friend T BT_Reference::decodeValue(); - friend int BT_Reference::getSize(); - template friend int BT_UniformArray::getLength(); - template friend T BT_UniformArray::operator[](int index); - template friend void BT_UniformArray::set(int index, const T& value); - template friend void BT_UniformArray::add(const T& value); - template friend void BT_UniformArray::addAll(const std::vector& values); - template friend BT_Type BT_UniformArray::getElementType(); - template friend int BT_UniformArray::getSize(); + // Try to truncate file by reclaiming trailing free block + void truncate() { + antiFreeListScope([&]() { + // Relocate the address table if possible + setAddressTable(getAddressTable()); + + auto freeList = getFreeList(); + std::sort(freeList.begin(), freeList.end(), + [](const BT_FreeListEntry& a, const BT_FreeListEntry& b) { return a.pointer.address < b.pointer.address; }); + + if (freeList.empty()) return; + + auto lastEntry = freeList.back(); + int64_t fileEnd = file_.length(); + int64_t expectedEnd = lastEntry.pointer.address + lastEntry.size; + if (expectedEnd != fileEnd) return; + + // Remove the last entry and update free list + freeList.pop_back(); + setFreeList(freeList); + + // Truncate file + int64_t newLength = lastEntry.pointer.address; + file_.truncate(newLength); + }); + } + + // Internals used by BT_Reference / BT_UniformArray + RandomAccessFile& file() { return file_; } + + void antiFreeListScope(const std::function& fn) { + liftFreeList(); + try { + fn(); + dropFreeList(); + } catch (...) { + try { + dropFreeList(); + } catch (...) { + // swallow to not throw during unwinding + } + throw; + } + } + + // Free memory region: adds to free list and merges contiguous + void free(BT_Pointer pointer, int32_t size) { + if (!freeListLifted_) throw std::runtime_error("Free list must be lifted before freeing memory"); + if (pointer.isNull() || size <= 0) throw std::invalid_argument("Cannot free null pointer or zero size"); + + auto freeList = getFreeList(); + freeList.push_back(BT_FreeListEntry{pointer, size}); + + // Merge contiguous blocks + if (!freeList.empty()) { + std::sort(freeList.begin(), freeList.end(), + [](const BT_FreeListEntry& a, const BT_FreeListEntry& b) { return a.pointer.address < b.pointer.address; }); + std::vector merged; + for (const auto& e : freeList) { + if (merged.empty()) { + merged.push_back(e); + } else { + auto& last = merged.back(); + if (last.pointer.address + last.size == e.pointer.address) { + last.size += e.size; + } else { + merged.push_back(e); + } + } + } + setFreeList(merged); + } else { + setFreeList(freeList); + } + } + + // Allocate memory region + BT_Pointer alloc(int32_t size) { + if (!freeListLifted_) throw std::runtime_error("Free list must be lifted before allocation"); + + auto freeList = getFreeList(); + + // No free blocks: allocate at end + if (freeList.empty()) { + return BT_Pointer(file_.length()); + } + + // First-fit block + std::optional bestFit; + for (const auto& entry : freeList) { + if (entry.size >= size) { + bestFit = entry; + break; + } + } + + if (!bestFit.has_value()) { + return BT_Pointer(file_.length()); + } + + bool exactFit = bestFit->size == size; + if (exactFit) { + BT_Pointer allocated = bestFit->pointer; + removePointer(freeList, allocated); + setFreeList(freeList); + return allocated; + } else { + BT_Pointer allocated = bestFit->pointer; + BT_FreeListEntry remainder{BT_Pointer(bestFit->pointer.address + size), bestFit->size - size}; + removePointer(freeList, allocated); + freeList.push_back(remainder); + setFreeList(freeList); + return allocated; + } + } + + // Address table getters/setters + std::unordered_map getAddressTable() { + file_.setPosition(0); + BT_Reference tableRef(this, file_.readPointer()); + if (tableRef.pointer_.isNull()) { + return {}; + } + + // Skip type byte (ADDRESS_TABLE) + file_.setPosition(tableRef.pointer_.address + 1); + int32_t tableCount = static_cast(file_.readInt(4)); + + std::unordered_map map; + for (int32_t i = 0; i < tableCount; ++i) { + int64_t keyHash = file_.readInt(8); + int64_t valueAddr = file_.readInt(typeInfo(BT_TypeId::POINTER).size); + map[keyHash] = BT_Pointer(valueAddr); + } + return map; + } + + void setAddressTable(const std::unordered_map& table) { + // Build buffer + std::vector buf; + buf.push_back(static_cast(BT_TypeId::ADDRESS_TABLE)); + appendIntLE(buf, static_cast(table.size()), 4); + for (const auto& kv : table) { + appendIntLE(buf, kv.first, 8); + appendIntLE(buf, kv.second.address, typeInfo(BT_TypeId::POINTER).size); + } + + // Write new address table at end + BT_Pointer tableAddress = alloc(static_cast(buf.size())); + file_.setPosition(tableAddress.address); + file_.write(buf); + + // Read old table pointer before updating + file_.setPosition(0); + BT_Reference oldTableRef(this, file_.readPointer()); + + // Update header to point to new table + file_.setPosition(0); + file_.writePointer(tableAddress); + + // Free old table if exists and isn't same + if (!oldTableRef.pointer_.isNull() && oldTableRef.pointer_ != tableAddress) { + free(oldTableRef.pointer_, oldTableRef.size()); + } + } + + // Free list load/store + std::vector getFreeList() { + if (freeListLifted_) { + return freeListCache_.value_or(std::vector{}); + } + + if (file_.length() < 4) return {}; + file_.setPosition(file_.length() - 4); + int32_t entryCount = static_cast(file_.readInt(4)); + if (entryCount == 0) return {}; + + int32_t entrySize = typeInfo(BT_TypeId::POINTER).size + 4; + int64_t freeListSize = static_cast(entryCount) * entrySize; + file_.setPosition(file_.length() - 4 - freeListSize); + + std::vector list; + list.reserve(entryCount); + for (int32_t i = 0; i < entryCount; ++i) { + int64_t ptrAddr = file_.readInt(typeInfo(BT_TypeId::POINTER).size); + int32_t sz = static_cast(file_.readInt(4)); + list.push_back(BT_FreeListEntry{BT_Pointer(ptrAddr), sz}); + } + return list; + } + + void setFreeList(const std::vector& list) { + if (freeListLifted_) { + freeListCache_ = list; + return; + } + + // Remove old free list + file_.setPosition(file_.length() - 4); + int32_t oldEntryCount = static_cast(file_.readInt(4)); + int64_t oldListSize = static_cast(oldEntryCount) * (typeInfo(BT_TypeId::POINTER).size + 4) + 4; + file_.truncate(file_.length() - oldListSize); + + // Append new free list + auto buf = bt_encode(list); + file_.setPosition(file_.length()); + file_.write(buf); + } + + void liftFreeList() { + if (freeListLifted_) throw std::runtime_error("Free list is already lifted"); + + freeListCache_ = getFreeList(); + + // Remove it from the file + file_.setPosition(file_.length() - 4); + int32_t oldEntryCount = static_cast(file_.readInt(4)); + int32_t oldEntrySize = typeInfo(BT_TypeId::POINTER).size + 4; + int64_t oldFreeListSize = static_cast(oldEntryCount) * oldEntrySize + 4; + file_.truncate(file_.length() - oldFreeListSize); + + freeListLifted_ = true; + } + + void dropFreeList() { + if (!freeListLifted_) throw std::runtime_error("Free list is not lifted"); + + // Write placeholder count + file_.setPosition(file_.length()); + file_.writeInt(0, 4); + + freeListLifted_ = false; + setFreeList(freeListCache_.value_or(std::vector{})); + freeListCache_.reset(); + } + +private: + RandomAccessFile file_; + bool freeListLifted_{false}; + std::optional> freeListCache_; }; -// Template implementations -template -T BT_Reference::decodeValue() { - if (_pointer.isNull()) { - throw std::runtime_error("Null pointer"); - } +// BT_Reference implementations +DecodedValue BT_Reference::decodeValue() { + if (pointer_.isNull()) return std::monostate{}; - _table->_file.seekg(_pointer.address); - uint8_t typeId; - _table->_file.read(reinterpret_cast(&typeId), 1); - BT_Type type = static_cast(typeId); - - if constexpr (std::is_same_v) { - if (type != BT_Type::INTEGER) { - throw std::runtime_error("Type mismatch"); - } - return _table->readInt32(_table->_file.tellg()); - } else if constexpr (std::is_same_v) { - if (type != BT_Type::FLOAT) { - throw std::runtime_error("Type mismatch"); - } - return _table->readFloat32(_table->_file.tellg()); - } else if constexpr (std::is_same_v) { - if (type != BT_Type::STRING) { - throw std::runtime_error("Type mismatch"); - } - int32_t length = _table->readInt32(_table->_file.tellg()); - _table->_file.seekg(static_cast(_table->_file.tellg()) + 4); - - std::string result(length, '\0'); - _table->_file.read(&result[0], length); - return result; - } else if constexpr (std::is_same_v>) { - if (type != BT_Type::INTEGER_ARRAY) { - throw std::runtime_error("Type mismatch"); - } - return BT_UniformArray(_table, _pointer); - } else if constexpr (std::is_same_v>) { - if (type != BT_Type::FLOAT_ARRAY) { - throw std::runtime_error("Type mismatch"); - } - return BT_UniformArray(_table, _pointer); - } else { - static_assert(sizeof(T) == 0, "Unsupported type for decoding"); - } -} - -int BT_Reference::getSize() { - if (_pointer.isNull()) { - return 0; - } - - _table->_file.seekg(_pointer.address); - uint8_t typeId; - _table->_file.read(reinterpret_cast(&typeId), 1); - BT_Type type = static_cast(typeId); + table_->file().setPosition(pointer_.address); + uint8_t typeId = table_->file().readByte(); + BT_TypeId type = typeFromId(typeId); switch (type) { - case BT_Type::INTEGER: - return 1 + 4; - case BT_Type::FLOAT: - return 1 + 4; - case BT_Type::STRING: { - int32_t length = _table->readInt32(_table->_file.tellg()); - return 1 + 4 + length; + case BT_TypeId::INTEGER: { + int32_t v = static_cast(table_->file().readInt(4)); + return v; } - case BT_Type::ADDRESS_TABLE: { - int32_t count = _table->readInt32(_table->_file.tellg()); - return 1 + 4 + count * (8 + 8); + case BT_TypeId::FLOAT: { + double v = table_->file().readFloat32(); + return v; + } + case BT_TypeId::STRING: { + int32_t len = static_cast(table_->file().readInt(4)); + auto bytes = table_->file().read(len); + return std::string(bytes.begin(), bytes.end()); + } + case BT_TypeId::ADDRESS_TABLE: { + throw std::runtime_error("Address table decoding not implemented"); + } + case BT_TypeId::POINTER: { + BT_Pointer p = table_->file().readPointer(); + return p; + } + case BT_TypeId::INTEGER_ARRAY: + case BT_TypeId::FLOAT_ARRAY: { + return std::make_shared(table_, pointer_); } default: - throw std::runtime_error("Unsupported type for size calculation"); + throw std::runtime_error("Unsupported type"); } } -template -int BT_UniformArray::getLength() { - if (_pointer.isNull()) { - return 0; +int32_t BT_Reference::size() { + if (pointer_.isNull()) return 0; + + table_->file().setPosition(pointer_.address); + BT_TypeId type = typeFromId(table_->file().readByte()); + + if (type == BT_TypeId::INTEGER) { + return 1 + 4; + } else if (type == BT_TypeId::FLOAT) { + return 1 + 4; + } else if (type == BT_TypeId::STRING) { + int32_t length = static_cast(table_->file().readInt(4)); + return 1 + 4 + length; + } else if (type == BT_TypeId::ADDRESS_TABLE) { + int32_t count = static_cast(table_->file().readInt(4)); + return 1 + 4 + count * (8 + typeInfo(BT_TypeId::POINTER).size); + } else { + throw std::runtime_error("Unsupported type for size()"); } - - _table->_file.seekg(_pointer.address); - uint8_t typeId; - _table->_file.read(reinterpret_cast(&typeId), 1); - BT_Type type = static_cast(typeId); - - if (!isBT_TypeArray(type)) { - throw std::runtime_error("Not an array"); - } - - return _table->readInt32(_table->_file.tellg()); } -template -T BT_UniformArray::operator[](int index) { - if (_pointer.isNull()) { - throw std::runtime_error("Null pointer"); - } +// BT_UniformArray implementations +int32_t BT_UniformArray::length() { + if (pointer_.isNull()) return 0; - int len = getLength(); - if (index < 0 || index >= len) { - throw std::out_of_range("Index out of range"); - } + table_->file().setPosition(pointer_.address); + BT_TypeId containerType = typeFromId(table_->file().readByte()); + if (!typeInfo(containerType).arrayType) throw std::runtime_error("Not an array"); - // Read element type - _table->_file.seekg(_pointer.address + 1 + 4); - uint8_t typeId; - _table->_file.read(reinterpret_cast(&typeId), 1); - BT_Type type = static_cast(typeId); - - int itemOffset = index * (1 + getBT_TypeSize(type)); - BT_Reference itemRef(_table, BT_Pointer(_pointer.address + 1 + 4 + itemOffset)); - return itemRef.decodeValue(); + return static_cast(table_->file().readInt(4)); } -template -void BT_UniformArray::set(int index, const T& value) { - if (_pointer.isNull()) { - throw std::runtime_error("Null pointer"); +BT_TypeId BT_UniformArray::elementTypeOrThrow() const { + if (pointer_.isNull()) throw std::runtime_error("Null pointer"); + // Read first element type + const_cast(table_)->file().setPosition(pointer_.address + 1 + 4); + uint8_t typeId = const_cast(table_)->file().readByte(); + return typeFromId(typeId); +} + +BT_TypeId BT_UniformArray::elementType() const { + if (length() == 0) { + // No elements; undefined + return BT_TypeId::INTEGER; // dummy, not used + } + return elementTypeOrThrow(); +} + +DecodedValue BT_UniformArray::get(int index) { + if (pointer_.isNull()) throw std::runtime_error("Null pointer"); + + int32_t len = length(); + if (index < 0 || index >= len) throw std::out_of_range("Index out of range"); + + // Determine element type + table_->file().setPosition(pointer_.address + 1 + 4); + BT_TypeId type = typeFromId(table_->file().readByte()); + + int itemStride = 1 + typeInfo(type).size; // type byte + fixed data + BT_Reference itemRef(table_, BT_Pointer((pointer_.address + 1 + 4) + static_cast(index) * itemStride)); + return itemRef.decodeValue(); +} + +void BT_UniformArray::set(int index, const ValueInput& value) { + if (pointer_.isNull()) throw std::runtime_error("Null pointer"); + + int32_t len = length(); + if (index < 0 || index >= len) throw std::out_of_range("Index out of range"); + + // Determine element type + table_->file().setPosition(pointer_.address + 1 + 4); + BT_TypeId type = typeFromId(table_->file().readByte()); + if (typeInfo(type).size == -1) { + throw std::runtime_error("Variable-size types not supported in uniform arrays."); } - int len = getLength(); - if (index < 0 || index >= len) { - throw std::out_of_range("Index out of range"); - } - - // Read element type - _table->_file.seekg(_pointer.address + 1 + 4); - uint8_t typeId; - _table->_file.read(reinterpret_cast(&typeId), 1); - BT_Type type = static_cast(typeId); - - BT_Type newValueType = getBT_TypeFromValue(); + // Ensure new value type matches + BT_TypeId newValueType = typeFromDynamic(value); if (newValueType != type) { + throw std::runtime_error("Type mismatch in BT_UniformArray::set()"); } - int itemOffset = index * (1 + getBT_TypeSize(type)); - BT_Pointer itemPointer(_pointer.address + 1 + 4 + itemOffset); + int itemStride = 1 + typeInfo(type).size; // header + payload + BT_Pointer itemPointer((pointer_.address + 1 + 4) + static_cast(index) * itemStride); auto valueBuffer = encodeValue(value); - _table->_file.seekp(itemPointer.address); - _table->_file.write(reinterpret_cast(valueBuffer.data()), valueBuffer.size()); + table_->file().setPosition(itemPointer.address); + table_->file().write(valueBuffer); } -template -void BT_UniformArray::add(const T& value) { - addAll({value}); -} +void BT_UniformArray::addAll(const std::vector& values) { + if (values.empty()) return; -template -void BT_UniformArray::addAll(const std::vector& values) { - _table->antiFreeListScope([&]() { - BT_Type elementType = getElementType(); + table_->antiFreeListScope([&]() { + // Determine type by existing element or new value's first element + BT_TypeId type; + if (length() > 0) { + table_->file().setPosition(pointer_.address + 1 + 4); + type = typeFromId(table_->file().readByte()); + } else { + type = typeFromDynamic(values.front()); + } - // Validate types - for (const auto& value : values) { - BT_Type newValueType = getBT_TypeFromValue(); - if (newValueType != elementType) { - throw std::runtime_error("Type mismatch"); + // Validate new values + for (size_t i = 0; i < values.size(); ++i) { + BT_TypeId t = typeFromDynamic(values[i]); + if (t != type) { + std::ostringstream oss; + oss << "Type mismatch at index " << i; + throw std::runtime_error(oss.str()); + } + if (typeInfo(t).size == -1) { + throw std::runtime_error("Variable-size types not supported in uniform arrays."); } } - // Read current array - int currentLength = getLength(); - int elementSize = 1 + getBT_TypeSize(elementType); - int bufferSize = 1 + 4 + currentLength * elementSize; + int32_t oldLen = length(); + int itemStride = 1 + typeInfo(type).size; + int32_t oldBufferSize = 1 + 4 + oldLen * itemStride; - _table->_file.seekg(_pointer.address); - std::vector fullBuffer(bufferSize); - _table->_file.read(reinterpret_cast(fullBuffer.data()), bufferSize); + // Read full existing buffer + table_->file().setPosition(pointer_.address); + auto fullBuffer = table_->file().read(oldBufferSize); - // Add new values - for (const auto& value : values) { - auto valueBuffer = encodeValue(value); - fullBuffer.insert(fullBuffer.end(), valueBuffer.begin(), valueBuffer.end()); + // Append new values + for (const auto& v : values) { + auto enc = encodeValue(v); + fullBuffer.insert(fullBuffer.end(), enc.begin(), enc.end()); } - // Update length - int newLength = currentLength + values.size(); - uint32_t len = static_cast(newLength); - fullBuffer[1] = len & 0xFF; - fullBuffer[2] = (len >> 8) & 0xFF; - fullBuffer[3] = (len >> 16) & 0xFF; - fullBuffer[4] = (len >> 24) & 0xFF; + // Update length in buffer (little-endian at offset 1) + int32_t newLength = oldLen + static_cast(values.size()); + fullBuffer[1] = static_cast(newLength & 0xFF); + fullBuffer[2] = static_cast((newLength >> 8) & 0xFF); + fullBuffer[3] = static_cast((newLength >> 16) & 0xFF); + fullBuffer[4] = static_cast((newLength >> 24) & 0xFF); // Free old array - _table->free(_pointer, getSize()); + int32_t oldSizeBytes = this->size(); + table_->free(pointer_, oldSizeBytes); - // Allocate new space - BT_Pointer newPointer = _table->alloc(fullBuffer.size()); + // Allocate new + BT_Pointer newPtr = table_->alloc(static_cast(fullBuffer.size())); - // Update address table references - auto addressTable = _table->getAddressTable(); - for (auto& [key, value] : addressTable) { - if (value == _pointer) { - value = newPointer; + // Replace references in address table + auto addressTable = table_->getAddressTable(); + for (auto& kv : addressTable) { + if (kv.second == pointer_) { + std::cout << "Updating address table entry for key " << kv.first + << " from " << pointer_.toString() << " to " << newPtr.toString() << "\n"; + kv.second = newPtr; } } - _table->setAddressTable(addressTable); - _pointer = newPointer; + table_->setAddressTable(addressTable); - // Write updated buffer - _table->_file.seekp(newPointer.address); - _table->_file.write(reinterpret_cast(fullBuffer.data()), fullBuffer.size()); + // Update this pointer + pointer_ = newPtr; + + // Write new buffer + table_->file().setPosition(newPtr.address); + table_->file().write(fullBuffer); + + std::cout << "Array resized to new length " << newLength << " at " << newPtr.toString() << "\n"; }); } -template -BT_Type BT_UniformArray::getElementType() { - if (getLength() == 0) { - return getBT_TypeFromValue(); - } - - _table->_file.seekg(_pointer.address + 1 + 4); - uint8_t typeId; - _table->_file.read(reinterpret_cast(&typeId), 1); - return static_cast(typeId); +int32_t BT_UniformArray::size() { + int32_t len = length(); + if (len == 0) return 1 + 4; + // Read element type + table_->file().setPosition(pointer_.address + 1 + 4); + BT_TypeId t = typeFromId(table_->file().readByte()); + return 1 + 4 + len * (1 + typeInfo(t).size); } -template -int BT_UniformArray::getSize() { - int len = getLength(); - if (len == 0) { - return 1 + 4; +std::string BT_UniformArray::toString(bool readValues) { + std::ostringstream oss; + if (readValues) { + oss << "Uniform Array of length " << length(); + return oss.str(); } - BT_Type elementType = getElementType(); - return 1 + 4 + len * (1 + getBT_TypeSize(elementType)); + oss << "Uniform Array: ["; + int32_t len = length(); + for (int i = 0; i < len; ++i) { + auto v = get(i); + if (i > 0) oss << ", "; + if (std::holds_alternative(v)) { + oss << std::get(v); + } else if (std::holds_alternative(v)) { + oss << std::get(v); + } else if (std::holds_alternative(v)) { + oss << "\"" << std::get(v) << "\""; + } else if (std::holds_alternative(v)) { + oss << std::get(v).toString(); + } else { + oss << "?"; + } + } + oss << "]"; + return oss.str(); } +// Binary dump for display std::string binaryDump(const std::vector& data) { - std::stringstream buffer; + std::ostringstream buffer; for (size_t i = 0; i < data.size(); i += 16) { - // Address - buffer << "0x" << std::setfill('0') << std::setw(4) << std::hex << std::uppercase << i - << " (" << std::setfill(' ') << std::setw(4) << std::dec << i << ") | "; + buffer << "0x" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << i + << std::dec << " (" << std::setw(4) << std::setfill(' ') << i << ") | "; // Hex bytes - for (int j = 0; j < 16; j++) { + for (int j = 0; j < 16; ++j) { if (i + j < data.size()) { - buffer << std::setfill('0') << std::setw(2) << std::hex << std::uppercase + buffer << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i + j]) << " "; } else { buffer << " "; @@ -855,10 +1006,9 @@ std::string binaryDump(const std::vector& data) { buffer << " | "; // Integer representation - for (int j = 0; j < 16; j++) { + for (int j = 0; j < 16; ++j) { if (i + j < data.size()) { - buffer << std::setfill(' ') << std::setw(3) << std::dec - << static_cast(data[i + j]) << " "; + buffer << std::dec << std::setw(3) << std::setfill(' ') << static_cast(data[i + j]) << " "; } else { buffer << " "; } @@ -867,9 +1017,9 @@ std::string binaryDump(const std::vector& data) { buffer << " | "; // ASCII representation - for (int j = 0; j < 16; j++) { + for (int j = 0; j < 16; ++j) { if (i + j < data.size()) { - uint8_t byte = data[i + j]; + int byte = data[i + j]; if (byte >= 32 && byte <= 126) { buffer << static_cast(byte); } else { @@ -885,70 +1035,85 @@ std::string binaryDump(const std::vector& data) { return buffer.str(); } +// Convenience: read full file into bytes +std::vector readAllBytes(const std::string& path) { + std::ifstream ifs(path, std::ios::binary); + ifs.seekg(0, std::ios::end); + std::streamsize size = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + std::vector buf(size); + if (size > 0) ifs.read(reinterpret_cast(buf.data()), size); + return buf; +} + +// MAIN demonstrating usage equivalent to the Dart example int main() { - // Remove existing file - std::remove("example.bin"); - - BinaryTable table("example.bin"); - table.initialize(); - - // Read file for dump - std::ifstream file("example.bin", std::ios::binary); - std::vector fileData((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - file.close(); - - std::cout << "File dump:\n"; - std::cout << binaryDump(fileData) << "\n"; - std::cout << "File size: " << fileData.size() << " bytes\n\n"; - - // Set values - table.set("int_array", std::vector{6, 3, 9, 2, 5}); - table.set("float_array", std::vector{1.5f, 2.5f, 3.5f}); - table.set("empty", std::vector{}); - - // Get arrays and modify - auto intArray = table.get>("int_array"); - auto floatArray = table.get>("float_array"); - - intArray.set(0, 1); - floatArray.set(1, 4.5f); - - std::cout << "int_array pointer: " << intArray.getPointer().toString() << "\n"; - std::cout << "float_array pointer: " << floatArray.getPointer().toString() << "\n"; - - intArray.add(10); - floatArray.add(5.5f); - - intArray.addAll({420, 69, 1337, 1738}); - floatArray.addAll({6.5f, 7.5f, 8.5f}); - - // Read back values - auto readback1 = table.get>("int_array"); - auto readback2 = table.get>("float_array"); - auto readback3 = table.get>("empty"); - - std::cout << "Readback1 length: " << readback1.getLength() << "\n"; - std::cout << "Readback2 length: " << readback2.getLength() << "\n"; - std::cout << "Readback3 length: " << readback3.getLength() << "\n"; - - // Print some values - if (readback1.getLength() > 0) { - std::cout << "First int: " << readback1[0] << "\n"; + const std::string filename = "example.bin"; + if (std::filesystem::exists(filename)) { + std::filesystem::remove(filename); } - if (readback2.getLength() > 0) { - std::cout << "First float: " << readback2[0] << "\n"; + { + // Create and initialise + BinaryTable table(filename); + table.initialise(); + + std::cout << "File dump:\n"; + std::cout << binaryDump(readAllBytes(filename)) << "\n"; + std::cout << "File size: " << std::filesystem::file_size(filename) << " bytes\n\n"; + + // Set arrays + table.set("int_array", std::vector{6, 3, 9, 2, 5}); + table.set("float_array", std::vector{1.5, 2.5, 3.5}); + table.set("empty", std::vector{}); + + // Modify elements + { + auto v = table.get("int_array"); + auto arr = std::get>(v); + arr->set(0, static_cast(1)); + } + { + auto v = table.get("float_array"); + auto arr = std::get>(v); + arr->set(1, static_cast(4.5)); + } + + { + auto v = table.get("int_array"); + auto arr = std::get>(v); + std::cout << "int_array pointer: " << arr->pointer_.toString() << "\n"; + } + { + auto v = table.get("float_array"); + auto arr = std::get>(v); + std::cout << "float_array pointer: " << arr->pointer_.toString() << "\n"; + } + + { + auto v = table.get("int_array"); + auto arr = std::get>(v); + arr->add(static_cast(10)); + arr->addAll({static_cast(420), static_cast(69), static_cast(1337), static_cast(1738)}); + } + { + auto v = table.get("float_array"); + auto arr = std::get>(v); + arr->add(static_cast(5.5)); + arr->addAll({6.5, 7.5, 8.5}); + } + + auto readback1 = table.get("int_array"); + auto readback2 = table.get("float_array"); + auto readback3 = table.get("empty"); + + std::cout << "Readback1: " << std::get>(readback1)->toString() << "\n"; + std::cout << "Readback2: " << std::get>(readback2)->toString() << "\n"; + std::cout << "Readback3: " << std::get>(readback3)->toString() << "\n\n"; + + std::cout << "File dump:\n"; + std::cout << binaryDump(readAllBytes(filename)) << "\n"; + std::cout << "File size: " << std::filesystem::file_size(filename) << " bytes\n"; } - // Final file dump - std::ifstream finalFile("example.bin", std::ios::binary); - std::vector finalData((std::istreambuf_iterator(finalFile)), - std::istreambuf_iterator()); - finalFile.close(); - - std::cout << "\nFile dump:\n"; - std::cout << binaryDump(finalData) << "\n"; - std::cout << "File size: " << finalData.size() << " bytes\n"; - return 0; } \ No newline at end of file