]> git.localhorst.tv Git - blank.git/commitdiff
save and load chunk data
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 10 Aug 2015 14:24:21 +0000 (16:24 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 10 Aug 2015 14:24:21 +0000 (16:24 +0200)
currently running a one chunk per file approach for simplicity

15 files changed:
Makefile
TODO
building
src/app/WorldState.cpp
src/app/WorldState.hpp
src/app/runtime.cpp
src/ui/ui.cpp
src/world/Chunk.hpp
src/world/ChunkLoader.hpp
src/world/Generator.cpp
src/world/World.cpp
src/world/World.hpp
src/world/WorldSave.cpp
src/world/WorldSave.hpp
src/world/chunk.cpp

index 9b261db863a2bb9c7b62e4d16cb171dc220c77d2..36fd0214bb30a3be2a445a74d492fcff38864711 100644 (file)
--- 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 f3c43c6460254048b980a0d0c887da130c0d967b..1ba83abd0205fa8751dfaf312ae9bbb65ea75c9d 100644 (file)
--- 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
index 98e21ecc882734e60eea99bc59999c7b29a6e454..bc22d9b55423ad9a8db25396434f5b8cd639b34c 100644 (file)
--- 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
index b686ab28b058f14608e67b1ee58ef0d9ec587ad4..12e142ac5a4cfe086f50e084d725162144a464d3 100644 (file)
@@ -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) {
 
index e4edd121cc664fa45e4fa713628891dbe724ab32..38f894420b717115c0cb049beb67c1f130b2b702 100644 (file)
@@ -18,7 +18,8 @@ public:
        WorldState(
                Environment &,
                const Interface::Config &,
-               const World::Config &
+               const World::Config &,
+               const WorldSave &
        );
 
        void Handle(const SDL_Event &) override;
index 30b15aeb4b0d105b713255fbba892837481d45a7..32a401220215dbeeb557e3a53e9b6a5f7ccf6737 100644 (file)
@@ -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());
index e53cff606291d12d4f4fde960a49af38fa169f01..0c89a2f3a554ab6c0347ed669bdd71d801dea22a 100644 (file)
@@ -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();
index f4f0bf006300d0c68b40e88b6aed65210ce29e96..e9a075b2168bef5238c3d3562fd0b1b4f0d0d7dd 100644 (file)
@@ -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;
 
 };
 
index 6a8c2c19ac30d2143d42f85f01512d68c63af531..b2730a67e15fdcaa2cf1b1e7039cf56d1c507427 100644 (file)
@@ -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<Chunk> &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 &reg;
        const Generator &gen;
+       const WorldSave &save;
 
        std::list<Chunk> loaded;
-       std::list<Chunk::Pos> to_generate;
+       std::list<Chunk::Pos> to_load;
        std::list<Chunk> to_free;
 
        IntervalTimer gen_timer;
index 3962252fa71837cf5152990a820dd03732f33ef1..c8621e75e42bc907d8656b1be33370a0b63ae2c2 100644 (file)
@@ -47,8 +47,7 @@ void Generator::operator ()(Chunk &chunk) const noexcept {
                        }
                }
        }
-       chunk.Invalidate();
-       chunk.CheckUpdate();
+       //chunk.CheckUpdate();
 }
 
 }
index 73a870dc28b7e1d9002d7efce6058a2f3894b71f..aa347e7f654d5e374b621ad1e4efe55a3419fd43 100644 (file)
@@ -5,7 +5,6 @@
 #include "../graphics/Format.hpp"
 #include "../graphics/Viewport.hpp"
 
-#include <iostream>
 #include <limits>
 #include <glm/gtx/io.hpp>
 #include <glm/gtx/transform.hpp>
 
 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());
 }
 
 
index 6bd8128dc9fb41073748952702ee071546306369..2c343dc7fd5a857e87c4705ee6ed1d972d311643 100644 (file)
@@ -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 &,
index 2bfa818017e675e5b26cdfcade708e3af1da426f..d0e8112e1d3b2e7cb48d865f688ff28f64938294 100644 (file)
@@ -5,7 +5,9 @@
 #include <cctype>
 #include <fstream>
 #include <iostream>
+#include <limits>
 #include <stdexcept>
+#include <zlib.h>
 
 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<int>::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();
+}
+
 }
index 7e40fc8ef5f05f0e2eedb2ab4261fbd62cc4eb18..b08b373f3e6719ee97dbb0ab21951a38e1d4da5b 100644 (file)
@@ -1,8 +1,10 @@
 #ifndef BLANK_WORLD_WORLDSAVE_HPP_
 #define BLANK_WORLD_WORLDSAVE_HPP_
 
+#include "Chunk.hpp"
 #include "World.hpp"
 
+#include <memory>
 #include <string>
 
 
@@ -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<char[]> chunk_buf;
 
 };
 
index 77899e79d5da360506502ae8954e54bf56648a3c..c4ce097922ec1065d5c150d7f7e51b81d7cbbcb7 100644 (file)
@@ -4,10 +4,11 @@
 
 #include "Generator.hpp"
 #include "WorldCollision.hpp"
+#include "WorldSave.hpp"
 
 #include <algorithm>
-#include <iostream>
 #include <limits>
+#include <ostream>
 #include <queue>
 
 
@@ -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 &reg, const Generator &gen) noexcept
+ChunkLoader::ChunkLoader(
+       const Config &config,
+       const BlockTypeRegistry &reg,
+       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);
 }