diff --git a/cpp/binary_table.h b/cpp/binary_table.h index 023dd88..908853e 100644 --- a/cpp/binary_table.h +++ b/cpp/binary_table.h @@ -1,5 +1,4 @@ /* - /$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$$$ /$$ /$$ /$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$$ |_ $$_/| $$$ /$$$| $$__ $$| $$_____/| $$$ | $$ |__ $$|_ $$_/ | $$$ | $$| $$_____/|__ $$__/ | $$ | $$$$ /$$$$| $$ \ $$| $$ | $$$$| $$ | $$ | $$ | $$$$| $$| $$ | $$ @@ -14,35 +13,21 @@ 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++. -This file was generated by ChatGPT based on the Dart implementation. May contain bugs. Only time will tell. */ -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include #include #include -#include -#include -using byte = uint8_t; -using Bytes = std::vector; - -// forward -struct BinaryTable; -struct BT_UniformArray; - -constexpr int POINTER_SIZE = 8; // same as BT_Type.POINTER in Dart - -enum class BT_Type : int { - POINTER = 0, // index 0 in Dart code held pointer size 8 +enum class BT_Type : uint8_t { + POINTER = 0, ADDRESS_TABLE = 1, INTEGER = 2, FLOAT = 3, @@ -51,866 +36,919 @@ enum class BT_Type : int { FLOAT_ARRAY = 6 }; -inline int bt_type_index(BT_Type t) { return static_cast(t); } +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; + } + default: return -1; // Variable size +} -// Value variant type -using Value = std::variant, std::vector, - uint64_t /*pointer address*/, std::shared_ptr>; +constexpr bool isBT_TypeArray(BT_Type type) { +} -struct BT_Pointer { +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 a = -1) : address(a) {} + + BT_Pointer(int64_t addr = -1) : address(addr) {} + bool isNull() const { return address == -1; } - bool operator==(const BT_Pointer& o) const { return address == o.address; } + + 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::ostringstream ss; + std::stringstream ss; ss << "0x" << std::hex << address << " (" << std::dec << address << ")"; return ss.str(); } }; -static const BT_Pointer BT_Null = BT_Pointer(-1); -// -// Utility: little-endian read/write helpers -// -int64_t readIntLE(std::fstream &f, int size) { - if (size < 1 || size > 8) throw std::invalid_argument("size must be 1..8"); - std::vector buf(size); - f.read(reinterpret_cast(buf.data()), size); - if (!f) throw std::runtime_error("readIntLE failed"); +const BT_Pointer BT_Null(-1); - uint64_t val = 0; - // little-endian - for (int i = size-1; i >= 0; --i) { - val = (val << 8) | buf[i]; - } - // sign extend - uint64_t signBit = (uint64_t)1 << (size*8 - 1); - if (val & signBit) { - // negative - int64_t signedVal = static_cast(val) - (static_cast(1) << (size*8)); - return signedVal; - } else { - return static_cast(val); - } -} +class BinaryTable; -void writeIntLE(std::fstream &f, int64_t value, int size) { - if (size < 1 || size > 8) throw std::invalid_argument("size must be 1..8"); - std::vector buf(size, 0); - uint64_t v = static_cast(value); - for (int i = 0; i < size; ++i) { - buf[i] = static_cast((v >> (i*8)) & 0xFF); - } - f.write(reinterpret_cast(buf.data()), size); - if (!f) throw std::runtime_error("writeIntLE failed"); -} +class BT_Reference { +protected: + BinaryTable* _table; + BT_Pointer _pointer; -float readFloat32LE(std::fstream &f) { - uint32_t u = static_cast(readIntLE(f,4)); - float val; - std::memcpy(&val, &u, sizeof(float)); - return val; -} -void writeFloat32LE(std::fstream &f, float v) { - uint32_t u; - std::memcpy(&u, &v, sizeof(float)); - writeIntLE(f, static_cast(u), 4); -} +public: + BT_Reference(BinaryTable* table, BT_Pointer pointer) : _table(table), _pointer(pointer) {} -BT_Pointer readPointerLE(std::fstream &f) { - int64_t addr = readIntLE(f, POINTER_SIZE); - return BT_Pointer(addr); -} -void writePointerLE(std::fstream &f, const BT_Pointer &p) { - writeIntLE(f, p.address, POINTER_SIZE); -} + template + T decodeValue(); -// -// FNV-1a 64-bit hash (like your Dart fnv1a) -// -uint64_t fnv1a_hash(const std::string &s) { - const uint64_t FNV_OFFSET = 0xcbf29ce484222325ULL; - const uint64_t FNV_PRIME = 0x100000001b3ULL; - uint64_t hash = FNV_OFFSET; - for (unsigned char c : s) { - hash ^= (uint64_t)c; - hash *= FNV_PRIME; - } - return hash; -} + int getSize(); -// -// encodeValue: produce bytes like your Dart encodeValue -// -Bytes encodeValue(const Value &v) { - Bytes out; - if (std::holds_alternative(v)) { - out.push_back(static_cast(bt_type_index(BT_Type::INTEGER))); - int32_t val = std::get(v); - for (int i = 0; i < 4; ++i) out.push_back(static_cast((val >> (i*8)) & 0xFF)); - } else if (std::holds_alternative(v)) { - out.push_back(static_cast(bt_type_index(BT_Type::FLOAT))); - float f = std::get(v); - uint32_t u; std::memcpy(&u, &f, sizeof(float)); - for (int i = 0; i < 4; ++i) out.push_back(static_cast((u >> (i*8)) & 0xFF)); - } else if (std::holds_alternative(v)) { - out.push_back(static_cast(bt_type_index(BT_Type::STRING))); - const std::string &s = std::get(v); - int32_t len = static_cast(s.size()); - for (int i = 0; i < 4; ++i) out.push_back(static_cast((len >> (i*8)) & 0xFF)); - out.insert(out.end(), s.begin(), s.end()); - } else if (std::holds_alternative>(v)) { - out.push_back(static_cast(bt_type_index(BT_Type::INTEGER_ARRAY))); - const auto &vec = std::get>(v); - int32_t len = static_cast(vec.size()); - for (int i = 0; i < 4; ++i) out.push_back(static_cast((len >> (i*8)) & 0xFF)); - // encode each element with full encodeValue (type byte + data) - for (int32_t item : vec) { - Value vi = item; - Bytes b = encodeValue(vi); - out.insert(out.end(), b.begin(), b.end()); - } - } else if (std::holds_alternative>(v)) { - out.push_back(static_cast(bt_type_index(BT_Type::FLOAT_ARRAY))); - const auto &vec = std::get>(v); - int32_t len = static_cast(vec.size()); - for (int i = 0; i < 4; ++i) out.push_back(static_cast((len >> (i*8)) & 0xFF)); - for (float item : vec) { - Value vf = item; - Bytes b = encodeValue(vf); - out.insert(out.end(), b.begin(), b.end()); - } - } else if (std::holds_alternative(v)) { - out.push_back(static_cast(bt_type_index(BT_Type::POINTER))); - uint64_t a = std::get(v); - for (int i = 0; i < POINTER_SIZE; ++i) out.push_back(static_cast((a >> (i*8)) & 0xFF)); - } else { - throw std::runtime_error("Unsupported type in encodeValue"); - } - return out; -} + std::string toString() const { return _pointer.toString(); } -// -// Read a Value from a file at current position -// -Value decodeValue(std::fstream &f) { - int typeId = f.get(); - if (typeId == EOF) throw std::runtime_error("decodeValue: EOF while reading type"); - BT_Type type = static_cast(typeId); + BT_Pointer getPointer() const { return _pointer; } +}; - switch (type) { - case BT_Type::INTEGER: { - int32_t iv = static_cast(readIntLE(f,4)); - return iv; - } - case BT_Type::FLOAT: { - float fv = readFloat32LE(f); - return fv; - } - case BT_Type::STRING: { - int32_t len = static_cast(readIntLE(f,4)); - std::string s; s.resize(len); - f.read(&s[0], len); - if (!f) throw std::runtime_error("decodeValue: unable to read string bytes"); - return s; - } - case BT_Type::POINTER: { - BT_Pointer p = readPointerLE(f); - return static_cast(p.address); - } - case BT_Type::ADDRESS_TABLE: - throw std::runtime_error("Address table decoding not implemented via decodeValue"); - case BT_Type::INTEGER_ARRAY: - case BT_Type::FLOAT_ARRAY: { - // return a UniformArray proxy: encode pointer we are at minus 0? We need to return a proxy object that knows the pointer - // However decodeValue isn't given a pointer. For usage we typically read pointer from address table and then create BT_Reference/UniformArray - // This function is mostly used for direct inline decode; we'll not implement reading arrays inline here. - throw std::runtime_error("Decoding array inline is not supported by decodeValue(); use BT_UniformArray via BinaryTable accessors."); - } - default: - throw std::runtime_error("Unsupported BT_Type in decodeValue"); - } -} - -// -// Free list entry -// struct BT_FreeListEntry { BT_Pointer pointer; - int32_t size; - BT_FreeListEntry() : pointer(-1), size(0) {} - BT_FreeListEntry(BT_Pointer p, int32_t s) : pointer(p), size(s) {} + int size; + + BT_FreeListEntry(BT_Pointer ptr, int sz) : pointer(ptr), size(sz) {} }; -// -// Proxy: uniform array wrapper -// -struct BT_UniformArray { - BinaryTable *table; - BT_Pointer pointer; +template +class BT_UniformArray : public BT_Reference { +public: + BT_UniformArray(BinaryTable* table, BT_Pointer pointer) : BT_Reference(table, pointer) {} - BT_UniformArray(BinaryTable *t, BT_Pointer p) : table(t), pointer(p) {} - - // Get length - int32_t length() const; - - // Index access (read) - Value get(int index) const; - - // Index write (only supports fixed-size numeric types for now) - void set(int index, const Value &v); - - // Add / addAll will reallocate and update pointer in address table by invoking BinaryTable methods - void add(const Value &v); - void addAll(const std::vector &vals); - - // element type detection (returns BT_Type if available) - BT_Type elementType() const; - - // size in bytes used in file - int32_t size() const; + 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(); }; -// -// BinaryTable -// -struct BinaryTable { - std::string path; - std::fstream f; - - BinaryTable(const std::string &p) : path(p) { - // open in read/write binary mode, create if not exists - f.open(path, std::ios::in | std::ios::out | std::ios::binary); - if (!f) { - // try create - std::ofstream create(path, std::ios::binary); - create.close(); - f.open(path, std::ios::in | std::ios::out | std::ios::binary); - if (!f) throw std::runtime_error("Failed to open file " + path); - } - } - - void initialise() { - // set file to empty then write header: pointer to address table (pointer), and free list entry count (4 bytes zeros) - f.seekp(0, std::ios::beg); - writePointerLE(f, BT_Null); // address table pointer - writeIntLE(f, 0, 4); // free-list entry count - f.flush(); - } - - // --- Address table get / set --- - // addressTable is map - std::map addressTableGet() { - // read table pointer from start - f.seekg(0, std::ios::beg); - BT_Pointer tablePtr = readPointerLE(f); - std::map table; - if (tablePtr.isNull()) return table; - - // at tablePtr.address, first byte is type, then 4 bytes count, then entries of 8 (hash) + POINTER_SIZE (pointer) - f.seekg(tablePtr.address + 1, std::ios::beg); - int32_t count = static_cast(readIntLE(f,4)); - int entrySize = 8 + POINTER_SIZE; - for (int i = 0; i < count; ++i) { - // read hash (8 bytes) - uint64_t keyHash = static_cast(readIntLE(f,8)); - int64_t valPtrAddr = readIntLE(f, POINTER_SIZE); - table[keyHash] = BT_Pointer(valPtrAddr); - } - return table; - } - - void addressTableSet(const std::map &table) { - // build buffer - Bytes buffer; - buffer.push_back(static_cast(bt_type_index(BT_Type::ADDRESS_TABLE))); - int32_t count = static_cast(table.size()); - for (int i = 0; i < 4; ++i) buffer.push_back(static_cast((count >> (i*8)) & 0xFF)); - for (const auto &kv : table) { - uint64_t key = kv.first; - BT_Pointer val = kv.second; - for (int i = 0; i < 8; ++i) buffer.push_back(static_cast((key >> (i*8)) & 0xFF)); - for (int i = 0; i < POINTER_SIZE; ++i) buffer.push_back(static_cast((val.address >> (i*8)) & 0xFF)); - } - - // allocate at end - BT_Pointer tableAddress = alloc(static_cast(buffer.size())); - // write buffer - f.seekp(tableAddress.address, std::ios::beg); - f.write(reinterpret_cast(buffer.data()), buffer.size()); - f.flush(); - - // read old pointer - f.seekg(0, std::ios::beg); - BT_Pointer oldPtr = readPointerLE(f); - - // update header - f.seekp(0, std::ios::beg); - writePointerLE(f, tableAddress); - f.flush(); - - // free old table if exists and not same - if (!oldPtr.isNull() && !(oldPtr == tableAddress)) { - BT_Pointer p = oldPtr; - int32_t s = pointerSize(oldPtr); - // must lift free list for free() but here we do simplest: lift, free, drop - liftFreeList(); - free_internal(p, s); - dropFreeList(); - } - } - - // --- free list handling --- +class BinaryTable { +private: + std::fstream _file; + std::string _filename; bool freeListLifted = false; - std::vector freeListCache; + std::vector _freeListCache; - std::vector freeListGet() { - if (freeListLifted) return freeListCache; - // read last 4 bytes for entry count - f.seekg(0, std::ios::end); - std::streampos fileLen = f.tellg(); - if (fileLen < 4) return {}; - f.seekg(fileLen - 4, std::ios::beg); - int32_t entryCount = static_cast(readIntLE(f, 4)); - if (entryCount == 0) return {}; - int entrySize = POINTER_SIZE + 4; - std::streampos freeListStart = fileLen - 4 - static_cast(entryCount * entrySize); - f.seekg(freeListStart, std::ios::beg); - std::vector list; - for (int i = 0; i < entryCount; ++i) { - int64_t ptrAddr = readIntLE(f, POINTER_SIZE); - int32_t sz = static_cast(readIntLE(f, 4)); - list.emplace_back(BT_Pointer(ptrAddr), sz); + 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 list; + return hash; } - void freeListSet(const std::vector &list) { + 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) { - freeListCache = list; + 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; } - // erase old free list at end - f.seekg(0, std::ios::end); - std::streampos fileLen = f.tellg(); - if (fileLen >= 4) { - // read old count - f.seekg(fileLen - 4, std::ios::beg); - int32_t oldCount = static_cast(readIntLE(f,4)); - int oldSize = (oldCount * (POINTER_SIZE + 4)) + 4; - // truncate: move put to new size (there's no direct truncate with std fstreams; workaround via reopening) - std::streampos newLen = fileLen - oldSize; - f.close(); - // platform-dependent truncation via std::ofstream::open + close - std::ofstream trunc(path, std::ios::in | std::ios::out | std::ios::binary); - trunc.seekp(newLen); - trunc.flush(); - trunc.close(); - // reopen - f.open(path, std::ios::in | std::ios::out | std::ios::binary); + + 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); } - // append new free list - f.seekp(0, std::ios::end); - for (const auto &e : list) { - writeIntLE(f, e.pointer.address, POINTER_SIZE); - writeIntLE(f, e.size, 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); } - int32_t count = static_cast(list.size()); - writeIntLE(f, count, 4); - f.flush(); + } + + ~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 already lifted"); - freeListCache = freeListGet(); - // truncate file to remove free list - f.seekg(0, std::ios::end); - std::streampos fileLen = f.tellg(); - if (fileLen < 4) { freeListLifted = true; return; } - f.seekg(fileLen - 4, std::ios::beg); - int32_t oldCount = static_cast(readIntLE(f,4)); - int oldEntrySize = POINTER_SIZE + 4; - int oldFreeListSize = oldCount * oldEntrySize + 4; - std::streampos newLen = fileLen - oldFreeListSize; - f.close(); - // truncate via std::ofstream - std::ofstream trunc(path, std::ios::in | std::ios::out | std::ios::binary); - trunc.seekp(newLen); - trunc.flush(); - trunc.close(); - f.open(path, std::ios::in | std::ios::out | std::ios::binary); + 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 not lifted"); - // append placeholder 4 bytes then use freeListSet to write full encoded list - f.seekp(0, std::ios::end); - writeIntLE(f, 0, 4); // placeholder count (will be replaced by freeListSet) + if (!freeListLifted) { + throw std::runtime_error("Free list is not lifted"); + } + + _file.seekp(0, std::ios::end); + writeInt32(_file.tellp(), 0); // Placeholder + freeListLifted = false; - freeListSet(freeListCache); - freeListCache.clear(); + setFreeList(_freeListCache); + _freeListCache.clear(); } - // internal free function used when freeListLifted==true - void free_internal(const BT_Pointer &pointer, int32_t size) { - if (!freeListLifted) throw std::runtime_error("free list must be lifted before freeing"); - if (pointer.isNull() || size <= 0) throw std::invalid_argument("Cannot free null pointer or zero size"); - auto list = freeListGet(); - list.emplace_back(pointer, size); + 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; + }); - // merge contiguous blocks - std::sort(list.begin(), list.end(), [](const BT_FreeListEntry &a, const BT_FreeListEntry &b){ - return a.pointer.address < b.pointer.address; - }); std::vector merged; - for (const auto &e : list) { - if (merged.empty()) merged.push_back(e); - else { - BT_FreeListEntry &last = merged.back(); - if (last.pointer.address + last.size == e.pointer.address) { - last.size += e.size; + 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(e); + merged.push_back(entry); } } } - freeListSet(merged); + + setFreeList(merged); } - void free(const BT_Pointer &pointer, int32_t size) { - free_internal(pointer, size); - } + BT_Pointer alloc(int size) { + if (!freeListLifted) { + throw std::runtime_error("Free list must be lifted before allocation"); + } - // allocation expects free list to be lifted - BT_Pointer alloc(int32_t size) { - if (!freeListLifted) throw std::runtime_error("free list must be lifted before alloc"); - auto list = freeListGet(); - if (list.empty()) { - // allocate at end of file - f.seekg(0, std::ios::end); - std::streampos end = f.tellg(); - return BT_Pointer(static_cast(end)); + auto freeList = getFreeList(); + + if (freeList.empty()) { + return BT_Pointer(getFileSize()); } - // find first fit - int idx = -1; - for (size_t i = 0; i < list.size(); ++i) { - if (list[i].size >= size) { idx = static_cast(i); break; } + + // Find best fit + BT_FreeListEntry* bestFit = nullptr; + for (auto& entry : freeList) { + if (entry.size >= size) { + bestFit = &entry; + break; + } } - if (idx == -1) { - f.seekg(0, std::ios::end); - std::streampos end = f.tellg(); - return BT_Pointer(static_cast(end)); + + if (!bestFit) { + return BT_Pointer(getFileSize()); } - BT_FreeListEntry chosen = list[idx]; - if (chosen.size == size) { - // exact fit, remove - list.erase(list.begin() + idx); - freeListSet(list); - return chosen.pointer; + + 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 - BT_Pointer allocated = chosen.pointer; - BT_FreeListEntry remainder(BT_Pointer(chosen.pointer.address + size), chosen.size - size); - list.erase(list.begin() + idx); - list.push_back(remainder); - freeListSet(list); - return allocated; + // Split block + bestFit->pointer = BT_Pointer(bestFit->pointer.address + size); + bestFit->size -= size; } + + setFreeList(freeList); + return allocatedPointer; } - // pointer size helper: compute stored size of object at pointer by reading type and necessary lengths - int32_t pointerSize(const BT_Pointer &p) { - if (p.isNull()) return 0; - f.seekg(p.address, std::ios::beg); - int typeId = f.get(); - if (typeId == EOF) throw std::runtime_error("pointerSize EOF"); - BT_Type type = static_cast(typeId); - switch (type) { - case BT_Type::INTEGER: return 1 + 4; - case BT_Type::FLOAT: return 1 + 4; - case BT_Type::STRING: { - int32_t len = static_cast(readIntLE(f,4)); - return 1 + 4 + len; + template + void set(const std::string& key, const T& value) { + antiFreeListScope([&]() { + auto addressTable = getAddressTable(); + uint64_t keyHash = fnv1aHash(key); + + if (addressTable.find(keyHash) != addressTable.end()) { + throw std::runtime_error("Key already exists"); } - case BT_Type::ADDRESS_TABLE: { - int32_t count = static_cast(readIntLE(f,4)); - return 1 + 4 + count * (8 + POINTER_SIZE); - } - case BT_Type::POINTER: - return 1 + POINTER_SIZE; - case BT_Type::INTEGER_ARRAY: - case BT_Type::FLOAT_ARRAY: { - // read length then need to determine element size by reading first element (type+size) - int32_t len = static_cast(readIntLE(f,4)); - // save position - std::streampos pos = f.tellg(); - // read first element's type - if (len == 0) return 1 + 4; // type byte + length only - int subTypeId = f.get(); - f.seekg(pos, std::ios::beg); - BT_Type elemType = static_cast(subTypeId); - // for uniform arrays of numeric types we expect element type size defined - int elemSize = 0; - if (elemType == BT_Type::INTEGER || elemType == BT_Type::FLOAT) elemSize = 1 + 4; - else throw std::runtime_error("Variable-size elements in uniform array not supported in pointerSize()"); - return 1 + 4 + len * elemSize; - } - default: - throw std::runtime_error("Unsupported type in pointerSize"); - } + + auto valueBuffer = encodeValue(value); + BT_Pointer valueAddress = alloc(valueBuffer.size()); + + _file.seekp(valueAddress.address); + _file.write(reinterpret_cast(valueBuffer.data()), valueBuffer.size()); + + addressTable[keyHash] = valueAddress; + setAddressTable(addressTable); + }); } - // operator[] and operator[]= equivalents - void set(const std::string &key, const Value &value) { - // anti-free-list-scope: lift free list, do op, drop - liftFreeList(); - auto table = addressTableGet(); + template + T get(const std::string& key) { + auto addressTable = getAddressTable(); + uint64_t keyHash = fnv1aHash(key); - uint64_t keyHash = fnv1a_hash(key); - if (table.find(keyHash) != table.end()) { - dropFreeList(); - throw std::runtime_error("Key already exists"); - } - Bytes buf = encodeValue(value); - BT_Pointer addr = alloc(static_cast(buf.size())); - // write value - f.seekp(addr.address, std::ios::beg); - f.write(reinterpret_cast(buf.data()), buf.size()); - f.flush(); - table[keyHash] = addr; - addressTableSet(table); - // addressTableSet will free old table and update header by itself (it lifts/drops internally) - dropFreeList(); - } - - // return Value for key - Value get(const std::string &key) { - auto table = addressTableGet(); - uint64_t keyHash = fnv1a_hash(key); - if (table.find(keyHash) == table.end()) throw std::runtime_error("Key does not exist"); - BT_Pointer p = table[keyHash]; - // to decode numeric/string types we can seek to pointer and call decodeValue - f.seekg(p.address, std::ios::beg); - int typeId = f.get(); - if (typeId == EOF) throw std::runtime_error("get: EOF"); - BT_Type type = static_cast(typeId); - if (type == BT_Type::INTEGER) { - int32_t v = static_cast(readIntLE(f,4)); - return v; - } else if (type == BT_Type::FLOAT) { - float v = readFloat32LE(f); - return v; - } else if (type == BT_Type::STRING) { - int32_t len = static_cast(readIntLE(f,4)); - std::string s; s.resize(len); - f.read(&s[0], len); - if (!f) throw std::runtime_error("get: unable to read string"); - return s; - } else if (type == BT_Type::INTEGER_ARRAY || type == BT_Type::FLOAT_ARRAY) { - // return a shared_ptr to uniform array proxy - auto proxy = std::make_shared(this, p); - return proxy; - } else if (type == BT_Type::POINTER) { - int64_t addr = readIntLE(f, POINTER_SIZE); - return static_cast(addr); - } else { - throw std::runtime_error("Unsupported get type"); - } - } - - void deleteKey(const std::string &key) { - liftFreeList(); - auto table = addressTableGet(); - uint64_t keyHash = fnv1a_hash(key); - if (table.find(keyHash) == table.end()) { - dropFreeList(); + auto it = addressTable.find(keyHash); + if (it == addressTable.end()) { throw std::runtime_error("Key does not exist"); } - BT_Pointer p = table[keyHash]; - int32_t s = pointerSize(p); - free_internal(p, s); - table.erase(keyHash); - addressTableSet(table); - dropFreeList(); + + BT_Reference valueRef(this, it->second); + return valueRef.decodeValue(); } - // relocate address table and try to truncate file - void truncate() { - liftFreeList(); - // re-write address table to ensure it's at the end - auto table = addressTableGet(); - addressTableSet(table); + void remove(const std::string& key) { + antiFreeListScope([&]() { + auto addressTable = getAddressTable(); + uint64_t keyHash = fnv1aHash(key); - // check last free block - auto list = freeListGet(); - if (list.empty()) { dropFreeList(); return; } - std::sort(list.begin(), list.end(), [](const BT_FreeListEntry &a, const BT_FreeListEntry &b){ - return a.pointer.address < b.pointer.address; + auto it = addressTable.find(keyHash); + if (it == addressTable.end()) { + throw std::runtime_error("Key does not exist"); + } + + BT_Reference valueRef(this, it->second); + free(it->second, valueRef.getSize()); + + addressTable.erase(it); + setAddressTable(addressTable); }); - auto last = list.back(); - f.seekg(0, std::ios::end); - int64_t fileEnd = static_cast(f.tellg()); - int64_t expected = last.pointer.address + last.size; - if (expected != fileEnd) { dropFreeList(); return; } - // remove last entry and truncate - list.pop_back(); - freeListSet(list); - // truncate file to last.pointer.address - f.close(); - std::ofstream trunc(path, std::ios::in | std::ios::out | std::ios::binary); - trunc.seekp(last.pointer.address); - trunc.flush(); - trunc.close(); - f.open(path, std::ios::in | std::ios::out | std::ios::binary); - dropFreeList(); } -}; // BinaryTable + // 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(); +}; -// BT_UniformArray implementation methods (defined after BinaryTable) -int32_t BT_UniformArray::length() const { - if (pointer.isNull()) return 0; - table->f.seekg(pointer.address, std::ios::beg); - int typeId = table->f.get(); - BT_Type t = static_cast(typeId); - if (!(t == BT_Type::INTEGER_ARRAY || t == BT_Type::FLOAT_ARRAY)) +// Template implementations +template +T BT_Reference::decodeValue() { + if (_pointer.isNull()) { + throw std::runtime_error("Null pointer"); + } + + _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); + + 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_Type::ADDRESS_TABLE: { + int32_t count = _table->readInt32(_table->_file.tellg()); + return 1 + 4 + count * (8 + 8); + } + default: + throw std::runtime_error("Unsupported type for size calculation"); + } +} + +template +int BT_UniformArray::getLength() { + 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); + + if (!isBT_TypeArray(type)) { throw std::runtime_error("Not an array"); - int32_t len = static_cast(readIntLE(table->f,4)); - return len; -} -BT_Type BT_UniformArray::elementType() const { - if (pointer.isNull()) throw std::runtime_error("Null pointer"); - int32_t len = length(); - if (len == 0) throw std::runtime_error("Empty array has no elementType"); - // element type is stored right after type byte + length (1+4) - table->f.seekg(pointer.address + 1 + 4, std::ios::beg); - int subTypeId = table->f.get(); - return static_cast(subTypeId); -} -Value BT_UniformArray::get(int index) const { - if (pointer.isNull()) throw std::runtime_error("Null pointer"); - int32_t len = length(); - if (index < 0 || index >= len) throw std::out_of_range("index"); - BT_Type elemType = elementType(); - if (elemType == BT_Type::INTEGER) { - // element entry size = 1 + 4 - int64_t itemAddr = pointer.address + 1 + 4 + index * (1 + 4); - table->f.seekg(itemAddr, std::ios::beg); - int tid = table->f.get(); - if (static_cast(tid) != BT_Type::INTEGER) throw std::runtime_error("element type mismatch"); - int32_t v = static_cast(readIntLE(table->f,4)); - return v; - } else if (elemType == BT_Type::FLOAT) { - int64_t itemAddr = pointer.address + 1 + 4 + index * (1 + 4); - table->f.seekg(itemAddr, std::ios::beg); - int tid = table->f.get(); - if (static_cast(tid) != BT_Type::FLOAT) throw std::runtime_error("element type mismatch"); - float v = readFloat32LE(table->f); - return v; - } else { - throw std::runtime_error("Unsupported element type in get()"); } + + return _table->readInt32(_table->_file.tellg()); } -void BT_UniformArray::set(int index, const Value &v) { - if (pointer.isNull()) throw std::runtime_error("Null pointer"); - int32_t len = length(); - if (index < 0 || index >= len) throw std::out_of_range("index"); - BT_Type elemType = elementType(); - // Only support fixed-size numeric types - if (elemType == BT_Type::INTEGER && std::holds_alternative(v)) { - int64_t itemAddr = pointer.address + 1 + 4 + index * (1 + 4); - auto buf = encodeValue(v); - table->f.seekp(itemAddr, std::ios::beg); - table->f.write(reinterpret_cast(buf.data()), buf.size()); - table->f.flush(); - } else if (elemType == BT_Type::FLOAT && std::holds_alternative(v)) { - int64_t itemAddr = pointer.address + 1 + 4 + index * (1 + 4); - auto buf = encodeValue(v); - table->f.seekp(itemAddr, std::ios::beg); - table->f.write(reinterpret_cast(buf.data()), buf.size()); - table->f.flush(); - } else { - throw std::runtime_error("Type mismatch or unsupported set()"); +template +T BT_UniformArray::operator[](int index) { + if (_pointer.isNull()) { + throw std::runtime_error("Null pointer"); } + + 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); + + int itemOffset = index * (1 + getBT_TypeSize(type)); + BT_Reference itemRef(_table, BT_Pointer(_pointer.address + 1 + 4 + itemOffset)); + return itemRef.decodeValue(); } -void BT_UniformArray::add(const Value &v) { - addAll({v}); +template +void BT_UniformArray::set(int index, const T& value) { + if (_pointer.isNull()) { + throw std::runtime_error("Null pointer"); + } + + 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(); + if (newValueType != type) { + } + + int itemOffset = index * (1 + getBT_TypeSize(type)); + BT_Pointer itemPointer(_pointer.address + 1 + 4 + itemOffset); + + auto valueBuffer = encodeValue(value); + _table->_file.seekp(itemPointer.address); + _table->_file.write(reinterpret_cast(valueBuffer.data()), valueBuffer.size()); } -void BT_UniformArray::addAll(const std::vector &vals) { - if (pointer.isNull()) throw std::runtime_error("Null pointer"); - // anti-free-list - table->liftFreeList(); - BT_Type elemType; - if (length() == 0) { - // deduce from first - const Value &first = vals.at(0); - if (std::holds_alternative(first)) elemType = BT_Type::INTEGER; - else if (std::holds_alternative(first)) elemType = BT_Type::FLOAT; - else throw std::runtime_error("Unsupported element type for uniform array"); - } else { - elemType = elementType(); - } - // validate - for (size_t i = 0; i < vals.size(); ++i) { - if (elemType == BT_Type::INTEGER && !std::holds_alternative(vals[i])) { - table->dropFreeList(); - throw std::runtime_error("Type mismatch in addAll"); - } else if (elemType == BT_Type::FLOAT && !std::holds_alternative(vals[i])) { - table->dropFreeList(); - throw std::runtime_error("Type mismatch in addAll"); - } - } - // read full buffer into memory - int32_t oldLen = length(); - int elemStoredSize = 1 + 4; - int32_t bufferSize = 1 + 4 + oldLen * elemStoredSize; - table->f.seekg(pointer.address, std::ios::beg); - Bytes fullBuffer(bufferSize); - table->f.read(reinterpret_cast(fullBuffer.data()), bufferSize); - // append encoded new elements - for (const auto &val : vals) { - Bytes b = encodeValue(val); - fullBuffer.insert(fullBuffer.end(), b.begin(), b.end()); - } - int32_t newLen = oldLen + static_cast(vals.size()); - // update length bytes - for (int i = 0; i < 4; ++i) fullBuffer[1 + i] = static_cast((newLen >> (i*8)) & 0xFF); - // free old array space - int32_t oldSize = 1 + 4 + oldLen * elemStoredSize; - table->free_internal(pointer, oldSize); - // allocate new space - BT_Pointer newPtr = table->alloc(static_cast(fullBuffer.size())); - // update address table entries that pointed to old pointer -> update to new pointer - // We need to update any keys that pointed to pointer.address; simplest is to rebuild address table and replace - auto tableMap = table->addressTableGet(); - for (auto &kv : tableMap) { - if (kv.second == pointer) kv.second = newPtr; - } - table->addressTableSet(tableMap); - // write new buffer to file - table->f.seekp(newPtr.address, std::ios::beg); - table->f.write(reinterpret_cast(fullBuffer.data()), fullBuffer.size()); - table->f.flush(); - // update this->pointer - pointer = newPtr; - table->dropFreeList(); +template +void BT_UniformArray::add(const T& value) { + addAll({value}); } -int32_t BT_UniformArray::size() const { - if (pointer.isNull()) return 0; - return table->pointerSize(pointer); -} +template +void BT_UniformArray::addAll(const std::vector& values) { + _table->antiFreeListScope([&]() { + BT_Type elementType = getElementType(); -// -// simple binary dump helper -// -std::string binaryDump(const std::string &path) { - std::ifstream in(path, std::ios::binary); - if (!in) return ""; - std::ostringstream oss; - std::vector data((std::istreambuf_iterator(in)), std::istreambuf_iterator()); - size_t len = data.size(); - for (size_t i = 0; i < len; i += 16) { - oss << "0x" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << i << " (" << std::dec << std::setw(4) << i << ") | "; - for (int j = 0; j < 16; ++j) { - if (i + j < len) { - oss << std::setw(2) << std::setfill('0') << std::hex << (int)data[i+j] << " "; - } else oss << " "; - } - oss << " | "; - for (int j = 0; j < 16; ++j) { - if (i + j < len) oss << std::dec << std::setw(3) << std::setfill(' ') << (int)data[i+j] << " "; - else oss << " "; - } - oss << " | "; - for (int j = 0; j < 16; ++j) { - if (i + j < len) { - byte b = data[i+j]; - if (b >= 32 && b <= 126) oss << (char)b; - else oss << '.'; + // Validate types + for (const auto& value : values) { + BT_Type newValueType = getBT_TypeFromValue(); + if (newValueType != elementType) { + throw std::runtime_error("Type mismatch"); } } - if (i + 16 < len) oss << "\n"; + + // Read current array + int currentLength = getLength(); + int elementSize = 1 + getBT_TypeSize(elementType); + int bufferSize = 1 + 4 + currentLength * elementSize; + + _table->_file.seekg(_pointer.address); + std::vector fullBuffer(bufferSize); + _table->_file.read(reinterpret_cast(fullBuffer.data()), bufferSize); + + // Add new values + for (const auto& value : values) { + auto valueBuffer = encodeValue(value); + fullBuffer.insert(fullBuffer.end(), valueBuffer.begin(), valueBuffer.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; + + // Free old array + _table->free(_pointer, getSize()); + + // Allocate new space + BT_Pointer newPointer = _table->alloc(fullBuffer.size()); + + // Update address table references + auto addressTable = _table->getAddressTable(); + for (auto& [key, value] : addressTable) { + if (value == _pointer) { + value = newPointer; + } + } + _table->setAddressTable(addressTable); + _pointer = newPointer; + + // Write updated buffer + _table->_file.seekp(newPointer.address); + _table->_file.write(reinterpret_cast(fullBuffer.data()), fullBuffer.size()); + }); +} + +template +BT_Type BT_UniformArray::getElementType() { + if (getLength() == 0) { + return getBT_TypeFromValue(); } - return oss.str(); + + _table->_file.seekg(_pointer.address + 1 + 4); + uint8_t typeId; + _table->_file.read(reinterpret_cast(&typeId), 1); + return static_cast(typeId); +} + +template +int BT_UniformArray::getSize() { + int len = getLength(); + if (len == 0) { + return 1 + 4; + } + + BT_Type elementType = getElementType(); + return 1 + 4 + len * (1 + getBT_TypeSize(elementType)); +} + +std::string binaryDump(const std::vector& data) { + std::stringstream 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 << ") | "; + + // Hex bytes + for (int j = 0; j < 16; j++) { + if (i + j < data.size()) { + buffer << std::setfill('0') << std::setw(2) << std::hex << std::uppercase + << static_cast(data[i + j]) << " "; + } else { + buffer << " "; + } + } + + buffer << " | "; + + // Integer representation + 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]) << " "; + } else { + buffer << " "; + } + } + + buffer << " | "; + + // ASCII representation + for (int j = 0; j < 16; j++) { + if (i + j < data.size()) { + uint8_t byte = data[i + j]; + if (byte >= 32 && byte <= 126) { + buffer << static_cast(byte); + } else { + buffer << '.'; + } + } + } + + buffer << " | "; + if (i + 16 < data.size()) buffer << "\n"; + } + + return buffer.str(); } int main() { - const std::string fname = "example.bin"; - // remove existing - std::remove(fname.c_str()); - BinaryTable table(fname); - table.initialise(); - std::cout << "File dump:\n" << binaryDump(fname) << "\n"; - { - // set some arrays/values - std::vector iarr = {6,3,9,2,5}; - std::vector farr = {1.5f, 2.5f, 3.5f}; - std::vector empty; - table.set("int_array", iarr); - table.set("float_array", farr); - table.set("empty", empty); + // 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"; + } + if (readback2.getLength() > 0) { + std::cout << "First float: " << readback2[0] << "\n"; } - // update first element of int_array - { - Value v = table.get("int_array"); - if (auto proxy = std::get_if>(&v)) { - auto arr = *proxy; - // but our get returns pointer wrapped in variant; the variant stores shared_ptr - // set element 0 to 1 - arr->set(0, Value(int32_t(1))); - arr->set(1, Value(int32_t(3))); // just demonstrate - // add items - arr->add(Value(int32_t(10))); - arr->addAll({Value(int32_t(420)), Value(int32_t(69)), Value(int32_t(1337)), Value(int32_t(1738))}); - // print results by reading back - int32_t len = arr->length(); - std::cout << "int_array content: ["; - for (int i=0;iget(i); - if (std::holds_alternative(e)) { - std::cout << std::get(e); - } else std::cout << "?"; - if (i+1 finalData((std::istreambuf_iterator(finalFile)), + std::istreambuf_iterator()); + finalFile.close(); - // float array - { - Value v = table.get("float_array"); - if (auto proxy = std::get_if>(&v)) { - auto arr = *proxy; - arr->set(1, Value(4.5f)); - arr->add(Value(5.5f)); - arr->addAll({Value(6.5f), Value(7.5f), Value(8.5f)}); - int32_t len = arr->length(); - std::cout << "float_array content: ["; - for (int i=0;iget(i); - if (std::holds_alternative(e)) { - std::cout << std::get(e); - } else std::cout << "?"; - if (i+1