#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // // 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, FLOAT = 3, STRING = 4, INTEGER_ARRAY = 5, FLOAT_ARRAY = 6 }; // 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"); } return static_cast(id); } // Pointer wrapper struct BT_Pointer { int64_t address{-1}; 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); } std::string toString() const { 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_); } }; // 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)); } } // Value input types acceptable to encodeValue using ValueInput = std::variant, std::vector>; // Forward: encodeValue std::vector encodeValue(const ValueInput& value); // 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); } // Encode a value into bytes [type byte][payload ...] std::vector encodeValue(const ValueInput& value) { std::vector buf; BT_TypeId valueType = typeFromDynamic(value); buf.push_back(static_cast(valueType)); 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"); } return buf; } // Free list entry struct BT_FreeListEntry { BT_Pointer pointer; int32_t size; }; // 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) {} 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 { public: 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 } // Set key = value void set(const std::string& key, const ValueInput& value) { antiFreeListScope([&]() { auto addressTable = getAddressTable(); int64_t keyHash = bt_hash(key); if (addressTable.find(keyHash) != addressTable.end()) { throw std::runtime_error("Key already exists"); } auto valueBuffer = encodeValue(value); // 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); }); } // Get decoded value DecodedValue get(const std::string& key) { auto addressTable = getAddressTable(); int64_t keyHashV = bt_hash(key); auto it = addressTable.find(keyHashV); if (it == addressTable.end()) throw std::runtime_error("Key does not exist"); BT_Pointer valuePtr = it->second; BT_Reference ref(this, valuePtr); return ref.decodeValue(); } void erase(const std::string& key) { antiFreeListScope([&]() { auto addressTable = getAddressTable(); int64_t keyHashV = bt_hash(key); auto it = addressTable.find(keyHashV); if (it == addressTable.end()) throw std::runtime_error("Key does not exist"); BT_Pointer valuePointer = it->second; BT_Reference valueRef(this, valuePointer); // Free the value free(valuePointer, valueRef.size()); // Remove from address table addressTable.erase(keyHashV); setAddressTable(addressTable); }); } // 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_; }; // BT_Reference implementations DecodedValue BT_Reference::decodeValue() { if (pointer_.isNull()) return std::monostate{}; table_->file().setPosition(pointer_.address); uint8_t typeId = table_->file().readByte(); BT_TypeId type = typeFromId(typeId); switch (type) { case BT_TypeId::INTEGER: { int32_t v = static_cast(table_->file().readInt(4)); return v; } 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"); } } 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()"); } } // BT_UniformArray implementations int32_t BT_UniformArray::length() { if (pointer_.isNull()) return 0; table_->file().setPosition(pointer_.address); BT_TypeId containerType = typeFromId(table_->file().readByte()); if (!typeInfo(containerType).arrayType) throw std::runtime_error("Not an array"); return static_cast(table_->file().readInt(4)); } 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."); } // Ensure new value type matches BT_TypeId newValueType = typeFromDynamic(value); if (newValueType != type) { throw std::runtime_error("Type mismatch in BT_UniformArray::set()"); } 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().setPosition(itemPointer.address); table_->file().write(valueBuffer); } void BT_UniformArray::addAll(const std::vector& values) { if (values.empty()) return; 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 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."); } } int32_t oldLen = length(); int itemStride = 1 + typeInfo(type).size; int32_t oldBufferSize = 1 + 4 + oldLen * itemStride; // Read full existing buffer table_->file().setPosition(pointer_.address); auto fullBuffer = table_->file().read(oldBufferSize); // Append new values for (const auto& v : values) { auto enc = encodeValue(v); fullBuffer.insert(fullBuffer.end(), enc.begin(), enc.end()); } // 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 int32_t oldSizeBytes = this->size(); table_->free(pointer_, oldSizeBytes); // Allocate new BT_Pointer newPtr = table_->alloc(static_cast(fullBuffer.size())); // 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); // 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"; }); } 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); } std::string BT_UniformArray::toString(bool readValues) { std::ostringstream oss; if (readValues) { oss << "Uniform Array of length " << length(); return oss.str(); } 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::ostringstream buffer; for (size_t i = 0; i < data.size(); i += 16) { 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) { if (i + j < data.size()) { buffer << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i + j]) << " "; } else { buffer << " "; } } buffer << " | "; // Integer representation for (int j = 0; j < 16; ++j) { if (i + j < data.size()) { buffer << std::dec << std::setw(3) << std::setfill(' ') << static_cast(data[i + j]) << " "; } else { buffer << " "; } } buffer << " | "; // ASCII representation for (int j = 0; j < 16; ++j) { if (i + j < data.size()) { int 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(); } // 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() { const std::string filename = "example.bin"; if (std::filesystem::exists(filename)) { std::filesystem::remove(filename); } { // 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"; } return 0; }