954 lines
30 KiB
C++
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;
|
|
} |