Add RandomAccessMemory class for in-memory binary operations
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace bt {
|
||||
|
||||
@@ -483,8 +486,25 @@ BinaryTable::~BinaryTable() {
|
||||
|
||||
void BinaryTable::initialize() {
|
||||
fseek(file_, 0, SEEK_SET);
|
||||
writeInt64(0, BT_Null.address()); // Address table pointer (8 bytes)
|
||||
writeInt32(8, 0); // Free list entry count (4 bytes)
|
||||
|
||||
// Magic number "SWPS" (0/4 bytes)
|
||||
const char magic[] = "SWPS";
|
||||
fwrite(magic, 1, 4, file_);
|
||||
|
||||
// Version (1.0 float16) (4/2 bytes)
|
||||
uint16_t version = 0x3C00; // 1.0 in float16 format
|
||||
uint8_t versionBytes[2] = {
|
||||
static_cast<uint8_t>(version & 0xFF),
|
||||
static_cast<uint8_t>((version >> 8) & 0xFF)
|
||||
};
|
||||
fwrite(versionBytes, 1, 2, file_);
|
||||
|
||||
// Address table pointer (null) (6/8 bytes)
|
||||
writeInt64(6, BT_Null.address());
|
||||
|
||||
// Free list count (0) (14/4 bytes)
|
||||
writeInt32(14, 0);
|
||||
|
||||
fflush(file_);
|
||||
}
|
||||
|
||||
@@ -600,7 +620,7 @@ void BinaryTable::setFilePosition(int64_t position) {
|
||||
|
||||
// Address table management
|
||||
std::unordered_map<int64_t, BT_Pointer> BinaryTable::getAddressTable() {
|
||||
int64_t tableAddress = readInt64(0);
|
||||
int64_t tableAddress = readInt64(6);
|
||||
DEBUG_PRINTLN("DEBUG: getAddressTable reading from address " << tableAddress);
|
||||
|
||||
if (tableAddress == -1) { // Null pointer
|
||||
@@ -674,7 +694,7 @@ void BinaryTable::setAddressTable(const std::unordered_map<int64_t, BT_Pointer>&
|
||||
}
|
||||
|
||||
// Read old table pointer FIRST to ensure we can clean it up later
|
||||
int64_t oldTablePointerAddress = readInt64(0);
|
||||
int64_t oldTablePointerAddress = readInt64(6);
|
||||
BT_Pointer oldTablePtr(oldTablePointerAddress);
|
||||
int32_t oldTableSize = 0;
|
||||
|
||||
@@ -729,7 +749,7 @@ void BinaryTable::setAddressTable(const std::unordered_map<int64_t, BT_Pointer>&
|
||||
fflush(file_);
|
||||
|
||||
// Atomically update header to point to new table
|
||||
writeInt64(0, newTableAddress.address());
|
||||
writeInt64(6, newTableAddress.address());
|
||||
fflush(file_);
|
||||
|
||||
// Only free old table after new one is successfully committed
|
||||
@@ -748,19 +768,15 @@ std::vector<BT_FreeListEntry> BinaryTable::getFreeList() {
|
||||
return freeListCache_;
|
||||
}
|
||||
|
||||
int64_t fileLength = getFileLength();
|
||||
if (fileLength < 4) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int32_t entryCount = readInt32(fileLength - 4);
|
||||
int32_t entryCount = readInt32(14);
|
||||
if (entryCount == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int32_t entrySize = 8 + 4; // Pointer + Size
|
||||
int32_t freeListSize = entryCount * entrySize;
|
||||
int64_t freeListStart = fileLength - 4 - freeListSize;
|
||||
int64_t fileLength = getFileLength();
|
||||
int64_t freeListStart = fileLength - freeListSize;
|
||||
|
||||
std::vector<BT_FreeListEntry> freeList;
|
||||
for (int32_t i = 0; i < entryCount; i++) {
|
||||
@@ -781,65 +797,44 @@ void BinaryTable::setFreeList(const std::vector<BT_FreeListEntry>& list) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always remove old free list first (matching Dart behavior)
|
||||
int64_t fileLength = getFileLength();
|
||||
DEBUG_PRINTLN("DEBUG: setFreeList fileLength=" << fileLength);
|
||||
|
||||
// Calculate old free list size to remove
|
||||
int32_t oldEntryCount = 0;
|
||||
if (fileLength >= 4) {
|
||||
oldEntryCount = readInt32(fileLength - 4);
|
||||
}
|
||||
DEBUG_PRINTLN("DEBUG: setFreeList oldEntryCount=" << oldEntryCount);
|
||||
|
||||
// Remove old free list (matching Dart: always truncate first)
|
||||
// Read OLD count from header (position 14)
|
||||
int32_t oldEntryCount = readInt32(14);
|
||||
|
||||
// Calculate old free list size (entries only, not count)
|
||||
int32_t oldListSize = oldEntryCount * (8 + 4);
|
||||
|
||||
// Remove old free list entries from EOF
|
||||
if (oldEntryCount > 0) {
|
||||
int32_t oldListSize = (oldEntryCount * (8 + 4)) + 4; // Entries + Count
|
||||
int64_t newFileLength = fileLength - oldListSize;
|
||||
DEBUG_PRINTLN("DEBUG: setFreeList - removing old free list, oldListSize=" << oldListSize << ", truncating to: " << newFileLength);
|
||||
truncateFile(newFileLength);
|
||||
fileLength = newFileLength; // Update file length
|
||||
int64_t currentLength = getFileLength();
|
||||
truncateFile(currentLength - oldListSize);
|
||||
}
|
||||
|
||||
// If the new free list is empty, we're done (old list already removed)
|
||||
if (list.empty()) {
|
||||
DEBUG_PRINTLN("DEBUG: setFreeList - empty list, old list removed, done");
|
||||
return;
|
||||
}
|
||||
|
||||
// Write new free list at end of file
|
||||
int64_t newLogicalEnd = fileLength;
|
||||
|
||||
// Encode new free list
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
// Entries
|
||||
for (const auto& entry : list) {
|
||||
// Pointer (8 bytes, little endian)
|
||||
int64_t addr = entry.pointer.address();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
buffer.push_back(static_cast<uint8_t>((addr >> (i * 8)) & 0xFF));
|
||||
}
|
||||
// Size (4 bytes, little endian)
|
||||
int32_t size = entry.size;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
buffer.push_back(static_cast<uint8_t>((size >> (i * 8)) & 0xFF));
|
||||
|
||||
// Write NEW count to header (position 14)
|
||||
writeInt32(14, static_cast<int32_t>(list.size()));
|
||||
|
||||
// Write NEW entries to EOF (if any)
|
||||
if (!list.empty()) {
|
||||
// Encode new free list entries
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
for (const auto& entry : list) {
|
||||
// Pointer (8 bytes, little endian)
|
||||
int64_t addr = entry.pointer.address();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
buffer.push_back(static_cast<uint8_t>((addr >> (i * 8)) & 0xFF));
|
||||
}
|
||||
// Size (4 bytes, little endian)
|
||||
int32_t size = entry.size;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
buffer.push_back(static_cast<uint8_t>((size >> (i * 8)) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
fseek(file_, 0, SEEK_END);
|
||||
fwrite(buffer.data(), 1, buffer.size(), file_);
|
||||
}
|
||||
|
||||
// Entry count (4 bytes, little endian)
|
||||
int32_t count = static_cast<int32_t>(list.size());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
buffer.push_back(static_cast<uint8_t>((count >> (i * 8)) & 0xFF));
|
||||
}
|
||||
|
||||
// Write at the logical end position
|
||||
fseek(file_, newLogicalEnd, SEEK_SET);
|
||||
fwrite(buffer.data(), 1, buffer.size(), file_);
|
||||
|
||||
fflush(file_);
|
||||
|
||||
// Update logical file length
|
||||
// File will be extended automatically by write operations
|
||||
}
|
||||
|
||||
void BinaryTable::truncateFile(int64_t newSize) {
|
||||
@@ -865,29 +860,24 @@ void BinaryTable::liftFreeList() {
|
||||
throw std::runtime_error("Free list is already lifted");
|
||||
}
|
||||
|
||||
// Cache the free list
|
||||
freeListCache_ = getFreeList();
|
||||
|
||||
// Remove free list from end of file
|
||||
int64_t fileLength = getFileLength();
|
||||
int32_t oldEntryCount = (fileLength >= 4) ? readInt32(fileLength - 4) : 0;
|
||||
// Read count from header (position 14)
|
||||
int32_t oldEntryCount = readInt32(14);
|
||||
|
||||
if (oldEntryCount > 0) {
|
||||
int32_t oldEntrySize = 8 + 4;
|
||||
int32_t oldFreeListSize = oldEntryCount * oldEntrySize + 4;
|
||||
int64_t newFileLength = fileLength - oldFreeListSize;
|
||||
int32_t oldFreeListSize = oldEntryCount * oldEntrySize; // Just entries, no count
|
||||
|
||||
// Store current file position to restore later if needed
|
||||
long currentPos = ftell(file_);
|
||||
|
||||
// Properly truncate the file
|
||||
truncateFile(newFileLength);
|
||||
|
||||
// Restore file position if it's still valid
|
||||
if (currentPos >= 0 && currentPos < newFileLength) {
|
||||
fseek(file_, currentPos, SEEK_SET);
|
||||
}
|
||||
// Remove free list entries from EOF
|
||||
int64_t fileLength = getFileLength();
|
||||
truncateFile(fileLength - oldFreeListSize);
|
||||
}
|
||||
|
||||
// Clear count in header (position 14)
|
||||
writeInt32(14, 0);
|
||||
|
||||
freeListLifted_ = true;
|
||||
}
|
||||
|
||||
@@ -898,9 +888,7 @@ void BinaryTable::dropFreeList() {
|
||||
}
|
||||
|
||||
freeListLifted_ = false;
|
||||
DEBUG_PRINTLN("DEBUG: About to call setFreeList - this might corrupt the address table!");
|
||||
setFreeList(freeListCache_);
|
||||
DEBUG_PRINTLN("DEBUG: setFreeList completed");
|
||||
setFreeList(freeListCache_); // This now writes count to header and entries to EOF
|
||||
freeListCache_.clear();
|
||||
}
|
||||
|
||||
@@ -1106,4 +1094,150 @@ void BinaryTable::debugAddressTable(const std::string& context) {
|
||||
DEBUG_PRINTLN("=========================");
|
||||
}
|
||||
|
||||
} // namespace bt
|
||||
std::string binaryDump(const std::vector<uint8_t>& data) {
|
||||
std::ostringstream buffer;
|
||||
|
||||
for (size_t i = 0; i < data.size(); i += 16) {
|
||||
// Address
|
||||
buffer << "0x" << std::hex << std::setfill('0') << std::setw(4) << std::uppercase << i
|
||||
<< " (" << std::dec << std::setfill(' ') << std::setw(4) << i << ") | ";
|
||||
|
||||
// Hex bytes
|
||||
for (int j = 0; j < 16; j++) {
|
||||
if (i + j < data.size()) {
|
||||
buffer << std::hex << std::setfill('0') << std::setw(2) << 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::dec << std::setfill(' ') << std::setw(3)
|
||||
<< 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();
|
||||
}
|
||||
|
||||
} // namespace bt
|
||||
|
||||
int main() {
|
||||
// Delete existing file if it exists
|
||||
if (std::filesystem::exists("example.bin")) {
|
||||
std::filesystem::remove("example.bin");
|
||||
}
|
||||
|
||||
bt::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 << bt::binaryDump(fileData) << "\n";
|
||||
std::cout << "File size: " << fileData.size() << " bytes\n";
|
||||
std::cout << " \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 elements
|
||||
auto intArray = table.getArray<int32_t>("int_array");
|
||||
auto floatArray = table.getArray<float>("float_array");
|
||||
|
||||
intArray.set(0, 1);
|
||||
floatArray.set(1, 4.5f);
|
||||
|
||||
std::cout << "int_array pointer: " << intArray.getPointer().address() << "\n";
|
||||
std::cout << "float_array pointer: " << floatArray.getPointer().address() << "\n";
|
||||
|
||||
intArray.add(10);
|
||||
floatArray.add(5.5f);
|
||||
|
||||
intArray.addAll({420, 69, 1337, 1738});
|
||||
floatArray.addAll({6.5f, 7.5f, 8.5f});
|
||||
|
||||
// Test the fetchSublist method
|
||||
auto intArraySublist = intArray.fetchSublist(0, 3);
|
||||
auto floatArraySublist = floatArray.fetchSublist(0, 2);
|
||||
std::cout << "Sublist int_array (0-3): ";
|
||||
for (auto val : intArraySublist) {
|
||||
std::cout << val << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
std::cout << "Sublist float_array (0-2): ";
|
||||
for (auto val : floatArraySublist) {
|
||||
std::cout << val << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
// Read back values
|
||||
auto readback1 = table.get<std::vector<int32_t>>("int_array");
|
||||
auto readback2 = table.get<std::vector<float>>("float_array");
|
||||
auto readback3 = table.get<std::vector<int32_t>>("empty");
|
||||
|
||||
std::cout << "Readback1: ";
|
||||
for (auto val : readback1) {
|
||||
std::cout << val << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
std::cout << "Readback2: ";
|
||||
for (auto val : readback2) {
|
||||
std::cout << val << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
std::cout << "Readback3: ";
|
||||
for (auto val : readback3) {
|
||||
std::cout << val << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
std::cout << " \n";
|
||||
|
||||
// Final file dump
|
||||
std::ifstream finalFile("example.bin", std::ios::binary);
|
||||
std::vector<uint8_t> finalFileData((std::istreambuf_iterator<char>(finalFile)),
|
||||
std::istreambuf_iterator<char>());
|
||||
finalFile.close();
|
||||
|
||||
std::cout << "File dump:\n";
|
||||
std::cout << bt::binaryDump(finalFileData) << "\n";
|
||||
std::cout << "File size: " << finalFileData.size() << " bytes\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user