From: Daniel Karbach Date: Mon, 10 Aug 2015 14:24:21 +0000 (+0200) Subject: save and load chunk data X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=551573ecb04969696f916aeb5485658e298a7f6b;p=blank.git save and load chunk data currently running a one chunk per file approach for simplicity --- diff --git a/Makefile b/Makefile index 9b261db..36fd021 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = g++ --std=c++11 LDXX = g++ -LIBS = sdl2 SDL2_image SDL2_ttf glew openal freealut +LIBS = sdl2 SDL2_image SDL2_ttf glew openal freealut zlib PKGFLAGS := $(shell pkg-config --cflags $(LIBS)) PKGLIBS := $(shell pkg-config --libs $(LIBS)) diff --git a/TODO b/TODO index f3c43c6..1ba83ab 100644 --- a/TODO +++ b/TODO @@ -29,11 +29,6 @@ networking exchange of chunks and entities -persistence - - unloaded chunks should be saved to disk and restored when they - are loaded again - launcher ui select or create a world with configurable parameters diff --git a/building b/building index 98e21ec..bc22d9b 100644 --- a/building +++ b/building @@ -1,17 +1,19 @@ Dependencies ============ - GLEW, GLM, SDL2, SDL2_image, SDL2_ttf, OpenAL, freealut + GLEW, GLM, SDL2, SDL2_image, SDL2_ttf, OpenAL, freealut, zlib CppUnit for tests -archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf openal freealut cppunit +archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf openal freealut zlib cppunit manual: CppUnit http://sourceforge.net/projects/cppunit/ GLEW http://glew.sourceforge.net/ GLM http://glm.g-truc.net/0.9.6/index.html + OpenAL http://openal.org/ SDL http://www.libsdl.org/ + zlib http://zlib.net/ Makefile diff --git a/src/app/WorldState.cpp b/src/app/WorldState.cpp index b686ab2..12e142a 100644 --- a/src/app/WorldState.cpp +++ b/src/app/WorldState.cpp @@ -10,10 +10,11 @@ namespace blank { WorldState::WorldState( Environment &env, const Interface::Config &ic, - const World::Config &wc + const World::Config &wc, + const WorldSave &save ) : env(env) -, world(env.assets, wc) +, world(env.assets, wc, save) , spawner(world) , interface(ic, env, world) { diff --git a/src/app/WorldState.hpp b/src/app/WorldState.hpp index e4edd12..38f8944 100644 --- a/src/app/WorldState.hpp +++ b/src/app/WorldState.hpp @@ -18,7 +18,8 @@ public: WorldState( Environment &, const Interface::Config &, - const World::Config & + const World::Config &, + const WorldSave & ); void Handle(const SDL_Event &) override; diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 30b15ae..32a4012 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -236,12 +236,12 @@ int Runtime::Execute() { if (save.Exists()) { save.Read(config.world); } else { - save.Create(config.world); + save.Write(config.world); } Application app(env); - WorldState world_state(env, config.interface, config.world); + WorldState world_state(env, config.interface, config.world, save); app.PushState(&world_state); PreloadState preloader(env, world_state.GetWorld().Loader()); diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index e53cff6..0c89a2f 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -410,7 +410,6 @@ void Interface::PlaceBlock() { next_pos -= aim_normal * glm::vec3(Chunk::Extent()); } mod_chunk->SetBlock(next_pos, selection); - mod_chunk->Invalidate(); if (config.audio_disabled) return; const Entity &player = ctrl.Controlled(); @@ -423,7 +422,6 @@ void Interface::PlaceBlock() { void Interface::RemoveBlock() noexcept { if (!aim_chunk) return; aim_chunk->SetBlock(aim_block, remove); - aim_chunk->Invalidate(); if (config.audio_disabled) return; const Entity &player = ctrl.Controlled(); diff --git a/src/world/Chunk.hpp b/src/world/Chunk.hpp index f4f0bf0..e9a075b 100644 --- a/src/world/Chunk.hpp +++ b/src/world/Chunk.hpp @@ -110,8 +110,6 @@ public: // check which faces of a block at given index are obstructed (and therefore invisible) Block::FaceSet Obstructed(const Pos &) const noexcept; - void Invalidate() noexcept { dirty = true; } - void SetBlock(int index, const Block &) noexcept; void SetBlock(const Block::Pos &pos, const Block &block) noexcept { SetBlock(ToIndex(pos), block); } void SetBlock(const Pos &pos, const Block &block) noexcept { SetBlock(ToIndex(pos), block); } @@ -160,6 +158,17 @@ public: return glm::translate((position - offset) * Extent()); } + void *BlockData() noexcept { return &blocks[0]; } + const void *BlockData() const noexcept { return &blocks[0]; } + static constexpr std::size_t BlockSize() noexcept { return sizeof(blocks) + sizeof(light); } + + void Invalidate() noexcept { dirty_model = dirty_save = true; } + void InvalidateModel() noexcept { dirty_model = true; } + void ClearModel() noexcept { dirty_model = false; } + void ClearSave() noexcept { dirty_save = false; } + bool ShouldUpdateModel() const noexcept { return dirty_model; } + bool ShouldUpdateSave() const noexcept { return dirty_save; } + void CheckUpdate() noexcept; void Draw() noexcept; @@ -173,7 +182,8 @@ private: unsigned char light[size]; BlockModel model; Pos position; - bool dirty; + bool dirty_model; + bool dirty_save; }; diff --git a/src/world/ChunkLoader.hpp b/src/world/ChunkLoader.hpp index 6a8c2c1..b2730a6 100644 --- a/src/world/ChunkLoader.hpp +++ b/src/world/ChunkLoader.hpp @@ -11,6 +11,7 @@ namespace blank { class BlockTypeRegistry; class Generator; +class WorldSave; class ChunkLoader { @@ -21,10 +22,15 @@ public: int gen_limit = 16; }; - ChunkLoader(const Config &, const BlockTypeRegistry &, const Generator &) noexcept; + ChunkLoader( + const Config &, + const BlockTypeRegistry &, + const Generator &, + const WorldSave & + ) noexcept; - void Generate(const Chunk::Pos &from, const Chunk::Pos &to); - void GenerateSurrounding(const Chunk::Pos &); + void Queue(const Chunk::Pos &from, const Chunk::Pos &to); + void QueueSurrounding(const Chunk::Pos &); std::list &Loaded() noexcept { return loaded; } @@ -39,12 +45,12 @@ public: void Rebase(const Chunk::Pos &); void Update(int dt); - std::size_t ToLoad() const noexcept { return to_generate.size(); } + std::size_t ToLoad() const noexcept { return to_load.size(); } void LoadOne(); void LoadN(std::size_t n); private: - Chunk &Generate(const Chunk::Pos &pos); + Chunk &Load(const Chunk::Pos &pos); // link given chunk to all loaded neighbors void Insert(Chunk &) noexcept; // remove a loaded chunk @@ -59,9 +65,10 @@ private: const BlockTypeRegistry ® const Generator &gen; + const WorldSave &save; std::list loaded; - std::list to_generate; + std::list to_load; std::list to_free; IntervalTimer gen_timer; diff --git a/src/world/Generator.cpp b/src/world/Generator.cpp index 3962252..c8621e7 100644 --- a/src/world/Generator.cpp +++ b/src/world/Generator.cpp @@ -47,8 +47,7 @@ void Generator::operator ()(Chunk &chunk) const noexcept { } } } - chunk.Invalidate(); - chunk.CheckUpdate(); + //chunk.CheckUpdate(); } } diff --git a/src/world/World.cpp b/src/world/World.cpp index 73a870d..aa347e7 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -5,7 +5,6 @@ #include "../graphics/Format.hpp" #include "../graphics/Viewport.hpp" -#include #include #include #include @@ -13,14 +12,14 @@ namespace blank { -World::World(const Assets &assets, const Config &config) +World::World(const Assets &assets, const Config &config, const WorldSave &save) : blockType() , blockShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}) , stairShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f }) , slabShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }}) , block_tex() , generate(config.gen) -, chunks(config.load, blockType, generate) +, chunks(config.load, blockType, generate, save) , player() , entities() , light_direction(config.light_direction) @@ -195,7 +194,7 @@ World::World(const Assets &assets, const Config &config) player->WorldCollidable(true); player->Position(config.spawn); - chunks.GenerateSurrounding(player->ChunkCoords()); + chunks.QueueSurrounding(player->ChunkCoords()); } diff --git a/src/world/World.hpp b/src/world/World.hpp index 6bd8128..2c343dc 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -37,7 +37,7 @@ public: ChunkLoader::Config load = ChunkLoader::Config(); }; - World(const Assets &, const Config &); + World(const Assets &, const Config &, const WorldSave &); bool Intersection( const Ray &, diff --git a/src/world/WorldSave.cpp b/src/world/WorldSave.cpp index 2bfa818..d0e8112 100644 --- a/src/world/WorldSave.cpp +++ b/src/world/WorldSave.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include using namespace std; @@ -14,7 +16,10 @@ namespace blank { WorldSave::WorldSave(const string &path) : root_path(path) -, conf_path(path + "world.conf") { +, conf_path(path + "world.conf") +, chunk_path(path + "chunks/%d/%d/%d.gz") +, chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits::digits10) +, chunk_buf(new char[chunk_bufsiz]) { } @@ -24,22 +29,6 @@ bool WorldSave::Exists() const noexcept { } -void WorldSave::Create(const World::Config &conf) const { - cout << "creating world save" << endl; - - if (!make_dirs(root_path)) { - throw runtime_error("failed to create world save directory"); - } - - ofstream out(conf_path); - out << "seed = " << conf.gen.seed << endl; - out.close(); - - if (!out) { - throw runtime_error("failed to write world config"); - } -} - void WorldSave::Read(World::Config &conf) const { cout << "reading world save" << endl; @@ -81,4 +70,76 @@ void WorldSave::Read(World::Config &conf) const { } } +void WorldSave::Write(const World::Config &conf) const { + cout << "writing world save" << endl; + + if (!make_dirs(root_path)) { + throw runtime_error("failed to create world save directory"); + } + + ofstream out(conf_path); + out << "seed = " << conf.gen.seed << endl; + out.close(); + + if (!out) { + throw runtime_error("failed to write world config"); + } +} + + +bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept { + return is_file(ChunkPath(pos)); +} + +void WorldSave::Read(Chunk &chunk) const { + const char *path = ChunkPath(chunk.Position()); + gzFile file = gzopen(path, "r"); + if (!file) { + throw runtime_error("failed to open chunk file"); + } + if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) { + throw runtime_error("failed to read chunk from file"); + } + if (gzclose(file) != Z_OK) { + throw runtime_error("failed to read chunk file"); + } + chunk.InvalidateModel(); + chunk.ClearSave(); +} + +void WorldSave::Write(Chunk &chunk) const { + const char *path = ChunkPath(chunk.Position()); + gzFile file = gzopen(path, "w"); + if (!file) { + // check if it's because of a missing path component + if (errno != ENOENT) { + // nope, fatal + throw runtime_error(strerror(errno)); + } + string dir_path(path); + dir_path.erase(dir_path.find_last_of("\\/")); + if (!make_dirs(dir_path)) { + throw runtime_error("failed to create dir for chunk file"); + } + file = gzopen(path, "w"); + if (!file) { + throw runtime_error("failed to open chunk file"); + } + } + if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) { + gzclose(file); // if this fails, it can't be helped + throw runtime_error("failed to write chunk to file"); + } + if (gzclose(file) != Z_OK) { + throw runtime_error("failed to write chunk file"); + } + chunk.ClearSave(); +} + + +const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const { + snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z); + return chunk_buf.get(); +} + } diff --git a/src/world/WorldSave.hpp b/src/world/WorldSave.hpp index 7e40fc8..b08b373 100644 --- a/src/world/WorldSave.hpp +++ b/src/world/WorldSave.hpp @@ -1,8 +1,10 @@ #ifndef BLANK_WORLD_WORLDSAVE_HPP_ #define BLANK_WORLD_WORLDSAVE_HPP_ +#include "Chunk.hpp" #include "World.hpp" +#include #include @@ -14,14 +16,24 @@ public: explicit WorldSave(const std::string &path); public: + // whole save bool Exists() const noexcept; - - void Create(const World::Config &) const; void Read(World::Config &) const; + void Write(const World::Config &) const; + + // single chunk + bool Exists(const Chunk::Pos &) const noexcept; + void Read(Chunk &) const; + void Write(Chunk &) const; + + const char *ChunkPath(const Chunk::Pos &) const; private: std::string root_path; std::string conf_path; + std::string chunk_path; + std::size_t chunk_bufsiz; + std::unique_ptr chunk_buf; }; diff --git a/src/world/chunk.cpp b/src/world/chunk.cpp index 77899e7..c4ce097 100644 --- a/src/world/chunk.cpp +++ b/src/world/chunk.cpp @@ -4,10 +4,11 @@ #include "Generator.hpp" #include "WorldCollision.hpp" +#include "WorldSave.hpp" #include -#include #include +#include #include @@ -26,7 +27,8 @@ Chunk::Chunk(const BlockTypeRegistry &types) noexcept , light{0} , model() , position(0, 0, 0) -, dirty(false) { +, dirty_model(false) +, dirty_save(false) { } @@ -34,7 +36,8 @@ Chunk::Chunk(Chunk &&other) noexcept : types(other.types) , model(std::move(other.model)) , position(other.position) -, dirty(other.dirty) { +, dirty_model(other.dirty_model) +, dirty_save(other.dirty_save) { std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor); std::copy(other.blocks, other.blocks + sizeof(blocks), blocks); std::copy(other.light, other.light + sizeof(light), light); @@ -47,7 +50,8 @@ Chunk &Chunk::operator =(Chunk &&other) noexcept { std::copy(other.light, other.light + sizeof(light), light); model = std::move(other.model); position = other.position; - dirty = other.dirty; + dirty_model = other.dirty_save; + dirty_save = other.dirty_save; return *this; } @@ -148,6 +152,7 @@ void Chunk::SetBlock(int index, const Block &block) noexcept { const BlockType &new_type = Type(block); blocks[index] = block; + Invalidate(); if (&old_type == &new_type) return; @@ -425,7 +430,7 @@ bool Chunk::IsSurface(const Pos &pos) const noexcept { void Chunk::Draw() noexcept { - if (dirty) { + if (ShouldUpdateModel()) { Update(); } model.Draw(); @@ -508,7 +513,7 @@ BlockModel::Buffer buf; } void Chunk::CheckUpdate() noexcept { - if (dirty) { + if (ShouldUpdateModel()) { Update(); } } @@ -549,7 +554,7 @@ void Chunk::Update() noexcept { } model.Update(buf); - dirty = false; + ClearModel(); } Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept { @@ -639,12 +644,18 @@ BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexce } -ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry ®, const Generator &gen) noexcept +ChunkLoader::ChunkLoader( + const Config &config, + const BlockTypeRegistry ®, + const Generator &gen, + const WorldSave &save +) noexcept : base(0, 0, 0) , reg(reg) , gen(gen) +, save(save) , loaded() -, to_generate() +, to_load() , to_free() , gen_timer(config.gen_limit) , load_dist(config.load_dist) @@ -673,7 +684,7 @@ struct ChunkLess { } -void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) { +void ChunkLoader::Queue(const Chunk::Pos &from, const Chunk::Pos &to) { for (int z = from.z; z < to.z; ++z) { for (int y = from.y; y < to.y; ++y) { for (int x = from.x; x < to.x; ++x) { @@ -681,7 +692,7 @@ void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) { if (Known(pos)) { continue; } else if (pos == base) { - Generate(pos); + Load(pos); // light testing // for (int i = 0; i < 16; ++i) { @@ -728,19 +739,23 @@ void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) { // loaded.back().Invalidate(); // loaded.back().CheckUpdate(); } else { - to_generate.emplace_back(pos); + to_load.emplace_back(pos); } } } } - to_generate.sort(ChunkLess(base)); + to_load.sort(ChunkLess(base)); } -Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) { +Chunk &ChunkLoader::Load(const Chunk::Pos &pos) { loaded.emplace_back(reg); Chunk &chunk = loaded.back(); chunk.Position(pos); - gen(chunk); + if (save.Exists(pos)) { + save.Read(chunk); + } else { + gen(chunk); + } Insert(chunk); return chunk; } @@ -772,7 +787,7 @@ Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept { } bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept { - for (const Chunk::Pos &chunk : to_generate) { + for (const Chunk::Pos &chunk : to_load) { if (chunk == pos) { return true; } @@ -791,14 +806,14 @@ Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) { return *chunk; } - for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) { + for (auto iter(to_load.begin()), end(to_load.end()); iter != end; ++iter) { if (*iter == pos) { - to_generate.erase(iter); + to_load.erase(iter); break; } } - return Generate(pos); + return Load(pos); } bool ChunkLoader::OutOfRange(const Chunk::Pos &pos) const noexcept { @@ -822,20 +837,20 @@ void ChunkLoader::Rebase(const Chunk::Pos &new_base) { } } // abort far away queued chunks - for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) { + for (auto iter(to_load.begin()), end(to_load.end()); iter != end;) { if (OutOfRange(*iter)) { - iter = to_generate.erase(iter); + iter = to_load.erase(iter); } else { ++iter; } } // add missing new chunks - GenerateSurrounding(base); + QueueSurrounding(base); } -void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) { +void ChunkLoader::QueueSurrounding(const Chunk::Pos &pos) { const Chunk::Pos offset(load_dist, load_dist, load_dist); - Generate(pos - offset, pos + offset); + Queue(pos - offset, pos + offset); } void ChunkLoader::Update(int dt) { @@ -845,6 +860,18 @@ void ChunkLoader::Update(int dt) { if (gen_timer.Hit()) { LoadOne(); } + + constexpr int max_save = 10; + int saved = 0; + for (Chunk &chunk : loaded) { + if (chunk.ShouldUpdateSave()) { + save.Write(chunk); + ++saved; + if (saved >= max_save) { + break; + } + } + } } void ChunkLoader::LoadN(std::size_t n) { @@ -855,11 +882,11 @@ void ChunkLoader::LoadN(std::size_t n) { } void ChunkLoader::LoadOne() { - if (to_generate.empty()) return; + if (to_load.empty()) return; // take position of next chunk in queue - Chunk::Pos pos(to_generate.front()); - to_generate.pop_front(); + Chunk::Pos pos(to_load.front()); + to_load.pop_front(); // look if the same chunk was already generated and still lingering for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) { @@ -881,7 +908,11 @@ void ChunkLoader::LoadOne() { Chunk &chunk = loaded.back(); chunk.Position(pos); - gen(chunk); + if (save.Exists(pos)) { + save.Read(chunk); + } else { + gen(chunk); + } Insert(chunk); }