/* /$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$$$ /$$ /$$ /$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$$ |_ $$_/| $$$ /$$$| $$__ $$| $$_____/| $$$ | $$ |__ $$|_ $$_/ | $$$ | $$| $$_____/|__ $$__/ | $$ | $$$$ /$$$$| $$ \ $$| $$ | $$$$| $$ | $$ | $$ | $$$$| $$| $$ | $$ | $$ | $$ $$/$$ $$| $$$$$$$ | $$$$$ | $$ $$ $$ | $$ | $$ | $$ $$ $$| $$$$$ | $$ | $$ | $$ $$$| $$| $$__ $$| $$__/ | $$ $$$$ /$$ | $$ | $$ | $$ $$$$| $$__/ | $$ | $$ | $$\ $ | $$| $$ \ $$| $$ | $$\ $$$| $$ | $$ | $$ | $$\ $$$| $$ | $$ /$$$$$$| $$ \/ | $$| $$$$$$$/| $$$$$$$$| $$ \ $$| $$$$$$/ /$$$$$$ /$$| $$ \ $$| $$$$$$$$ | $$ |______/|__/ |__/|_______/ |________/|__/ \__/ \______/ |______/|__/|__/ \__/|________/ |__/ © 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++. 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 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 ADDRESS_TABLE = 1, INTEGER = 2, FLOAT = 3, STRING = 4, INTEGER_ARRAY = 5, FLOAT_ARRAY = 6 }; inline int bt_type_index(BT_Type t) { return static_cast(t); } // Value variant type using Value = std::variant, std::vector, uint64_t /*pointer address*/, std::shared_ptr>; struct BT_Pointer { int64_t address; BT_Pointer(int64_t a = -1) : address(a) {} bool isNull() const { return address == -1; } bool operator==(const BT_Pointer& o) const { return address == o.address; } std::string toString() const { std::ostringstream 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"); 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); } } 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"); } 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); } 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); } // // 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; } // // 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; } // // 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); 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) {} }; // // Proxy: uniform array wrapper // struct BT_UniformArray { BinaryTable *table; BT_Pointer 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; }; // // 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 --- bool freeListLifted = false; 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); } return list; } void freeListSet(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); } // 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); } int32_t count = static_cast(list.size()); writeIntLE(f, count, 4); f.flush(); } 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); 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) freeListLifted = false; freeListSet(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); // 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; } else { merged.push_back(e); } } } freeListSet(merged); } void free(const BT_Pointer &pointer, int32_t size) { free_internal(pointer, size); } // 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)); } // 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; } } if (idx == -1) { f.seekg(0, std::ios::end); std::streampos end = f.tellg(); return BT_Pointer(static_cast(end)); } BT_FreeListEntry chosen = list[idx]; if (chosen.size == size) { // exact fit, remove list.erase(list.begin() + idx); freeListSet(list); return chosen.pointer; } 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; } } // 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; } 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"); } } // 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(); 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(); 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(); } // 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); // 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 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 // 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)) 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()"); } } 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()"); } } void BT_UniformArray::add(const Value &v) { addAll({v}); } 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(); } int32_t BT_UniformArray::size() const { if (pointer.isNull()) return 0; return table->pointerSize(pointer); } // // 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 << '.'; } } if (i + 16 < len) oss << "\n"; } return oss.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); } // 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>(&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