Files
SweepStore/cpp/binary_table.h

954 lines
30 KiB
C++

/*
/$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$$$ /$$ /$$ /$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$$
|_ $$_/| $$$ /$$$| $$__ $$| $$_____/| $$$ | $$ |__ $$|_ $$_/ | $$$ | $$| $$_____/|__ $$__/
| $$ | $$$$ /$$$$| $$ \ $$| $$ | $$$$| $$ | $$ | $$ | $$$$| $$| $$ | $$
| $$ | $$ $$/$$ $$| $$$$$$$ | $$$$$ | $$ $$ $$ | $$ | $$ | $$ $$ $$| $$$$$ | $$
| $$ | $$ $$$| $$| $$__ $$| $$__/ | $$ $$$$ /$$ | $$ | $$ | $$ $$$$| $$__/ | $$
| $$ | $$\ $ | $$| $$ \ $$| $$ | $$\ $$$| $$ | $$ | $$ | $$\ $$$| $$ | $$
/$$$$$$| $$ \/ | $$| $$$$$$$/| $$$$$$$$| $$ \ $$| $$$$$$/ /$$$$$$ /$$| $$ \ $$| $$$$$$$$ | $$
|______/|__/ |__/|_______/ |________/|__/ \__/ \______/ |______/|__/|__/ \__/|________/ |__/
© 2025-26 by Benjamin Watt of IMBENJI.NET LIMITED - All rights reserved.
Use of this source code is governed by a MIT license that can be found in the LICENSE file.
This file is part of the SweepStore (formerly Binary Table) package for C++.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <map>
#include <cstdint>
#include <type_traits>
#include <iomanip>
#include <algorithm>
#include <functional>
enum class BT_Type : uint8_t {
POINTER = 0,
ADDRESS_TABLE = 1,
INTEGER = 2,
FLOAT = 3,
STRING = 4,
INTEGER_ARRAY = 5,
FLOAT_ARRAY = 6
};
constexpr int getBT_TypeSize(BT_Type type) {
switch (type) {
case BT_Type::POINTER: return 8;
case BT_Type::INTEGER: return 4;
case BT_Type::FLOAT: return 4;
}
default: return -1; // Variable size
}
constexpr bool isBT_TypeArray(BT_Type type) {
}
return type == BT_Type::INTEGER_ARRAY || type == BT_Type::FLOAT_ARRAY;
template<typename T>
constexpr BT_Type getBT_TypeFromValue() {
if constexpr (std::is_same_v<T, int32_t>) {
return BT_Type::INTEGER;
} else if constexpr (std::is_same_v<T, float>) {
return BT_Type::FLOAT;
} else if constexpr (std::is_same_v<T, std::string>) {
return BT_Type::STRING;
} else if constexpr (std::is_same_v<T, std::vector<int32_t>>) {
return BT_Type::INTEGER_ARRAY;
} else if constexpr (std::is_same_v<T, std::vector<float>>) {
return BT_Type::FLOAT_ARRAY;
} else {
static_assert(sizeof(T) == 0, "Unsupported type");
}
}
template<typename T>
std::vector<uint8_t> encodeValue(const T& value) {
std::vector<uint8_t> buffer;
BT_Type valueType = getBT_TypeFromValue<T>();
buffer.push_back(static_cast<uint8_t>(valueType));
if constexpr (std::is_same_v<T, int32_t>) {
uint32_t val = static_cast<uint32_t>(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<T, float>) {
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<T, std::string>) {
uint32_t len = static_cast<uint32_t>(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<uint8_t>(c));
}
} else if constexpr (std::is_same_v<T, std::vector<int32_t>>) {
uint32_t len = static_cast<uint32_t>(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<T, std::vector<float>>) {
uint32_t len = static_cast<uint32_t>(value.size());
buffer.push_back(len & 0xFF);
buffer.push_back((len >> 8) & 0xFF);
buffer.push_back((len >> 16) & 0xFF);
buffer.push_back((len >> 24) & 0xFF);
for (const auto& item : value) {
auto itemBuffer = encodeValue(item);
buffer.insert(buffer.end(), itemBuffer.begin(), itemBuffer.end());
}
}
return buffer;
}
class BT_Pointer {
public:
int64_t address;
BT_Pointer(int64_t addr = -1) : address(addr) {}
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::stringstream ss;
ss << "0x" << std::hex << address << " (" << std::dec << address << ")";
return ss.str();
}
};
const BT_Pointer BT_Null(-1);
class BinaryTable;
class BT_Reference {
protected:
BinaryTable* _table;
BT_Pointer _pointer;
public:
BT_Reference(BinaryTable* table, BT_Pointer pointer) : _table(table), _pointer(pointer) {}
template<typename T>
T decodeValue();
int getSize();
std::string toString() const { return _pointer.toString(); }
BT_Pointer getPointer() const { return _pointer; }
};
struct BT_FreeListEntry {
BT_Pointer pointer;
int size;
BT_FreeListEntry(BT_Pointer ptr, int sz) : pointer(ptr), size(sz) {}
};
template<typename T>
class BT_UniformArray : public BT_Reference {
public:
BT_UniformArray(BinaryTable* table, BT_Pointer pointer) : BT_Reference(table, pointer) {}
int getLength();
T operator[](int index);
void set(int index, const T& value);
void add(const T& value);
void addAll(const std::vector<T>& values);
BT_Type getElementType();
int getSize();
};
class BinaryTable {
private:
std::fstream _file;
std::string _filename;
bool freeListLifted = false;
std::vector<BT_FreeListEntry> _freeListCache;
uint64_t fnv1aHash(const std::string& str) {
uint64_t hash = 0xcbf29ce484222325ULL; // FNV offset basis
for (char c : str) {
hash ^= static_cast<uint8_t>(c);
hash *= 0x100000001b3ULL; // FNV prime
}
return hash;
}
int32_t readInt32(std::streampos pos) {
_file.seekg(pos);
uint8_t bytes[4];
_file.read(reinterpret_cast<char*>(bytes), 4);
return static_cast<int32_t>(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<uint8_t>(value & 0xFF),
static_cast<uint8_t>((value >> 8) & 0xFF),
static_cast<uint8_t>((value >> 16) & 0xFF),
static_cast<uint8_t>((value >> 24) & 0xFF)
_file.write(reinterpret_cast<char*>(bytes), 4);
};
}
_file.seekg(pos);
int64_t readInt64(std::streampos pos) {
uint8_t bytes[8];
_file.read(reinterpret_cast<char*>(bytes), 8);
int64_t result = 0;
for (int i = 0; i < 8; i++) {
result |= static_cast<int64_t>(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<uint8_t>((value >> (i * 8)) & 0xFF);
}
_file.write(reinterpret_cast<char*>(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<char*>(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<uint64_t, BT_Pointer> getAddressTable() {
BT_Pointer tablePtr = readPointer(0);
std::map<uint64_t, BT_Pointer> addressTable;
if (tablePtr.isNull()) {
return addressTable;
}
_file.seekg(tablePtr.address + 1); // Skip type byte
int32_t tableCount = readInt32(_file.tellg());
_file.seekg(static_cast<std::streampos>(_file.tellg()) + 4);
for (int i = 0; i < tableCount; i++) {
uint64_t keyHash = static_cast<uint64_t>(readInt64(_file.tellg()));
_file.seekg(static_cast<std::streampos>(_file.tellg()) + 8);
BT_Pointer valuePtr = readPointer(_file.tellg());
_file.seekg(static_cast<std::streampos>(_file.tellg()) + 8);
addressTable[keyHash] = valuePtr;
}
return addressTable;
}
void setAddressTable(const std::map<uint64_t, BT_Pointer>& table) {
std::vector<uint8_t> buffer;
buffer.push_back(static_cast<uint8_t>(BT_Type::ADDRESS_TABLE));
// Write count
uint32_t count = static_cast<uint32_t>(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<uint8_t>((key >> (i * 8)) & 0xFF));
}
// Value pointer (8 bytes)
for (int i = 0; i < 8; i++) {
buffer.push_back(static_cast<uint8_t>((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<char*>(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<BT_FreeListEntry> getFreeList() {
if (freeListLifted) {
return _freeListCache;
}
auto fileSize = getFileSize();
int32_t entryCount = readInt32(fileSize - 4);
if (entryCount == 0) {
return {};
}
int entrySize = 8 + 4; // Pointer + Size
int freeListSize = entryCount * entrySize;
_file.seekg(fileSize - 4 - freeListSize);
std::vector<BT_FreeListEntry> freeList;
for (int i = 0; i < entryCount; i++) {
BT_Pointer pointer = readPointer(_file.tellg());
_file.seekg(static_cast<std::streampos>(_file.tellg()) + 8);
int32_t size = readInt32(_file.tellg());
_file.seekg(static_cast<std::streampos>(_file.tellg()) + 4);
freeList.emplace_back(pointer, size);
}
return freeList;
}
void setFreeList(const std::vector<BT_FreeListEntry>& list) {
if (freeListLifted) {
_freeListCache = list;
return;
}
auto fileSize = getFileSize();
int32_t oldEntryCount = readInt32(fileSize - 4);
int oldListSize = (oldEntryCount * (8 + 4)) + 4;
// Truncate old free list
_file.close();
_file.open(_filename, std::ios::in | std::ios::out | std::ios::binary);
_file.seekp(0, std::ios::end);
auto newSize = static_cast<std::streamoff>(fileSize) - oldListSize;
_file.seekp(newSize);
// Write new free list
for (const auto& entry : list) {
writeInt64(_file.tellp(), entry.pointer.address);
_file.seekp(static_cast<std::streampos>(_file.tellp()) + 8);
writeInt32(_file.tellp(), entry.size);
_file.seekp(static_cast<std::streampos>(_file.tellp()) + 4);
}
// Write entry count
writeInt32(_file.tellp(), static_cast<int32_t>(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);
}
}
~BinaryTable() {
if (_file.is_open()) {
_file.close();
}
}
void initialize() {
_file.seekp(0);
writePointer(0, BT_Null); // Address table pointer
writeInt32(8, 0); // Free list entry count
}
void liftFreeList() {
if (freeListLifted) {
throw std::runtime_error("Free list is already lifted");
}
_freeListCache = getFreeList();
auto fileSize = getFileSize();
int32_t oldEntryCount = readInt32(fileSize - 4);
int oldFreeListSize = oldEntryCount * (8 + 4) + 4;
// Truncate file to remove free list
_file.close();
_file.open(_filename, std::ios::in | std::ios::out | std::ios::binary);
_file.seekp(0, std::ios::end);
auto newSize = static_cast<std::streamoff>(fileSize) - oldFreeListSize;
_file.seekp(newSize);
freeListLifted = true;
}
void dropFreeList() {
if (!freeListLifted) {
throw std::runtime_error("Free list is not lifted");
}
_file.seekp(0, std::ios::end);
writeInt32(_file.tellp(), 0); // Placeholder
freeListLifted = false;
setFreeList(_freeListCache);
_freeListCache.clear();
}
void antiFreeListScope(std::function<void()> fn) {
liftFreeList();
try {
fn();
} catch (...) {
dropFreeList();
throw;
}
dropFreeList();
}
void free(BT_Pointer pointer, int size) {
if (!freeListLifted) {
throw std::runtime_error("Free list must be lifted before freeing memory");
}
if (pointer.isNull() || size <= 0) {
throw std::invalid_argument("Cannot free null pointer or zero size");
}
auto freeList = getFreeList();
freeList.emplace_back(pointer, size);
// Merge contiguous blocks
std::sort(freeList.begin(), freeList.end(),
[](const BT_FreeListEntry& a, const BT_FreeListEntry& b) {
return a.pointer.address < b.pointer.address;
});
std::vector<BT_FreeListEntry> merged;
for (const auto& entry : freeList) {
if (merged.empty()) {
merged.push_back(entry);
} else {
auto& last = merged.back();
if (last.pointer.address + last.size == entry.pointer.address) {
last.size += entry.size;
} else {
merged.push_back(entry);
}
}
}
setFreeList(merged);
}
BT_Pointer alloc(int size) {
if (!freeListLifted) {
throw std::runtime_error("Free list must be lifted before allocation");
}
auto freeList = getFreeList();
if (freeList.empty()) {
return BT_Pointer(getFileSize());
}
// Find best fit
BT_FreeListEntry* bestFit = nullptr;
for (auto& entry : freeList) {
if (entry.size >= size) {
bestFit = &entry;
break;
}
}
if (!bestFit) {
return BT_Pointer(getFileSize());
}
BT_Pointer allocatedPointer = bestFit->pointer;
if (bestFit->size == size) {
// Exact fit - remove entry
freeList.erase(std::remove_if(freeList.begin(), freeList.end(),
[allocatedPointer](const BT_FreeListEntry& entry) {
return entry.pointer == allocatedPointer;
}), freeList.end());
} else {
// Split block
bestFit->pointer = BT_Pointer(bestFit->pointer.address + size);
bestFit->size -= size;
}
setFreeList(freeList);
return allocatedPointer;
}
template<typename T>
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");
}
auto valueBuffer = encodeValue(value);
BT_Pointer valueAddress = alloc(valueBuffer.size());
_file.seekp(valueAddress.address);
_file.write(reinterpret_cast<char*>(valueBuffer.data()), valueBuffer.size());
addressTable[keyHash] = valueAddress;
setAddressTable(addressTable);
});
}
template<typename T>
T get(const std::string& key) {
auto addressTable = getAddressTable();
uint64_t keyHash = fnv1aHash(key);
auto it = addressTable.find(keyHash);
if (it == addressTable.end()) {
throw std::runtime_error("Key does not exist");
}
BT_Reference valueRef(this, it->second);
return valueRef.decodeValue<T>();
}
void remove(const std::string& key) {
antiFreeListScope([&]() {
auto addressTable = getAddressTable();
uint64_t keyHash = fnv1aHash(key);
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);
});
}
// Friend declarations for template access
template<typename T> friend T BT_Reference::decodeValue();
friend int BT_Reference::getSize();
template<typename T> friend int BT_UniformArray<T>::getLength();
template<typename T> friend T BT_UniformArray<T>::operator[](int index);
template<typename T> friend void BT_UniformArray<T>::set(int index, const T& value);
template<typename T> friend void BT_UniformArray<T>::add(const T& value);
template<typename T> friend void BT_UniformArray<T>::addAll(const std::vector<T>& values);
template<typename T> friend BT_Type BT_UniformArray<T>::getElementType();
template<typename T> friend int BT_UniformArray<T>::getSize();
};
// Template implementations
template<typename T>
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<char*>(&typeId), 1);
BT_Type type = static_cast<BT_Type>(typeId);
if constexpr (std::is_same_v<T, int32_t>) {
if (type != BT_Type::INTEGER) {
throw std::runtime_error("Type mismatch");
}
return _table->readInt32(_table->_file.tellg());
} else if constexpr (std::is_same_v<T, float>) {
if (type != BT_Type::FLOAT) {
throw std::runtime_error("Type mismatch");
}
return _table->readFloat32(_table->_file.tellg());
} else if constexpr (std::is_same_v<T, std::string>) {
if (type != BT_Type::STRING) {
throw std::runtime_error("Type mismatch");
}
int32_t length = _table->readInt32(_table->_file.tellg());
_table->_file.seekg(static_cast<std::streampos>(_table->_file.tellg()) + 4);
std::string result(length, '\0');
_table->_file.read(&result[0], length);
return result;
} else if constexpr (std::is_same_v<T, BT_UniformArray<int32_t>>) {
if (type != BT_Type::INTEGER_ARRAY) {
throw std::runtime_error("Type mismatch");
}
return BT_UniformArray<int32_t>(_table, _pointer);
} else if constexpr (std::is_same_v<T, BT_UniformArray<float>>) {
if (type != BT_Type::FLOAT_ARRAY) {
throw std::runtime_error("Type mismatch");
}
return BT_UniformArray<float>(_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<char*>(&typeId), 1);
BT_Type type = static_cast<BT_Type>(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<typename T>
int BT_UniformArray<T>::getLength() {
if (_pointer.isNull()) {
return 0;
}
_table->_file.seekg(_pointer.address);
uint8_t typeId;
_table->_file.read(reinterpret_cast<char*>(&typeId), 1);
BT_Type type = static_cast<BT_Type>(typeId);
if (!isBT_TypeArray(type)) {
throw std::runtime_error("Not an array");
}
return _table->readInt32(_table->_file.tellg());
}
template<typename T>
T BT_UniformArray<T>::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<char*>(&typeId), 1);
BT_Type type = static_cast<BT_Type>(typeId);
int itemOffset = index * (1 + getBT_TypeSize(type));
BT_Reference itemRef(_table, BT_Pointer(_pointer.address + 1 + 4 + itemOffset));
return itemRef.decodeValue<T>();
}
template<typename T>
void BT_UniformArray<T>::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<char*>(&typeId), 1);
BT_Type type = static_cast<BT_Type>(typeId);
BT_Type newValueType = getBT_TypeFromValue<T>();
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<char*>(valueBuffer.data()), valueBuffer.size());
}
template<typename T>
void BT_UniformArray<T>::add(const T& value) {
addAll({value});
}
template<typename T>
void BT_UniformArray<T>::addAll(const std::vector<T>& values) {
_table->antiFreeListScope([&]() {
BT_Type elementType = getElementType();
// Validate types
for (const auto& value : values) {
BT_Type newValueType = getBT_TypeFromValue<T>();
if (newValueType != elementType) {
throw std::runtime_error("Type mismatch");
}
}
// 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<uint8_t> fullBuffer(bufferSize);
_table->_file.read(reinterpret_cast<char*>(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<uint32_t>(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<char*>(fullBuffer.data()), fullBuffer.size());
});
}
template<typename T>
BT_Type BT_UniformArray<T>::getElementType() {
if (getLength() == 0) {
return getBT_TypeFromValue<T>();
}
_table->_file.seekg(_pointer.address + 1 + 4);
uint8_t typeId;
_table->_file.read(reinterpret_cast<char*>(&typeId), 1);
return static_cast<BT_Type>(typeId);
}
template<typename T>
int BT_UniformArray<T>::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<uint8_t>& 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<int>(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<int>(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<char>(byte);
} else {
buffer << '.';
}
}
}
buffer << " | ";
if (i + 16 < data.size()) buffer << "\n";
}
return buffer.str();
}
int main() {
// Remove existing file
std::remove("example.bin");
BinaryTable table("example.bin");
table.initialize();
// Read file for dump
std::ifstream file("example.bin", std::ios::binary);
std::vector<uint8_t> fileData((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
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<int32_t>{6, 3, 9, 2, 5});
table.set("float_array", std::vector<float>{1.5f, 2.5f, 3.5f});
table.set("empty", std::vector<int32_t>{});
// Get arrays and modify
auto intArray = table.get<BT_UniformArray<int32_t>>("int_array");
auto floatArray = table.get<BT_UniformArray<float>>("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<BT_UniformArray<int32_t>>("int_array");
auto readback2 = table.get<BT_UniformArray<float>>("float_array");
auto readback3 = table.get<BT_UniformArray<int32_t>>("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";
}
// Final file dump
std::ifstream finalFile("example.bin", std::ios::binary);
std::vector<uint8_t> finalData((std::istreambuf_iterator<char>(finalFile)),
std::istreambuf_iterator<char>());
finalFile.close();
std::cout << "\nFile dump:\n";
std::cout << binaryDump(finalData) << "\n";
std::cout << "File size: " << finalData.size() << " bytes\n";
return 0;
}