From 31a5729a0021feff05eb3f701a9963860703ba07 Mon Sep 17 00:00:00 2001 From: ImBenji Date: Wed, 17 Sep 2025 23:40:03 +0100 Subject: [PATCH] Add initial project files including analysis options, pubspec, and BinaryTable implementation --- c++/binary_table.h | 916 +++++++++++++++++++++++++++++++++++++++++++++ example.bin | Bin 0 -> 303 bytes 2 files changed, 916 insertions(+) create mode 100644 c++/binary_table.h create mode 100644 example.bin diff --git a/c++/binary_table.h b/c++/binary_table.h new file mode 100644 index 0000000..023dd88 --- /dev/null +++ b/c++/binary_table.h @@ -0,0 +1,916 @@ +/* + + /$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$$$ /$$ /$$ /$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$$ +|_ $$_/| $$$ /$$$| $$__ $$| $$_____/| $$$ | $$ |__ $$|_ $$_/ | $$$ | $$| $$_____/|__ $$__/ + | $$ | $$$$ /$$$$| $$ \ $$| $$ | $$$$| $$ | $$ | $$ | $$$$| $$| $$ | $$ + | $$ | $$ $$/$$ $$| $$$$$$$ | $$$$$ | $$ $$ $$ | $$ | $$ | $$ $$ $$| $$$$$ | $$ + | $$ | $$ $$$| $$| $$__ $$| $$__/ | $$ $$$$ /$$ | $$ | $$ | $$ $$$$| $$__/ | $$ + | $$ | $$\ $ | $$| $$ \ $$| $$ | $$\ $$$| $$ | $$ | $$ | $$\ $$$| $$ | $$ + /$$$$$$| $$ \/ | $$| $$$$$$$/| $$$$$$$$| $$ \ $$| $$$$$$/ /$$$$$$ /$$| $$ \ $$| $$$$$$$$ | $$ +|______/|__/ |__/|_______/ |________/|__/ \__/ \______/ |______/|__/|__/ \__/|________/ |__/ + +© 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;eb7mnBV{;5_M5as_cMfr3n@*ccet*nwQ21`LydVi!Pmd;k#~j@)98xQ#e~YC&M#CYcMH-}_Hc ygK!xbfl3%wlvaEd>1JWph6;v1yoXm41Ac8uP-D>Tg1JEsDBlHD69S|`UIhSU7%QFt literal 0 HcmV?d00001