2024-09-20 21:30:47 -04:00

185 lines
4.5 KiB
C++

#include <algorithm>
#include "storage.h"
#include <libreborn/libreborn.h>
// Setup
#define DEFAULT_SIZE 16777216 // 16 MiB
Storage::Storage(const int chunks) {
// Allocate
total_size = DEFAULT_SIZE;
used_size = 0;
for (int i = 0; i < chunks; i++) {
chunk_to_block.push_back(nullptr);
}
buffer = new Buffer(total_size);
// First Free Block
Block *block = new Block;
block->offset = 0;
block->size = total_size;
free_blocks.push_back(block);
}
Storage::~Storage() {
delete buffer;
for (const std::vector<Block *> &blocks : {free_blocks, used_blocks}) {
for (const Block *block : blocks) {
delete block;
}
}
}
// Free Block
void Storage::free_block(Block *block) {
// Check
if (block == nullptr) {
return;
}
// Find Block
std::vector<Block *>::iterator it = std::ranges::find(used_blocks, block);
if (it == used_blocks.end()) {
return;
}
used_blocks.erase(it);
// Update Size
used_size -= block->size;
// Merge With Next/Previous Blocks
for (it = free_blocks.begin(); it < free_blocks.end(); ++it) {
const Block *x = *it;
if (x->offset == (block->offset + block->size)) {
// Found Free Block After Target
block->size += x->size;
free_blocks.erase(it);
delete x;
break;
}
}
for (it = free_blocks.begin(); it < free_blocks.end(); ++it) {
const Block *x = *it;
if ((x->offset + x->size) == block->offset) {
// Found Free Block Before Target
block->size += x->size;
block->offset -= x->size;
free_blocks.erase(it);
delete x;
break;
}
}
// Add To Free Block List
free_blocks.push_back(block);
// Fragmentation
check_fragmentation();
}
// Check Fragmentation
ssize_t Storage::get_end() const {
ssize_t end = 0;
for (const Block *block : used_blocks) {
const ssize_t new_end = block->offset + block->size;
if (new_end > end) {
end = new_end;
}
}
return end;
}
void Storage::check_fragmentation() {
const ssize_t end = get_end();
if (end == 0) {
return;
}
const float fragmentation = 1.0f - (float(used_size) / float(end));
if (fragmentation >= 0.5f) {
// 50% Fragmentation
recreate();
}
}
// Upload Chunk
void Storage::upload(const int chunk, const ssize_t size, const void *data) {
// Free Old Block
free_block(chunk_to_block[chunk]);
// Check Size
if (size == 0) {
chunk_to_block[chunk] = nullptr;
return;
}
// Find Free Block
std::vector<Block *>::iterator it;
while (true) {
for (it = free_blocks.begin(); it < free_blocks.end(); ++it) {
if ((*it)->size >= size) {
break;
}
}
if (it == free_blocks.end()) {
// Get Extra Space
recreate(size);
} else {
// Done!
break;
}
}
Block *old_free_block = *it;
// Create New Used Block
Block *new_used_block = new Block;
new_used_block->offset = old_free_block->offset;
new_used_block->size = size;
used_blocks.push_back(new_used_block);
// Update Old Free Block
old_free_block->offset += size;
old_free_block->size -= size;
if (old_free_block->size == 0) {
free_blocks.erase(it);
delete old_free_block;
}
// Upload
buffer->upload(new_used_block->offset, size, data);
// Assign Block To Chunk
chunk_to_block[chunk] = new_used_block;
// Update Size
used_size += size;
// Check Fragmentation
check_fragmentation();
}
// Recreate/Defragment
void Storage::recreate(const ssize_t extra_size) {
// Create New Buffer
const ssize_t new_size = (used_size + extra_size) * 2;
Buffer *new_buffer = new Buffer(new_size);
// Copy Used Blocks
intptr_t offset = 0;
for (Block *block : used_blocks) {
new_buffer->upload(offset, block->size, buffer->client_side_data + block->offset);
block->offset = offset;
offset += block->size;
}
// Update Free Blocks
for (const Block *block : free_blocks) {
delete block;
}
free_blocks.clear();
Block *block = new Block;
block->offset = offset;
block->size = new_size - offset;
free_blocks.push_back(block);
// Use New Buffer
delete buffer;
buffer = new_buffer;
total_size = new_size;
}