Files

141 lines
4.5 KiB
C++

#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "Codec.h"
#include "Math.h"
#include "SaveIntegrity.h"
#include "SaveMigration.h"
namespace vf {
// In-memory backup chain for a region save container.
//
// A full backup keeps every chunk payload. An incremental keeps only the
// payloads whose checksum moved since the entry before it, plus the list of
// chunks that disappeared, so a long series of small edits costs a handful of
// records rather than a copy of the world. Every entry also records the
// checksum of every chunk that was live at that moment, which is what makes a
// restore verifiable rather than hopeful.
//
// Time is always a tick handed in by the caller. Nothing here reads a clock, so
// the same sequence of captures and prunes gives the same chain every run.
//
// Restores go back through the shared canonical packer, so a restored buffer is
// byte for byte the buffer that was captured. That only holds for a canonical
// layout, which is why capture refuses a fragmented container and points at the
// compactor instead.
enum class BackupKind : uint8_t {
Full = 0,
Incremental = 1,
};
const char* backupKindName(BackupKind kind);
// One chunk inside an entry. In `stored` the payload is present and the
// checksum describes it; in `state` the payload is left empty and only the
// checksum is carried, because the state list covers the whole world.
struct BackupChunk {
Vec3i coord;
uint32_t checksum = 0;
ByteBuffer payload;
};
struct BackupEntry {
uint32_t id = 0;
uint32_t parentId = 0;
BackupKind kind = BackupKind::Full;
uint64_t tick = 0;
SaveVersion version = currentSaveVersion;
int regionX = 0;
int regionZ = 0;
uint32_t sectorBytes = saveSectorBytes;
std::vector<BackupChunk> stored;
std::vector<Vec3i> removed;
std::vector<BackupChunk> state;
std::size_t storedBytes() const;
const BackupChunk* findStored(const Vec3i& coord) const;
const BackupChunk* findState(const Vec3i& coord) const;
};
// The chain itself: one full backup followed by the incrementals hanging off
// it. Walking a chain is the only supported way to reach an entry, and a walk
// that cannot reach a full backup is reported instead of guessed around.
class BackupIndex {
public:
std::vector<BackupEntry> entries;
const BackupEntry* find(uint32_t id) const;
BackupEntry* find(uint32_t id);
uint32_t latestId() const;
std::size_t count() const { return entries.size(); }
std::size_t storedBytes() const;
// Ancestors first, `id` last. On a break `complete` is false and `failure`
// says which link is missing.
std::vector<uint32_t> chainTo(uint32_t id, bool& complete, std::string& failure) const;
};
struct RestoreResult {
bool ok = false;
ByteBuffer save;
uint32_t fromId = 0;
std::vector<uint32_t> chain;
std::size_t chunksRestored = 0;
std::size_t chunksFromFull = 0;
std::size_t chunksFromIncrements = 0;
std::size_t chunksRemoved = 0;
std::size_t incrementsApplied = 0;
std::string failure;
};
// Keep the newest `keepLatest` entries, plus the newest entry of each of the
// last `dailyBuckets` days and `weeklyBuckets` weeks, plus every ancestor those
// entries need. Days and weeks are measured in ticks against the tick passed to
// prune, never against a system clock.
struct RetentionPolicy {
std::size_t keepLatest = 3;
std::size_t dailyBuckets = 1;
std::size_t weeklyBuckets = 1;
uint64_t dayTicks = 24000;
uint64_t weekTicks = 168000;
};
class BackupStore {
public:
RetentionPolicy retention;
SaveScanner scanner;
uint32_t capture(BackupKind kind, const ByteBuffer& save, uint64_t tick);
// Lifts an older container to the current version first, so a chain never
// mixes formats. Returns 0 and sets lastFailure if the migration refuses.
uint32_t captureMigrated(BackupKind kind, const ByteBuffer& save, uint64_t tick);
RestoreResult restore(uint32_t id) const;
RestoreResult restoreLatest() const;
// Deletes an entry without touching the rest, the way a stray file removal
// would. Chains that depended on it become detectably broken.
bool drop(uint32_t id);
std::vector<uint32_t> prune(uint64_t nowTick);
const BackupIndex& index() const { return chain; }
const std::string& lastFailure() const { return failure; }
std::size_t storedBytes() const { return chain.storedBytes(); }
private:
BackupIndex chain;
uint32_t nextId = 1;
std::string failure;
};
} // namespace vf