]> git.localhorst.tv Git - blank.git/commitdiff
split chunk stuff
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 9 Sep 2015 19:43:42 +0000 (21:43 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 9 Sep 2015 19:43:42 +0000 (21:43 +0200)
storage, indexing, redering, loading, etc are now all separated
so the different states can pull in what they need and it's more
flexible and makes way for some optimizations as well

29 files changed:
src/ai/Spawner.cpp
src/app/PreloadState.cpp
src/app/Runtime.hpp
src/app/ServerState.cpp
src/app/ServerState.hpp
src/app/UnloadState.cpp
src/app/UnloadState.hpp
src/app/WorldState.cpp
src/app/WorldState.hpp
src/app/runtime.cpp
src/client/InteractiveState.hpp
src/client/client.cpp
src/io/WorldSave.cpp
src/io/WorldSave.hpp
src/net/net.cpp
src/ui/Interface.hpp
src/ui/ui.cpp
src/world/Chunk.hpp
src/world/ChunkIndex.hpp [new file with mode: 0644]
src/world/ChunkLoader.hpp
src/world/ChunkRenderer.hpp
src/world/ChunkStore.hpp [new file with mode: 0644]
src/world/Generator.cpp
src/world/Player.hpp [new file with mode: 0644]
src/world/World.cpp
src/world/World.hpp
src/world/chunk.cpp
src/world/render.cpp [deleted file]
tst/world/ChunkTest.cpp

index 8ba32ffaf0e17ee6e5c50a179efdb94493a42dff..13a8f52e6f44eb8be438faee0553d185da1af06f 100644 (file)
@@ -6,6 +6,7 @@
 #include "../model/Skeletons.hpp"
 #include "../world/BlockLookup.hpp"
 #include "../world/BlockType.hpp"
+#include "../world/ChunkIndex.hpp"
 #include "../world/Entity.hpp"
 #include "../world/World.hpp"
 
@@ -51,11 +52,12 @@ void Spawner::CheckDespawn() noexcept {
                if (e.Dead()) {
                        delete *iter;
                        iter = controllers.erase(iter);
+                       end = controllers.end();
                        continue;
                }
                bool safe = false;
-               for (const Entity *ref : refs) {
-                       glm::vec3 diff(ref->AbsoluteDifference(e));
+               for (const Player &ref : refs) {
+                       glm::vec3 diff(ref.entity->AbsoluteDifference(e));
                        if (dot(diff, diff) < despawn_range) {
                                safe = true;
                                break;
@@ -65,6 +67,7 @@ void Spawner::CheckDespawn() noexcept {
                        e.Kill();
                        delete *iter;
                        iter = controllers.erase(iter);
+                       end = controllers.end();
                } else {
                        ++iter;
                }
@@ -77,13 +80,11 @@ void Spawner::TrySpawn() {
        // select random player to punish
        auto &players = world.Players();
        if (players.size() == 0) return;
-       Entity &player = *players[random.Next<unsigned short>() % players.size()];
+       const Player &player = players[random.Next<unsigned short>() % players.size()];
 
-       glm::ivec3 chunk(
-               (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
-               (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
-               (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range
-       );
+       int index = random.Next<unsigned int>() % player.chunks->TotalChunks();
+
+       glm::ivec3 chunk(player.chunks->PositionOf(index));
 
        glm::ivec3 pos(
                random.Next<unsigned char>() % Chunk::width,
@@ -92,16 +93,14 @@ void Spawner::TrySpawn() {
        );
 
        // distance check
-       glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.Position());
-       float dist = dot(diff, diff);
-       if (dist > despawn_range || dist < spawn_distance) {
-               return;
-       }
+       //glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.entity->Position());
+       //float dist = dot(diff, diff);
+       //if (dist > despawn_range || dist < spawn_distance) {
+       //      return;
+       //}
 
        // check if the spawn block and the one above it are loaded and inhabitable
-       BlockLookup spawn_block(
-               world.Loader().Loaded(player.ChunkCoords()),
-               chunk * Chunk::Extent() + pos);
+       BlockLookup spawn_block((*player.chunks)[index], pos);
        if (!spawn_block || spawn_block.GetType().collide_block) {
                return;
        }
@@ -111,7 +110,7 @@ void Spawner::TrySpawn() {
                return;
        }
 
-       Spawn(player, player.ChunkCoords() + chunk, glm::vec3(pos) + glm::vec3(0.5f));
+       Spawn(*player.entity, chunk, glm::vec3(pos) + glm::vec3(0.5f));
 }
 
 void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 &pos) {
index 3b686107a278febd6fcc7f01f0fe06a44fa1b5cf..7d5f3729735c741657870a879abc92bfff0f986f 100644 (file)
@@ -27,7 +27,7 @@ void PreloadState::Handle(const SDL_Event &e) {
 
 void PreloadState::Update(int dt) {
        loader.LoadN(per_update);
-       if (loader.ToLoad() == 0) {
+       if (loader.ToLoad() <= 0) {
                env.state.Pop();
                render.Update(render.MissingChunks());
        } else {
index 10a0e55cb559365366c29c090c2dc29bfb67778f..65ad408d4ffffd7c1a826142f2a5a6b8734e95f5 100644 (file)
@@ -5,6 +5,7 @@
 #include "../net/Client.hpp"
 #include "../net/Server.hpp"
 #include "../ui/Interface.hpp"
+#include "../world/Generator.hpp"
 #include "../world/World.hpp"
 
 #include <cstddef>
@@ -44,6 +45,7 @@ public:
                int multisampling = 1;
 
                Client::Config client = Client::Config();
+               Generator::Config gen = Generator::Config();
                HeadlessEnvironment::Config env = HeadlessEnvironment::Config();
                Interface::Config interface = Interface::Config();
                Server::Config server = Server::Config();
index c2163ad161976124f08b5bbcfc3cf1dba450d826..e83984106ed06c100c04c6d6f656fff8c0f9a89e 100644 (file)
@@ -11,15 +11,18 @@ namespace blank {
 
 ServerState::ServerState(
        HeadlessEnvironment &env,
+       const Generator::Config &gc,
        const World::Config &wc,
        const WorldSave &ws,
        const Server::Config &sc
 )
 : env(env)
 , block_types()
-, world(block_types, wc, ws)
+, world(block_types, wc)
+, generator(gc)
+, chunk_loader(world.Chunks(), generator, ws)
 , skeletons()
-, spawner(world, skeletons, wc.gen.seed)
+, spawner(world, skeletons, gc.seed)
 , server(sc, world)
 , push_timer(16) {
        TextureIndex tex_index;
index aa7e779c7dd7acf6736e930f50d463c288543775..0deb65aff460ed5158fa28064a3ef2756300ca00 100644 (file)
@@ -7,12 +7,15 @@
 #include "../model/Skeletons.hpp"
 #include "../net/Server.hpp"
 #include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkLoader.hpp"
+#include "../world/Generator.hpp"
 #include "../world/World.hpp"
 
 
 namespace blank {
 
 class HeadlessEnvironment;
+class WorldSave;
 
 class ServerState
 : public State {
@@ -20,6 +23,7 @@ class ServerState
 public:
        ServerState(
                HeadlessEnvironment &,
+               const Generator::Config &,
                const World::Config &,
                const WorldSave &,
                const Server::Config &
@@ -33,6 +37,8 @@ private:
        HeadlessEnvironment &env;
        BlockTypeRegistry block_types;
        World world;
+       Generator generator;
+       ChunkLoader chunk_loader;
        Skeletons skeletons;
        Spawner spawner;
        Server server;
index 534a14acb4a0b9496382fb6023b2ea3dd9418a35..43aae056f27727ebc036e75d69dc044549dcec6f 100644 (file)
@@ -7,14 +7,18 @@
 
 namespace blank {
 
-UnloadState::UnloadState(Environment &env, ChunkLoader &loader)
+UnloadState::UnloadState(
+       Environment &env,
+       ChunkStore &chunks,
+       const WorldSave &save)
 : env(env)
-, loader(loader)
+, chunks(chunks)
+, save(save)
 , progress(env.assets.large_ui_font)
-, cur(loader.Loaded().begin())
-, end(loader.Loaded().end())
+, cur(chunks.begin())
+, end(chunks.end())
 , done(0)
-, total(loader.Loaded().size())
+, total(chunks.NumLoaded())
 , per_update(64) {
        progress.Position(glm::vec3(0.0f), Gravity::CENTER);
        progress.Template("Unloading chunks: %d/%d (%d%%)");
@@ -22,10 +26,10 @@ UnloadState::UnloadState(Environment &env, ChunkLoader &loader)
 
 
 void UnloadState::OnResume() {
-       cur = loader.Loaded().begin();
-       end = loader.Loaded().end();
+       cur = chunks.begin();
+       end = chunks.end();
        done = 0;
-       total = loader.Loaded().size();
+       total = chunks.NumLoaded();
 }
 
 
@@ -36,7 +40,7 @@ void UnloadState::Handle(const SDL_Event &) {
 void UnloadState::Update(int dt) {
        for (std::size_t i = 0; i < per_update && cur != end; ++i, ++cur, ++done) {
                if (cur->ShouldUpdateSave()) {
-                       loader.SaveFile().Write(*cur);
+                       save.Write(*cur);
                }
        }
        if (cur == end) {
index 877f8fb4d7f4030382fb2b47190c25a52b562c72..ba7e8093d8a56b7eb1dfef958f224ce566720e90 100644 (file)
 namespace blank {
 
 class Chunk;
-class ChunkLoader;
+class ChunkStore;
 class Environment;
+class WorldSave;
 
 class UnloadState
 : public State {
 
 public:
-       UnloadState(Environment &, ChunkLoader &);
+       UnloadState(Environment &, ChunkStore &, const WorldSave &);
 
        void OnResume();
 
@@ -29,7 +30,8 @@ public:
 
 private:
        Environment &env;
-       ChunkLoader &loader;
+       ChunkStore &chunks;
+       const WorldSave &save;
        Progress progress;
        std::list<Chunk>::iterator cur;
        std::list<Chunk>::iterator end;
index 8c1e93dd47dcfdf1696dfb34e172f5fe5051acec..5bb01bfb2bb3fd37d5aa84d4acc2668996b6e858 100644 (file)
@@ -11,19 +11,22 @@ namespace blank {
 
 WorldState::WorldState(
        Environment &env,
+       const Generator::Config &gc,
        const Interface::Config &ic,
        const World::Config &wc,
        const WorldSave &save
 )
 : env(env)
 , block_types()
-, world(block_types, wc, save)
-, chunk_renderer(world, wc.load.load_dist)
+, world(block_types, wc)
+, interface(ic, env, world, world.AddPlayer(ic.player_name))
+, generator(gc)
+, chunk_loader(world.Chunks(), generator, save)
+, chunk_renderer(*interface.GetPlayer().chunks)
 , skeletons()
-, spawner(world, skeletons, wc.gen.seed)
-, interface(ic, env, world, *world.AddPlayer(ic.player_name))
-, preload(env, world.Loader(), chunk_renderer)
-, unload(env, world.Loader()) {
+, spawner(world, skeletons, gc.seed)
+, preload(env, chunk_loader, chunk_renderer)
+, unload(env, world.Chunks(), save) {
        TextureIndex tex_index;
        env.loader.LoadBlockTypes("default", block_types, tex_index);
        chunk_renderer.LoadTextures(env.loader, tex_index);
@@ -72,19 +75,22 @@ void WorldState::Update(int dt) {
        interface.Update(dt);
        spawner.Update(dt);
        world.Update(dt);
-       chunk_renderer.Rebase(interface.Player().ChunkCoords());
+       chunk_loader.Update(dt);
        chunk_renderer.Update(dt);
 
-       glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords());
+       Entity &player = *interface.GetPlayer().entity;
+
+       glm::mat4 trans = player.Transform(player.ChunkCoords());
        glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
        glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
-       env.audio.Position(interface.Player().Position());
-       env.audio.Velocity(interface.Player().Velocity());
+       env.audio.Position(player.Position());
+       env.audio.Velocity(player.Velocity());
        env.audio.Orientation(dir, up);
 }
 
 void WorldState::Render(Viewport &viewport) {
-       viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
+       Entity &player = *interface.GetPlayer().entity;
+       viewport.WorldPosition(player.Transform(player.ChunkCoords()));
        chunk_renderer.Render(viewport);
        world.Render(viewport);
        interface.Render(viewport);
index c0b4ce10a23829f8012d9a4c144d3d2e2a971ba4..d5eac1980d850a9ce31b98ef8988bf187bc9e9d6 100644 (file)
@@ -8,7 +8,9 @@
 #include "../model/Skeletons.hpp"
 #include "../ui/Interface.hpp"
 #include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkLoader.hpp"
 #include "../world/ChunkRenderer.hpp"
+#include "../world/Generator.hpp"
 #include "../world/World.hpp"
 
 
@@ -22,6 +24,7 @@ class WorldState
 public:
        WorldState(
                Environment &,
+               const Generator::Config &,
                const Interface::Config &,
                const World::Config &,
                const WorldSave &
@@ -40,10 +43,12 @@ private:
        Environment &env;
        BlockTypeRegistry block_types;
        World world;
+       Interface interface;
+       Generator generator;
+       ChunkLoader chunk_loader;
        ChunkRenderer chunk_renderer;
        Skeletons skeletons;
        Spawner spawner;
-       Interface interface;
 
        PreloadState preload;
        UnloadState unload;
index e169a7bb5eb1d73efd4efa997ddc4f902c119ca9..8ce7b5dd59562fa989487ae635cc266849939349 100644 (file)
@@ -220,7 +220,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                                                cerr << "missing argument to -s" << endl;
                                                                error = true;
                                                        } else {
-                                                               config.world.gen.seed = strtoul(argv[i], nullptr, 10);
+                                                               config.gen.seed = strtoul(argv[i], nullptr, 10);
                                                        }
                                                        break;
                                                case 't':
@@ -317,12 +317,14 @@ void Runtime::RunStandalone() {
        WorldSave save(config.env.GetWorldPath(config.world.name));
        if (save.Exists()) {
                save.Read(config.world);
+               save.Read(config.gen);
        } else {
                save.Write(config.world);
+               save.Write(config.gen);
        }
 
        Application app(env);
-       WorldState world_state(env, config.interface, config.world, save);
+       WorldState world_state(env, config.gen, config.interface, config.world, save);
        app.PushState(&world_state);
        Run(app);
 }
@@ -333,12 +335,14 @@ void Runtime::RunServer() {
        WorldSave save(config.env.GetWorldPath(config.world.name));
        if (save.Exists()) {
                save.Read(config.world);
+               save.Read(config.gen);
        } else {
                save.Write(config.world);
+               save.Write(config.gen);
        }
 
        HeadlessApplication app(env);
-       ServerState server_state(env, config.world, save, config.server);
+       ServerState server_state(env, config.gen, config.world, save, config.server);
        app.PushState(&server_state);
        Run(app);
 }
index 65c92d08ff33ab905421201621c575a1ce333250..efaeae425c03d869ef8df008ebad386793138b72 100644 (file)
@@ -37,8 +37,8 @@ private:
        BlockTypeRegistry block_types;
        WorldSave save;
        World world;
-       ChunkRenderer chunk_renderer;
        Interface interface;
+       ChunkRenderer chunk_renderer;
 
 };
 
index 829cc6b1eae49697439bda9a13e697aab34f4a6e..ba9fcc9bdfb3c9b9e742deb60d1c7d0dd51f46a5 100644 (file)
@@ -46,14 +46,14 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
 : master(master)
 , block_types()
 , save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetClientConf().host))
-, world(block_types, master.GetWorldConf(), save)
-, chunk_renderer(world, master.GetWorldConf().load.load_dist)
+, world(block_types, master.GetWorldConf())
 , interface(
        master.GetInterfaceConf(),
        master.GetEnv(),
        world,
-       *world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
-) {
+       world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
+)
+, chunk_renderer(*interface.GetPlayer().chunks) {
        TextureIndex tex_index;
        master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index);
        chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
@@ -99,21 +99,23 @@ void InteractiveState::Update(int dt) {
 
        interface.Update(dt);
        world.Update(dt);
-       chunk_renderer.Rebase(interface.Player().ChunkCoords());
        chunk_renderer.Update(dt);
 
-       master.GetClient().SendPlayerUpdate(interface.Player());
+       Entity &player = *interface.GetPlayer().entity;
 
-       glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords());
+       master.GetClient().SendPlayerUpdate(player);
+
+       glm::mat4 trans = player.Transform(player.ChunkCoords());
        glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
        glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
-       master.GetEnv().audio.Position(interface.Player().Position());
-       master.GetEnv().audio.Velocity(interface.Player().Velocity());
+       master.GetEnv().audio.Position(player.Position());
+       master.GetEnv().audio.Velocity(player.Velocity());
        master.GetEnv().audio.Orientation(dir, up);
 }
 
 void InteractiveState::Render(Viewport &viewport) {
-       viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
+       Entity &player = *interface.GetPlayer().entity;
+       viewport.WorldPosition(player.Transform(player.ChunkCoords()));
        chunk_renderer.Render(viewport);
        world.Render(viewport);
        interface.Render(viewport);
@@ -195,7 +197,7 @@ void MasterState::On(const Packet::Join &pack) {
        pack.ReadPlayerID(player_id);
        state.reset(new InteractiveState(*this, player_id));
 
-       pack.ReadPlayer(state->GetInterface().Player());
+       pack.ReadPlayer(*state->GetInterface().GetPlayer().entity);
 
        env.state.PopAfter(this);
        env.state.Push(state.get());
index bd041f2e89cf20aba12487b1db2352ea010ebfac..5cfb7f9d7fd7ec24bcdfc2cdccb18db18e049348 100644 (file)
@@ -17,7 +17,8 @@ namespace blank {
 
 WorldSave::WorldSave(const string &path)
 : root_path(path)
-, conf_path(path + "world.conf")
+, world_conf_path(path + "world.conf")
+, gen_conf_path(path + "gen.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]) {
@@ -26,12 +27,13 @@ WorldSave::WorldSave(const string &path)
 
 
 bool WorldSave::Exists() const noexcept {
-       return is_dir(root_path) && is_file(conf_path);
+       return is_dir(root_path) && is_file(world_conf_path);
 }
 
 
+// TODO: better implementation of config files
 void WorldSave::Read(World::Config &conf) const {
-       ifstream in(conf_path);
+       ifstream in(world_conf_path);
        if (!in) {
                throw runtime_error("failed to open world config");
        }
@@ -58,11 +60,11 @@ void WorldSave::Read(World::Config &conf) const {
                string name(line, name_begin, name_end - name_begin + 1);
                string value(line, value_begin, value_end - value_begin + 1);
 
-               if (name == "seed") {
-                       conf.gen.seed = stoul(value);
-               } else {
+       //      if (name == "seed") {
+       //              conf.gen.seed = stoul(value);
+       //      } else {
                        throw runtime_error("unknown world option: " + name);
-               }
+       //      }
        }
        if (in.bad()) {
                throw runtime_error("IO error reading world config");
@@ -74,8 +76,8 @@ void WorldSave::Write(const World::Config &conf) const {
                throw runtime_error("failed to create world save directory");
        }
 
-       ofstream out(conf_path);
-       out << "seed = " << conf.gen.seed << endl;
+       ofstream out(world_conf_path);
+       //out << "seed = " << conf.gen.seed << endl;
        out.close();
 
        if (!out) {
@@ -84,6 +86,60 @@ void WorldSave::Write(const World::Config &conf) const {
 }
 
 
+void WorldSave::Read(Generator::Config &conf) const {
+       ifstream in(gen_conf_path);
+       if (!in) {
+               throw runtime_error("failed to open generator config");
+       }
+
+       constexpr char spaces[] = "\n\r\t ";
+
+       string line;
+       while (getline(in, line)) {
+               if (line.empty() || line[0] == '#') continue;
+               auto equals_pos = line.find_first_of('=');
+
+               auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces));
+               auto name_end = equals_pos - 1;
+               while (name_end > name_begin && isspace(line[name_end])) {
+                       --name_end;
+               }
+
+               auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces));
+               auto value_end = line.length() - 1;
+               while (value_end > value_begin && isspace(line[value_end])) {
+                       --value_end;
+               }
+
+               string name(line, name_begin, name_end - name_begin + 1);
+               string value(line, value_begin, value_end - value_begin + 1);
+
+               if (name == "seed") {
+                       conf.seed = stoul(value);
+               } else {
+                       throw runtime_error("unknown generator option: " + name);
+               }
+       }
+       if (in.bad()) {
+               throw runtime_error("IO error reading world config");
+       }
+}
+
+void WorldSave::Write(const Generator::Config &conf) const {
+       if (!make_dirs(root_path)) {
+               throw runtime_error("failed to create world save directory");
+       }
+
+       ofstream out(gen_conf_path);
+       out << "seed = " << conf.seed << endl;
+       out.close();
+
+       if (!out) {
+               throw runtime_error("failed to write generator config");
+       }
+}
+
+
 bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
        return is_file(ChunkPath(pos));
 }
index 4095016a10c442df04ac19fcc078e7a6552ffee8..1796257fe748d62ecfbf88f2bb0e94c5e13c3abf 100644 (file)
@@ -2,6 +2,7 @@
 #define BLANK_IO_WORLDSAVE_HPP_
 
 #include "../world/Chunk.hpp"
+#include "../world/Generator.hpp"
 #include "../world/World.hpp"
 
 #include <memory>
@@ -20,6 +21,8 @@ public:
        bool Exists() const noexcept;
        void Read(World::Config &) const;
        void Write(const World::Config &) const;
+       void Read(Generator::Config &) const;
+       void Write(const Generator::Config &) const;
 
        // single chunk
        bool Exists(const Chunk::Pos &) const noexcept;
@@ -30,7 +33,8 @@ public:
 
 private:
        std::string root_path;
-       std::string conf_path;
+       std::string world_conf_path;
+       std::string gen_conf_path;
        std::string chunk_path;
        std::size_t chunk_bufsiz;
        std::unique_ptr<char[]> chunk_buf;
index d56e4093545526e980bde0ee95bf6e916025effc..9fe282f5138b4c7a115819834c3a2b0a44373c16 100644 (file)
@@ -297,7 +297,7 @@ void ClientConnection::On(const Packet::Login &pack) {
        string name;
        pack.ReadPlayerName(name);
 
-       Entity *new_player = server.GetWorld().AddPlayer(name);
+       Entity *new_player = server.GetWorld().AddPlayer(name).entity;
 
        if (new_player) {
                // success!
index 7c9a91e33324520c458ff7cf773ea90d51360f56..72db5d13eb94fbea290c434e8eae260234925cb3 100644 (file)
@@ -11,6 +11,7 @@
 #include "../model/OutlineModel.hpp"
 #include "../world/Block.hpp"
 #include "../world/EntityCollision.hpp"
+#include "../world/Player.hpp"
 #include "../world/WorldCollision.hpp"
 
 #include <string>
@@ -41,10 +42,9 @@ public:
                bool visual_disabled = false;
        };
 
-       Interface(const Config &, Environment &, World &, Entity &);
+       Interface(const Config &, Environment &, World &, const Player &);
 
-       Entity &Player() noexcept { return ctrl.Controlled(); }
-       const Entity &Player() const noexcept { return ctrl.Controlled(); }
+       const Player &GetPlayer() noexcept { return player; }
 
        void HandlePress(const SDL_KeyboardEvent &);
        void HandleRelease(const SDL_KeyboardEvent &);
@@ -91,6 +91,7 @@ private:
 private:
        Environment &env;
        World &world;
+       Player player;
        FPSController ctrl;
        HUD hud;
 
index 2ec4d883d3e6f963dc047e85694a5bfb9bcec00d..39942987f6375a243fd0412572cffbc8201bf3fe 100644 (file)
@@ -103,11 +103,11 @@ Interface::Interface(
        const Config &config,
        Environment &env,
        World &world,
-       Entity &player)
+       const Player &player)
 : env(env)
 , world(world)
-// let's assume this succeeds and hope for the best for now
-, ctrl(player)
+, player(player)
+, ctrl(*player.entity)
 , hud(world.BlockTypes(), env.assets.small_ui_font)
 , aim{{ 0, 0, 0 }, { 0, 0, -1 }}
 , aim_world()
@@ -526,7 +526,7 @@ void Interface::UpdateOutline() {
        outl_buf.Clear();
        aim_world.GetType().FillOutlineModel(outl_buf);
        outline.Update(outl_buf);
-       outline_transform = aim_world.GetChunk().Transform(Player().ChunkCoords());
+       outline_transform = aim_world.GetChunk().Transform(player.entity->ChunkCoords());
        outline_transform *= aim_world.BlockTransform();
        outline_transform *= glm::scale(glm::vec3(1.005f));
 }
index 0440f9c7bb8a97aa10a639cefed469b304df0a7b..23ed869aa7ed17b08d91dd5bf57eff9b163cbf15 100644 (file)
@@ -99,11 +99,10 @@ public:
        bool IsSurface(const Block::Pos &pos) const noexcept { return IsSurface(Pos(pos)); }
        bool IsSurface(const Pos &pos) const noexcept;
 
-       void SetNeighbor(Chunk &) noexcept;
+       void SetNeighbor(Block::Face, Chunk &) noexcept;
        bool HasNeighbor(Block::Face f) const noexcept { return neighbor[f]; }
        Chunk &GetNeighbor(Block::Face f) noexcept { return *neighbor[f]; }
        const Chunk &GetNeighbor(Block::Face f) const noexcept { return *neighbor[f]; }
-       void ClearNeighbors() noexcept;
        void Unlink() noexcept;
 
        // check which faces of a block at given index are obstructed (and therefore invisible)
@@ -159,6 +158,10 @@ public:
        const void *BlockData() const noexcept { return &blocks[0]; }
        static constexpr std::size_t BlockSize() noexcept { return sizeof(blocks) + sizeof(light); }
 
+       void Ref() noexcept { ++ref_count; }
+       void UnRef() noexcept { --ref_count; }
+       bool Referenced() const noexcept { return ref_count > 0; }
+
        void Invalidate() noexcept { dirty_model = dirty_save = true; }
        void InvalidateModel() noexcept { dirty_model = true; }
        void ClearModel() noexcept { dirty_model = false; }
@@ -174,6 +177,7 @@ private:
        Block blocks[size];
        unsigned char light[size];
        Pos position;
+       int ref_count;
        bool dirty_model;
        bool dirty_save;
 
diff --git a/src/world/ChunkIndex.hpp b/src/world/ChunkIndex.hpp
new file mode 100644 (file)
index 0000000..6749737
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef BLANK_WORLD_CHUNKINDEX_HPP_
+#define BLANK_WORLD_CHUNKINDEX_HPP_
+
+#include "Chunk.hpp"
+
+#include <vector>
+
+
+namespace blank {
+
+class ChunkStore;
+
+class ChunkIndex {
+
+public:
+       ChunkIndex(ChunkStore &, const Chunk::Pos &base, int extent);
+       ~ChunkIndex();
+
+       ChunkIndex(const ChunkIndex &) = delete;
+       ChunkIndex &operator =(const ChunkIndex &) = delete;
+
+public:
+       bool InRange(const Chunk::Pos &) const noexcept;
+       int IndexOf(const Chunk::Pos &) const noexcept;
+       Chunk::Pos PositionOf(int) const noexcept;
+       /// returns nullptr if given position is out of range or the chunk
+       /// is not loaded, so also works as a "has" function
+       Chunk *Get(const Chunk::Pos &) noexcept;
+       const Chunk *Get(const Chunk::Pos &) const noexcept;
+       Chunk *operator [](int i) noexcept { return chunks[i]; }
+       const Chunk *operator [](int i) const noexcept { return chunks[i]; }
+
+       void Register(Chunk &) noexcept;
+
+       int TotalChunks() const noexcept { return total_length; }
+       int IndexedChunks() const noexcept { return total_indexed; }
+       int MissingChunks() const noexcept { return total_length - total_indexed; }
+
+       Chunk::Pos NextMissing() noexcept;
+
+       const Chunk::Pos &Base() const noexcept { return base; }
+       void Rebase(const Chunk::Pos &);
+
+private:
+       int GetCol(int) const noexcept;
+
+       void Shift(Block::Face);
+
+       void Clear() noexcept;
+       void Scan() noexcept;
+
+       void Set(int index, Chunk &) noexcept;
+       void Unset(int index) noexcept;
+
+private:
+       ChunkStore &store;
+       Chunk::Pos base;
+       int extent;
+       int side_length;
+       int total_length;
+       int total_indexed;
+       int last_missing;
+       glm::ivec3 stride;
+       std::vector<Chunk *> chunks;
+
+};
+
+}
+
+#endif
index 7cc8dfca917cce00ad8234d9f4339126173f2c7f..0198651f2ab307e137d6b7e220c291461b51dd54 100644 (file)
@@ -1,83 +1,40 @@
 #ifndef BLANK_WORLD_CHUNKLOADER_HPP_
 #define BLANK_WORLD_CHUNKLOADER_HPP_
 
-#include "Chunk.hpp"
-#include "../app/IntervalTimer.hpp"
-
 #include <list>
 
 
 namespace blank {
 
-class BlockTypeRegistry;
+class ChunkStore;
 class Generator;
 class WorldSave;
 
 class ChunkLoader {
 
 public:
-       struct Config {
-               int load_dist = 6;
-               int unload_dist = 8;
-               int gen_limit = 16;
-       };
-
        ChunkLoader(
-               const Config &,
-               const BlockTypeRegistry &,
+               ChunkStore &,
                const Generator &,
                const WorldSave &
        ) noexcept;
 
-       void Queue(const Chunk::Pos &from, const Chunk::Pos &to);
-       void QueueSurrounding(const Chunk::Pos &);
-
-       std::list<Chunk> &Loaded() noexcept { return loaded; }
        const WorldSave &SaveFile() const noexcept { return save; }
 
-       Chunk *Loaded(const Chunk::Pos &) noexcept;
-       bool Queued(const Chunk::Pos &) noexcept;
-       bool Known(const Chunk::Pos &) noexcept;
-       Chunk &ForceLoad(const Chunk::Pos &);
-
-       bool OutOfRange(const Chunk &c) const noexcept { return OutOfRange(c.Position()); }
-       bool OutOfRange(const Chunk::Pos &) const noexcept;
-
-       void Rebase(const Chunk::Pos &);
        void Update(int dt);
 
-       std::size_t ToLoad() const noexcept { return to_load.size(); }
+       int ToLoad() const noexcept;
+
        // returns true if the chunk was generated
+       // (as opposed to loaded from file)
        bool LoadOne();
        void LoadN(std::size_t n);
 
 private:
-       Chunk &Load(const Chunk::Pos &pos);
-       // link given chunk to all loaded neighbors
-       void Insert(Chunk &) noexcept;
-       // remove a loaded chunk
-       // this unlinks it from its neighbors as well as moves it to the free list
-       // given iterator must point to a chunk from the loaded list
-       // returns an iterator to the chunk following the removed one
-       // in the loaded list (end for the last one)
-       std::list<Chunk>::iterator Remove(std::list<Chunk>::iterator) noexcept;
-
-private:
-       Chunk::Pos base;
-
-       const BlockTypeRegistry &reg;
+       ChunkStore &store;
        const Generator &gen;
        const WorldSave &save;
 
-       std::list<Chunk> loaded;
-       std::list<Chunk::Pos> to_load;
-       std::list<Chunk> to_free;
-
-       IntervalTimer gen_timer;
-
-       int load_dist;
-       int unload_dist;
-
 };
 
 }
index fe4f6a66d0062a6a81fac5ed5fcee89a291d62f5..d45d0fd415f3f57f05ad209299fd0f0a7d256d4e 100644 (file)
 namespace blank {
 
 class AssetLoader;
+class BlockModel;
+class ChunkIndex;
 class TextureIndex;
 class Viewport;
-class World;
 
 class ChunkRenderer {
 
 public:
-       /// render_distance in chunks, excluding the base chunk which is always rendered
-       ChunkRenderer(World &, int render_distance);
+       explicit ChunkRenderer(ChunkIndex &);
+       ~ChunkRenderer();
 
        void LoadTextures(const AssetLoader &, const TextureIndex &);
        void FogDensity(float d) noexcept { fog_density = d; }
 
-       bool InRange(const Chunk::Pos &) const noexcept;
-       int IndexOf(const Chunk::Pos &) const noexcept;
+       int MissingChunks() const noexcept;
 
-       int TotalChunks() const noexcept { return total_length; }
-       int IndexedChunks() const noexcept { return total_indexed; }
-       int MissingChunks() const noexcept { return total_length - total_indexed; }
-
-       void Rebase(const Chunk::Pos &);
-       void Rescan();
-       void Scan();
        void Update(int dt);
 
        void Render(Viewport &);
 
 private:
-       int GetCol(int) const noexcept;
-
-       void Shift(Block::Face);
-
-private:
-       World &world;
-       ArrayTexture block_tex;
-
-       int render_dist;
-       int side_length;
-       int total_length;
-       int total_indexed;
-       glm::ivec3 stride;
+       ChunkIndex &index;
        std::vector<BlockModel> models;
-       std::vector<Chunk *> chunks;
 
-       Chunk::Pos base;
+       ArrayTexture block_tex;
 
        float fog_density;
 
diff --git a/src/world/ChunkStore.hpp b/src/world/ChunkStore.hpp
new file mode 100644 (file)
index 0000000..68434bb
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef BLANK_WORLD_CHUNKSTORE_HPP_
+#define BLANK_WORLD_CHUNKSTORE_HPP_
+
+#include "Chunk.hpp"
+
+#include <list>
+
+
+namespace blank {
+
+class ChunkIndex;
+
+class ChunkStore {
+
+public:
+       ChunkStore(const BlockTypeRegistry &);
+       ~ChunkStore();
+
+       ChunkStore(const ChunkStore &) = delete;
+       ChunkStore &operator =(const ChunkStore &) = delete;
+
+public:
+       ChunkIndex &MakeIndex(const Chunk::Pos &base, int extent);
+       void UnregisterIndex(ChunkIndex &);
+
+       /// returns nullptr if given position is not loaded
+       Chunk *Get(const Chunk::Pos &);
+       /// returns nullptr if given position is not indexed
+       Chunk *Allocate(const Chunk::Pos &);
+
+       std::list<Chunk>::iterator begin() noexcept { return loaded.begin(); }
+       std::list<Chunk>::iterator end() noexcept { return loaded.end(); }
+
+       std::size_t NumLoaded() const noexcept { return loaded.size(); }
+
+       /// returns true if one of the indices is incomplete
+       bool HasMissing() const noexcept;
+       /// get the total number of missing chunks
+       /// this is an estimate and represents the upper bound since
+       /// chunks may be counted more than once if indices overlap
+       int EstimateMissing() const noexcept;
+
+       /// get coordinates of a missing chunk
+       /// this will return garbage if none are actually missing
+       Chunk::Pos NextMissing() noexcept;
+
+       void Clean();
+
+private:
+       const BlockTypeRegistry &types;
+
+       std::list<Chunk> loaded;
+       std::list<Chunk> free;
+
+       std::list<ChunkIndex> indices;
+
+};
+
+}
+
+#endif
index c8621e75e42bc907d8656b1be33370a0b63ae2c2..c37c18b6c2aa4a7f337286d6810bfd154ea6e002 100644 (file)
@@ -13,9 +13,10 @@ Generator::Generator(const Config &config) noexcept
 , typeNoise(config.seed)
 , stretch(1.0f/config.stretch)
 , solid_threshold(config.solid_threshold)
+// TODO: stable dynamic generator configuration
 , space(0)
-, light(0)
-, solids() {
+, light(13)
+, solids({ 1, 4, 7, 10 }) {
 
 }
 
@@ -47,7 +48,6 @@ void Generator::operator ()(Chunk &chunk) const noexcept {
                        }
                }
        }
-       //chunk.CheckUpdate();
 }
 
 }
diff --git a/src/world/Player.hpp b/src/world/Player.hpp
new file mode 100644 (file)
index 0000000..68aa352
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef BLANK_WORLD_PLAYER_HPP_
+#define BLANK_WORLD_PLAYER_HPP_
+
+namespace blank {
+
+class ChunkIndex;
+class Entity;
+
+struct Player {
+
+       Entity *entity;
+       ChunkIndex *chunks;
+
+       Player(Entity *e, ChunkIndex *i)
+       : entity(e), chunks(i) { }
+
+};
+
+}
+
+#endif
index 7b9095401dcecc62444a3e7cf0de53761519c940..8b43657d3b58b1520e40c25019762883e6f7e5fb 100644 (file)
@@ -1,5 +1,6 @@
 #include "World.hpp"
 
+#include "ChunkIndex.hpp"
 #include "EntityCollision.hpp"
 #include "WorldCollision.hpp"
 #include "../app/Assets.hpp"
 
 namespace blank {
 
-World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save)
+World::World(const BlockTypeRegistry &types, const Config &config)
 : config(config)
 , block_type(types)
-, generate(config.gen)
-, chunks(config.load, types, generate, save)
+, chunks(types)
+// TODO: set spawn base and extent from config
+, spawn_index(chunks.MakeIndex(Chunk::Pos(0, 0, 0), 3))
 , players()
 , entities()
 , light_direction(config.light_direction)
 , fog_density(config.fog_density) {
-       generate.Space(0);
-       generate.Light(13);
-       generate.Solids({ 1, 4, 7, 10 });
+
+}
+
+World::~World() {
+       chunks.UnregisterIndex(spawn_index);
 }
 
 
-Entity *World::AddPlayer(const std::string &name) {
-       for (Entity *e : players) {
-               if (e->Name() == name) {
-                       return nullptr;
+Player World::AddPlayer(const std::string &name) {
+       for (Player &p : players) {
+               if (p.entity->Name() == name) {
+                       return { nullptr, nullptr };
                }
        }
-       Entity &player = AddEntity();
-       player.Name(name);
+       Entity &entity = AddEntity();
+       entity.Name(name);
        // TODO: load from save file here
-       player.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
-       player.WorldCollidable(true);
-       player.Position(config.spawn);
-       players.push_back(&player);
-       chunks.QueueSurrounding(player.ChunkCoords());
-       return &player;
+       entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
+       entity.WorldCollidable(true);
+       entity.Position(config.spawn);
+       ChunkIndex *index = &chunks.MakeIndex(entity.ChunkCoords(), 6);
+       players.emplace_back(&entity, index);
+       return players.back();
 }
 
-Entity *World::AddPlayer(const std::string &name, std::uint32_t id) {
-       for (Entity *e : players) {
-               if (e->Name() == name) {
-                       return nullptr;
+Player World::AddPlayer(const std::string &name, std::uint32_t id) {
+       for (Player &p : players) {
+               if (p.entity->Name() == name) {
+                       return { nullptr, nullptr };
                }
        }
-       Entity *player = AddEntity(id);
-       if (!player) {
-               return nullptr;
+       Entity *entity = AddEntity(id);
+       if (!entity) {
+               return { nullptr, nullptr };
        }
-       player->Name(name);
+       entity->Name(name);
        // TODO: load from save file here
-       player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
-       player->WorldCollidable(true);
-       player->Position(config.spawn);
-       players.push_back(player);
-       chunks.QueueSurrounding(player->ChunkCoords());
-       return player;
+       entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
+       entity->WorldCollidable(true);
+       entity->Position(config.spawn);
+       ChunkIndex *index = &chunks.MakeIndex(entity->ChunkCoords(), 6);
+       players.emplace_back(entity, index);
+       return players.back();
 }
 
 Entity &World::AddEntity() {
@@ -134,7 +138,7 @@ bool World::Intersection(
 ) {
        candidates.clear();
 
-       for (Chunk &cur_chunk : chunks.Loaded()) {
+       for (Chunk &cur_chunk : chunks) {
                float cur_dist;
                if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) {
                        candidates.push_back({ &cur_chunk, cur_dist });
@@ -194,7 +198,7 @@ bool World::Intersection(const Entity &e, std::vector<WorldCollision> &col) {
        Chunk::Pos reference = e.ChunkCoords();
        glm::mat4 M = e.Transform(reference);
        bool any = false;
-       for (Chunk &cur_chunk : chunks.Loaded()) {
+       for (Chunk &cur_chunk : chunks) {
                if (manhattan_radius(cur_chunk.Position() - e.ChunkCoords()) > 1) {
                        // chunk is not one of the 3x3x3 surrounding the entity
                        // since there's no entity which can extent over 16 blocks, they can be skipped
@@ -225,6 +229,9 @@ void World::Update(int dt) {
                        Resolve(entity, col);
                }
        }
+       for (Player &player : players) {
+               player.chunks->Rebase(player.entity->ChunkCoords());
+       }
        for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
                if (iter->CanRemove()) {
                        iter = RemoveEntity(iter);
@@ -232,9 +239,6 @@ void World::Update(int dt) {
                        ++iter;
                }
        }
-       // TODO: make flexible
-       chunks.Rebase(players[0]->ChunkCoords());
-       chunks.Update(dt);
 }
 
 void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
@@ -271,9 +275,13 @@ void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
 
 World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
        // check for player
-       auto player = std::find(players.begin(), players.end(), &*eh);
-       if (player != players.end()) {
-               players.erase(player);
+       for (auto player = players.begin(), end = players.end(); player != end;) {
+               if (player->entity == &*eh) {
+                       chunks.UnregisterIndex(*player->chunks);
+                       player = players.erase(player);
+               } else {
+                       ++player;
+               }
        }
        return entities.erase(eh);
 }
@@ -285,7 +293,7 @@ void World::Render(Viewport &viewport) {
        entity_prog.SetFogDensity(fog_density);
 
        for (Entity &entity : entities) {
-               entity.Render(entity.ChunkTransform(players[0]->ChunkCoords()), entity_prog);
+               entity.Render(entity.ChunkTransform(players[0].entity->ChunkCoords()), entity_prog);
        }
 }
 
index c605d7779f61693c763dbe044345c4e09275b889..8d822501bd07735d8c2448f9ec6250989d16e9aa 100644 (file)
@@ -1,9 +1,10 @@
 #ifndef BLANK_WORLD_WORLD_HPP_
 #define BLANK_WORLD_WORLD_HPP_
 
-#include "ChunkLoader.hpp"
+#include "ChunkStore.hpp"
 #include "Entity.hpp"
 #include "Generator.hpp"
+#include "Player.hpp"
 
 #include <cstdint>
 #include <list>
@@ -33,12 +34,10 @@ public:
                // I chose 0.011 because it yields 91 and 124 for those, so
                // slightly less than 6 and 8 chunks
                float fog_density = 0.011f;
-
-               Generator::Config gen = Generator::Config();
-               ChunkLoader::Config load = ChunkLoader::Config();
        };
 
-       World(const BlockTypeRegistry &, const Config &, const WorldSave &);
+       World(const BlockTypeRegistry &, const Config &);
+       ~World();
 
        const std::string &Name() const noexcept { return config.name; }
 
@@ -66,21 +65,21 @@ public:
        void Resolve(Entity &e, std::vector<WorldCollision> &);
 
        const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
-       ChunkLoader &Loader() noexcept { return chunks; }
+       ChunkStore &Chunks() noexcept { return chunks; }
 
        /// add player with given name
-       /// returns nullptr if the name is already taken
-       Entity *AddPlayer(const std::string &name);
+       /// returns nullptr in entity if the name is already taken
+       Player AddPlayer(const std::string &name);
        /// add player with given name and ID
-       /// returns nullptr if the name or ID is already taken
-       Entity *AddPlayer(const std::string &name, std::uint32_t id);
+       /// returns nullptr in entity if the name or ID is already taken
+       Player AddPlayer(const std::string &name, std::uint32_t id);
        /// add an entity with an autogenerated ID
        Entity &AddEntity();
        /// add entity with given ID
        /// returns nullptr if the ID is already taken
        Entity *AddEntity(std::uint32_t id);
 
-       const std::vector<Entity *> &Players() const noexcept { return players; }
+       const std::vector<Player> &Players() const noexcept { return players; }
        std::list<Entity> &Entities() noexcept { return entities; }
        const std::list<Entity> &Entities() const noexcept { return entities; }
 
@@ -97,10 +96,10 @@ private:
 
        const BlockTypeRegistry &block_type;
 
-       Generator generate;
-       ChunkLoader chunks;
+       ChunkStore chunks;
+       ChunkIndex &spawn_index;
 
-       std::vector<Entity *> players;
+       std::vector<Player> players;
        std::list<Entity> entities;
 
        glm::vec3 light_direction;
index 11c9d36bdaedee1682725db27c31d3341b89b305..1d1a118a80030131b94e31534d2b5b7115591b80 100644 (file)
@@ -1,10 +1,17 @@
 #include "BlockLookup.hpp"
 #include "Chunk.hpp"
+#include "ChunkIndex.hpp"
 #include "ChunkLoader.hpp"
+#include "ChunkRenderer.hpp"
+#include "ChunkStore.hpp"
 
 #include "Generator.hpp"
 #include "WorldCollision.hpp"
+#include "../app/Assets.hpp"
+#include "../graphics/BlockLighting.hpp"
+#include "../graphics/Viewport.hpp"
 #include "../io/WorldSave.hpp"
+#include "../model/BlockModel.hpp"
 
 #include <algorithm>
 #include <limits>
@@ -26,6 +33,7 @@ Chunk::Chunk(const BlockTypeRegistry &types) noexcept
 , blocks{}
 , light{0}
 , position(0, 0, 0)
+, ref_count(0)
 , dirty_model(false)
 , dirty_save(false) {
 
@@ -212,11 +220,17 @@ void edge_light(
 
 }
 
-void Chunk::SetNeighbor(Chunk &other) noexcept {
-       if (other.position == position + Pos(-1, 0, 0)) {
-               if (neighbor[Block::FACE_LEFT] != &other) {
-                       neighbor[Block::FACE_LEFT] = &other;
-                       other.neighbor[Block::FACE_RIGHT] = this;
+void Chunk::SetNeighbor(Block::Face face, Chunk &other) noexcept {
+       neighbor[face] = &other;
+       other.neighbor[Block::Opposite(face)] = this;
+
+       switch (face) {
+
+               default:
+                       // error
+                       break;
+
+               case Block::FACE_LEFT:
                        for (int z = 0; z < depth; ++z) {
                                for (int y = 0; y < height; ++y) {
                                        Pos my_pos(0, y, z);
@@ -225,12 +239,9 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                        edge_light(other, other_pos, *this, my_pos);
                                }
                        }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(1, 0, 0)) {
-               if (neighbor[Block::FACE_RIGHT] != &other) {
-                       neighbor[Block::FACE_RIGHT] = &other;
-                       other.neighbor[Block::FACE_LEFT] = this;
+                       break;
+
+               case Block::FACE_RIGHT:
                        for (int z = 0; z < depth; ++z) {
                                for (int y = 0; y < height; ++y) {
                                        Pos my_pos(width - 1, y, z);
@@ -239,12 +250,9 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                        edge_light(other, other_pos, *this, my_pos);
                                }
                        }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, -1, 0)) {
-               if (neighbor[Block::FACE_DOWN] != &other) {
-                       neighbor[Block::FACE_DOWN] = &other;
-                       other.neighbor[Block::FACE_UP] = this;
+                       break;
+
+               case Block::FACE_DOWN:
                        for (int z = 0; z < depth; ++z) {
                                for (int x = 0; x < width; ++x) {
                                        Pos my_pos(x, 0, z);
@@ -253,12 +261,9 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                        edge_light(other, other_pos, *this, my_pos);
                                }
                        }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, 1, 0)) {
-               if (neighbor[Block::FACE_UP] != &other) {
-                       neighbor[Block::FACE_UP] = &other;
-                       other.neighbor[Block::FACE_DOWN] = this;
+                       break;
+
+               case Block::FACE_UP:
                        for (int z = 0; z < depth; ++z) {
                                for (int x = 0; x < width; ++x) {
                                        Pos my_pos(x, height - 1, z);
@@ -267,12 +272,9 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                        edge_light(other, other_pos, *this, my_pos);
                                }
                        }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, 0, -1)) {
-               if (neighbor[Block::FACE_BACK] != &other) {
-                       neighbor[Block::FACE_BACK] = &other;
-                       other.neighbor[Block::FACE_FRONT] = this;
+                       break;
+
+               case Block::FACE_BACK:
                        for (int y = 0; y < height; ++y) {
                                for (int x = 0; x < width; ++x) {
                                        Pos my_pos(x, y, 0);
@@ -281,12 +283,9 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                        edge_light(other, other_pos, *this, my_pos);
                                }
                        }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, 0, 1)) {
-               if (neighbor[Block::FACE_FRONT] != &other) {
-                       neighbor[Block::FACE_FRONT] = &other;
-                       other.neighbor[Block::FACE_BACK] = this;
+                       break;
+
+               case Block::FACE_FRONT:
                        for (int y = 0; y < height; ++y) {
                                for (int x = 0; x < width; ++x) {
                                        Pos my_pos(x, y, depth - 1);
@@ -295,12 +294,13 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                        edge_light(other, other_pos, *this, my_pos);
                                }
                        }
-                       work_light();
-               }
+                       break;
+
        }
+       work_light();
 }
 
-void Chunk::ClearNeighbors() noexcept {
+void Chunk::Unlink() noexcept {
        for (int face = 0; face < Block::FACE_COUNT; ++face) {
                if (neighbor[face]) {
                        neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
@@ -627,294 +627,416 @@ BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexce
 
 
 ChunkLoader::ChunkLoader(
-       const Config &config,
-       const BlockTypeRegistry &reg,
+       ChunkStore &store,
        const Generator &gen,
        const WorldSave &save
 ) noexcept
-: base(0, 0, 0)
-, reg(reg)
+: store(store)
 , gen(gen)
-, save(save)
-, loaded()
-, to_load()
-, to_free()
-, gen_timer(config.gen_limit)
-, load_dist(config.load_dist)
-, unload_dist(config.unload_dist) {
-       gen_timer.Start();
+, save(save) {
+
 }
 
-namespace {
+void ChunkLoader::Update(int dt) {
+       // check if there's chunks waiting to be loaded
+       // load until one of load or generation limits was hit
+       constexpr int max_load = 10;
+       constexpr int max_gen = 1;
+       int loaded = 0;
+       int generated = 0;
+       while (loaded < max_load && generated < max_gen && store.HasMissing()) {
+               if (LoadOne()) {
+                       ++generated;
+               } else {
+                       ++loaded;
+               }
+       }
+
+       // store a few chunks as well
+       constexpr int max_save = 10;
+       int saved = 0;
+       for (Chunk &chunk : store) {
+               if (chunk.ShouldUpdateSave()) {
+                       save.Write(chunk);
+                       ++saved;
+                       if (saved >= max_save) {
+                               break;
+                       }
+               }
+       }
+}
 
-struct ChunkLess {
+int ChunkLoader::ToLoad() const noexcept {
+       return store.EstimateMissing();
+}
 
-       explicit ChunkLess(const Chunk::Pos &base) noexcept
-       : base(base) { }
+bool ChunkLoader::LoadOne() {
+       if (!store.HasMissing()) return false;
 
-       bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept {
-               Chunk::Pos da(base - a);
-               Chunk::Pos db(base - b);
-               return
-                       da.x * da.x + da.y * da.y + da.z * da.z <
-                       db.x * db.x + db.y * db.y + db.z * db.z;
+       Chunk::Pos pos = store.NextMissing();
+       Chunk *chunk = store.Allocate(pos);
+       if (!chunk) {
+               // chunk store corrupted?
+               return false;
        }
 
-       Chunk::Pos base;
+       if (save.Exists(pos)) {
+               save.Read(*chunk);
+               return false;
+       } else {
+               gen(*chunk);
+               return true;
+       }
+}
+
+void ChunkLoader::LoadN(std::size_t n) {
+       std::size_t end = std::min(n, std::size_t(ToLoad()));
+       for (std::size_t i = 0; i < end && store.HasMissing(); ++i) {
+               LoadOne();
+       }
+}
 
-};
+
+ChunkRenderer::ChunkRenderer(ChunkIndex &index)
+: index(index)
+, models(index.TotalChunks())
+, block_tex()
+, fog_density(0.0f) {
 
 }
 
-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) {
-                               Chunk::Pos pos(x, y, z);
-                               if (Known(pos)) {
-                                       continue;
-                               } else if (pos == base) {
-                                       Load(pos);
-
-                               //      light testing
-                               //      for (int i = 0; i < 16; ++i) {
-                               //              for (int j = 0; j < 16; ++j) {
-                               //                      loaded.back().SetBlock(Chunk::Pos{  i, j,  0 }, Block(1));
-                               //                      loaded.back().SetBlock(Chunk::Pos{  i, j, 15 }, Block(1));
-                               //                      loaded.back().SetBlock(Chunk::Pos{  0, j,  i }, Block(1));
-                               //                      loaded.back().SetBlock(Chunk::Pos{ 15, j,  i }, Block(1));
-                               //              }
-                               //      }
-                               //      loaded.back().SetBlock(Chunk::Pos{  1,  0,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14,  0,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  1,  0, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14,  0, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  1, 15,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14, 15,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  1, 15, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  7,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  7,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  8,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  8,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  7, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  7, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  8, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  8, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  8 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  8 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  8 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  8 }, Block(13));
-                               //      loaded.back().Invalidate();
-                               //      loaded.back().CheckUpdate();
-
-                               //      orientation testing
-                               //      for (int i = 0; i < Block::FACE_COUNT; ++i) {
-                               //              for (int j = 0; j < Block::TURN_COUNT; ++j) {
-                               //                      loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
-                               //              }
-                               //      }
-                               //      loaded.back().Invalidate();
-                               //      loaded.back().CheckUpdate();
-                               } else {
-                                       to_load.emplace_back(pos);
-                               }
+ChunkRenderer::~ChunkRenderer() {
+
+}
+
+int ChunkRenderer::MissingChunks() const noexcept {
+       return index.MissingChunks();
+}
+
+void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) {
+       block_tex.Bind();
+       loader.LoadTextures(tex_index, block_tex);
+       block_tex.FilterNearest();
+}
+
+void ChunkRenderer::Update(int dt) {
+       for (int i = 0, updates = 0; updates < dt && i < index.TotalChunks(); ++i) {
+               if (index[i] && index[i]->ShouldUpdateModel()) {
+                       index[i]->Update(models[i]);
+                       ++updates;
+               }
+       }
+}
+
+void ChunkRenderer::Render(Viewport &viewport) {
+       BlockLighting &chunk_prog = viewport.ChunkProgram();
+       chunk_prog.SetTexture(block_tex);
+       chunk_prog.SetFogDensity(fog_density);
+
+       for (int i = 0; i < index.TotalChunks(); ++i) {
+               if (!index[i]) continue;
+               glm::mat4 m(index[i]->Transform(index.Base()));
+               glm::mat4 mvp(chunk_prog.GetVP() * m);
+               if (!CullTest(Chunk::Bounds(), mvp)) {
+                       if (index[i]->ShouldUpdateModel()) {
+                               index[i]->Update(models[i]);
                        }
+                       chunk_prog.SetM(m);
+                       models[i].Draw();
                }
        }
-       to_load.sort(ChunkLess(base));
 }
 
-Chunk &ChunkLoader::Load(const Chunk::Pos &pos) {
-       loaded.emplace_back(reg);
-       Chunk &chunk = loaded.back();
-       chunk.Position(pos);
-       if (save.Exists(pos)) {
-               save.Read(chunk);
+
+ChunkIndex::ChunkIndex(ChunkStore &store, const Chunk::Pos &base, int extent)
+: store(store)
+, base(base)
+, extent(extent)
+, side_length(2 * extent + 1)
+, total_length(side_length * side_length * side_length)
+, total_indexed(0)
+, last_missing(0)
+, stride(1, side_length, side_length * side_length)
+, chunks(total_length, nullptr) {
+       Scan();
+}
+
+ChunkIndex::~ChunkIndex() {
+       Clear();
+}
+
+bool ChunkIndex::InRange(const Chunk::Pos &pos) const noexcept {
+       return manhattan_radius(pos - base) <= extent;
+}
+
+int ChunkIndex::IndexOf(const Chunk::Pos &pos) const noexcept {
+       Chunk::Pos mod_pos(
+               GetCol(pos.x),
+               GetCol(pos.y),
+               GetCol(pos.z)
+       );
+       return mod_pos.x * stride.x
+               +  mod_pos.y * stride.y
+               +  mod_pos.z * stride.z;
+}
+
+Chunk::Pos ChunkIndex::PositionOf(int i) const noexcept {
+       Chunk::Pos zero_pos(
+               (i / stride.x) % side_length,
+               (i / stride.y) % side_length,
+               (i / stride.z) % side_length
+       );
+       Chunk::Pos zero_base(
+               GetCol(base.x),
+               GetCol(base.y),
+               GetCol(base.z)
+       );
+       Chunk::Pos base_relative(zero_pos - zero_base);
+       if (base_relative.x > extent) base_relative.x -= side_length;
+       else if (base_relative.x < -extent) base_relative.x += side_length;
+       if (base_relative.y > extent) base_relative.y -= side_length;
+       else if (base_relative.y < -extent) base_relative.y += side_length;
+       if (base_relative.z > extent) base_relative.z -= side_length;
+       else if (base_relative.z < -extent) base_relative.z += side_length;
+       return base + base_relative;
+}
+
+Chunk *ChunkIndex::Get(const Chunk::Pos &pos) noexcept {
+       if (InRange(pos)) {
+               return chunks[IndexOf(pos)];
        } else {
-               gen(chunk);
+               return nullptr;
        }
-       Insert(chunk);
-       return chunk;
 }
 
-void ChunkLoader::Insert(Chunk &chunk) noexcept {
-       for (Chunk &other : loaded) {
-               chunk.SetNeighbor(other);
+const Chunk *ChunkIndex::Get(const Chunk::Pos &pos) const noexcept {
+       if (InRange(pos)) {
+               return chunks[IndexOf(pos)];
+       } else {
+               return nullptr;
        }
 }
 
-std::list<Chunk>::iterator ChunkLoader::Remove(std::list<Chunk>::iterator chunk) noexcept {
-       // fetch next entry while chunk's still in the list
-       std::list<Chunk>::iterator next = chunk;
-       ++next;
-       // unlink neighbors so they won't reference a dead chunk
-       chunk->ClearNeighbors();
-       // if it should be saved, do it now
-       if (chunk->ShouldUpdateSave()) {
-               save.Write(*chunk);
+void ChunkIndex::Rebase(const Chunk::Pos &new_base) {
+       if (new_base == base) return;
+
+       Chunk::Pos diff(new_base - base);
+
+       if (manhattan_radius(diff) > extent) {
+               // that's more than half, so probably not worth shifting
+               base = new_base;
+               Clear();
+               Scan();
+               store.Clean();
+               return;
+       }
+
+       while (diff.x > 0) {
+               Shift(Block::FACE_RIGHT);
+               --diff.x;
+       }
+       while (diff.x < 0) {
+               Shift(Block::FACE_LEFT);
+               ++diff.x;
+       }
+       while (diff.y > 0) {
+               Shift(Block::FACE_UP);
+               --diff.y;
        }
-       // and move it from loaded to free list
-       to_free.splice(to_free.end(), loaded, chunk);
-       return next;
+       while (diff.y < 0) {
+               Shift(Block::FACE_DOWN);
+               ++diff.y;
+       }
+       while (diff.z > 0) {
+               Shift(Block::FACE_FRONT);
+               --diff.z;
+       }
+       while (diff.z < 0) {
+               Shift(Block::FACE_BACK);
+               ++diff.z;
+       }
+       store.Clean();
+}
+
+int ChunkIndex::GetCol(int c) const noexcept {
+       c %= side_length;
+       if (c < 0) c += side_length;
+       return c;
 }
 
-Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
-       for (Chunk &chunk : loaded) {
-               if (chunk.Position() == pos) {
-                       return &chunk;
+void ChunkIndex::Shift(Block::Face f) {
+       int a_axis = Block::Axis(f);
+       int b_axis = (a_axis + 1) % 3;
+       int c_axis = (a_axis + 2) % 3;
+       int dir = Block::Direction(f);
+       base[a_axis] += dir;
+       int a = GetCol(base[a_axis] + (extent * dir));
+       int a_stride = a * stride[a_axis];
+       for (int b = 0; b < side_length; ++b) {
+               int b_stride = b * stride[b_axis];
+               for (int c = 0; c < side_length; ++c) {
+                       int bc_stride = b_stride + c * stride[c_axis];
+                       int index = a_stride + bc_stride;
+                       Unset(index);
+                       int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
+                       if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
+                               Set(index, chunks[neighbor]->GetNeighbor(f));
+                       }
                }
        }
-       return nullptr;
 }
 
-bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
-       for (const Chunk::Pos &chunk : to_load) {
-               if (chunk == pos) {
-                       return true;
-               }
+void ChunkIndex::Clear() noexcept {
+       for (int i = 0; i < total_length && total_indexed > 0; ++i) {
+               Unset(i);
        }
-       return false;
 }
 
-bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept {
-       if (Loaded(pos)) return true;
-       return Queued(pos);
+void ChunkIndex::Scan() noexcept {
+       for (Chunk &chunk : store) {
+               Register(chunk);
+       }
 }
 
-Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
-       Chunk *chunk = Loaded(pos);
-       if (chunk) {
-               return *chunk;
+void ChunkIndex::Register(Chunk &chunk) noexcept {
+       if (InRange(chunk.Position())) {
+               Set(IndexOf(chunk.Position()), chunk);
+       }
+}
+
+void ChunkIndex::Set(int index, Chunk &chunk) noexcept {
+       Unset(index);
+       chunks[index] = &chunk;
+       chunk.Ref();
+       ++total_indexed;
+}
+
+void ChunkIndex::Unset(int index) noexcept {
+       if (chunks[index]) {
+               chunks[index]->UnRef();
+               chunks[index] = nullptr;
+               --total_indexed;
        }
+}
 
-       for (auto iter(to_load.begin()), end(to_load.end()); iter != end; ++iter) {
-               if (*iter == pos) {
-                       to_load.erase(iter);
+Chunk::Pos ChunkIndex::NextMissing() noexcept {
+       int roundtrip = last_missing;
+       while (chunks[last_missing]) {
+               ++last_missing;
+               last_missing %= total_length;
+               if (last_missing == roundtrip) {
                        break;
                }
        }
+       return PositionOf(last_missing);
+}
+
+
+ChunkStore::ChunkStore(const BlockTypeRegistry &types)
+: types(types)
+, loaded()
+, free()
+, indices() {
 
-       return Load(pos);
 }
 
-bool ChunkLoader::OutOfRange(const Chunk::Pos &pos) const noexcept {
-       return std::abs(base.x - pos.x) > unload_dist
-                       || std::abs(base.y - pos.y) > unload_dist
-                       || std::abs(base.z - pos.z) > unload_dist;
+ChunkStore::~ChunkStore() {
+
 }
 
-void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
-       if (new_base == base) {
-               return;
-       }
-       base = new_base;
+ChunkIndex &ChunkStore::MakeIndex(const Chunk::Pos &pos, int extent) {
+       indices.emplace_back(*this, pos, extent);
+       return indices.back();
+}
 
-       // unload far away chunks
-       for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
-               if (OutOfRange(*iter)) {
-                       iter = Remove(iter);
-               } else {
-                       ++iter;
-               }
-       }
-       // abort far away queued chunks
-       for (auto iter(to_load.begin()), end(to_load.end()); iter != end;) {
-               if (OutOfRange(*iter)) {
-                       iter = to_load.erase(iter);
+void ChunkStore::UnregisterIndex(ChunkIndex &index) {
+       for (auto i = indices.begin(), end = indices.end(); i != end; ++i) {
+               if (&*i == &index) {
+                       indices.erase(i);
+                       return;
                } else {
-                       ++iter;
+                       ++i;
                }
        }
-       // add missing new chunks
-       QueueSurrounding(base);
 }
 
-void ChunkLoader::QueueSurrounding(const Chunk::Pos &pos) {
-       const Chunk::Pos offset(load_dist, load_dist, load_dist);
-       Queue(pos - offset, pos + offset);
+Chunk *ChunkStore::Get(const Chunk::Pos &pos) {
+       for (ChunkIndex &index : indices) {
+               Chunk *chunk = index.Get(pos);
+               if (chunk) {
+                       return chunk;
+               }
+       }
+       return nullptr;
 }
 
-void ChunkLoader::Update(int dt) {
-       // check if a chunk load is scheduled for this frame
-       // and if there's chunks waiting to be loaded
-       gen_timer.Update(dt);
-       if (gen_timer.Hit()) {
-               // we may
-               // load until one of load or generation limits was hit
-               constexpr int max_load = 10;
-               constexpr int max_gen = 1;
-               int loaded = 0;
-               int generated = 0;
-               while (!to_load.empty() && loaded < max_load && generated < max_gen) {
-                       if (LoadOne()) {
-                               ++generated;
-                       } else {
-                               ++loaded;
-                       }
+Chunk *ChunkStore::Allocate(const Chunk::Pos &pos) {
+       Chunk *chunk = Get(pos);
+       if (chunk) {
+               return chunk;
+       }
+       if (free.empty()) {
+               loaded.emplace(loaded.begin(), types);
+       } else {
+               loaded.splice(loaded.begin(), free, free.begin());
+               loaded.front().Unlink();
+       }
+       chunk = &loaded.front();
+       chunk->Position(pos);
+       for (ChunkIndex &index : indices) {
+               if (index.InRange(pos)) {
+                       index.Register(*chunk);
                }
        }
-
-       constexpr int max_save = 10;
-       int saved = 0;
-       for (Chunk &chunk : loaded) {
-               if (chunk.ShouldUpdateSave()) {
-                       save.Write(chunk);
-                       ++saved;
-                       if (saved >= max_save) {
-                               break;
-                       }
+       for (int i = 0; i < Block::FACE_COUNT; ++i) {
+               Block::Face face = Block::Face(i);
+               Chunk::Pos neighbor_pos(pos + Block::FaceNormal(face));
+               Chunk *neighbor = Get(neighbor_pos);
+               if (neighbor) {
+                       chunk->SetNeighbor(face, *neighbor);
                }
        }
+       return chunk;
 }
 
-void ChunkLoader::LoadN(std::size_t n) {
-       std::size_t end = std::min(n, ToLoad());
-       for (std::size_t i = 0; i < end; ++i) {
-               LoadOne();
+bool ChunkStore::HasMissing() const noexcept {
+       for (const ChunkIndex &index : indices) {
+               if (index.MissingChunks() > 0) {
+                       return true;
+               }
        }
+       return false;
 }
 
-bool ChunkLoader::LoadOne() {
-       if (to_load.empty()) return false;
-
-       // take position of next chunk in queue
-       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) {
-               if (iter->Position() == pos) {
-                       loaded.splice(loaded.end(), to_free, iter);
-                       Insert(loaded.back());
-                       return false;
-               }
+int ChunkStore::EstimateMissing() const noexcept {
+       int missing = 0;
+       for (const ChunkIndex &index : indices) {
+               missing += index.MissingChunks();
        }
+       return missing;
+}
 
-       // if the free list is empty, allocate a new chunk
-       // otherwise clear an unused one
-       if (to_free.empty()) {
-               loaded.emplace_back(reg);
-       } else {
-               to_free.front().ClearNeighbors();
-               loaded.splice(loaded.end(), to_free, to_free.begin());
+Chunk::Pos ChunkStore::NextMissing() noexcept {
+       for (ChunkIndex &index : indices) {
+               if (index.MissingChunks()) {
+                       return index.NextMissing();
+               }
        }
+       return Chunk::Pos(0, 0, 0);
+}
 
-       bool generated = false;
-       Chunk &chunk = loaded.back();
-       chunk.Position(pos);
-       if (save.Exists(pos)) {
-               save.Read(chunk);
-       } else {
-               gen(chunk);
-               generated = true;
+void ChunkStore::Clean() {
+       for (auto i = loaded.begin(), end = loaded.end(); i != end;) {
+               if (i->Referenced()) {
+                       ++i;
+               } else {
+                       auto chunk = i;
+                       ++i;
+                       free.splice(free.end(), loaded, chunk);
+                       chunk->Unlink();
+                       chunk->InvalidateModel();
+               }
        }
-       Insert(chunk);
-       return generated;
 }
 
 }
diff --git a/src/world/render.cpp b/src/world/render.cpp
deleted file mode 100644 (file)
index 4a237e7..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-#include "ChunkRenderer.hpp"
-
-#include "World.hpp"
-#include "../app/Assets.hpp"
-#include "../graphics/BlockLighting.hpp"
-#include "../graphics/Viewport.hpp"
-
-
-namespace blank {
-
-ChunkRenderer::ChunkRenderer(World &world, int rd)
-: world(world)
-, block_tex()
-, render_dist(rd)
-, side_length(2 * rd + 1)
-, total_length(side_length * side_length * side_length)
-, total_indexed(0)
-, stride(1, side_length, side_length * side_length)
-, models(total_length)
-, chunks(total_length)
-, base(0, 0, 0)
-, fog_density(0.0f) {
-
-}
-
-
-void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) {
-       block_tex.Bind();
-       loader.LoadTextures(tex_index, block_tex);
-       block_tex.FilterNearest();
-}
-
-
-bool ChunkRenderer::InRange(const Chunk::Pos &pos) const noexcept {
-       return manhattan_radius(pos - base) <= render_dist;
-}
-
-int ChunkRenderer::IndexOf(const Chunk::Pos &pos) const noexcept {
-       Chunk::Pos mod_pos(
-               GetCol(pos.x),
-               GetCol(pos.y),
-               GetCol(pos.z)
-       );
-       return mod_pos.x * stride.x
-               +  mod_pos.y * stride.y
-               +  mod_pos.z * stride.z;
-}
-
-
-void ChunkRenderer::Rebase(const Chunk::Pos &new_base) {
-       if (new_base == base) return;
-
-       Chunk::Pos diff(new_base - base);
-
-       if (manhattan_radius(diff) > render_dist) {
-               // that's more than half, so probably not worth shifting
-               base = new_base;
-               Rescan();
-               return;
-       }
-
-       while (diff.x > 0) {
-               Shift(Block::FACE_RIGHT);
-               --diff.x;
-       }
-       while (diff.x < 0) {
-               Shift(Block::FACE_LEFT);
-               ++diff.x;
-       }
-       while (diff.y > 0) {
-               Shift(Block::FACE_UP);
-               --diff.y;
-       }
-       while (diff.y < 0) {
-               Shift(Block::FACE_DOWN);
-               ++diff.y;
-       }
-       while (diff.z > 0) {
-               Shift(Block::FACE_FRONT);
-               --diff.z;
-       }
-       while (diff.z < 0) {
-               Shift(Block::FACE_BACK);
-               ++diff.z;
-       }
-}
-
-int ChunkRenderer::GetCol(int c) const noexcept {
-       c %= side_length;
-       if (c < 0) c += side_length;
-       return c;
-}
-
-void ChunkRenderer::Shift(Block::Face f) {
-       int a_axis = Block::Axis(f);
-       int b_axis = (a_axis + 1) % 3;
-       int c_axis = (a_axis + 2) % 3;
-       int dir = Block::Direction(f);
-       base[a_axis] += dir;
-       int a = GetCol(base[a_axis] + (render_dist * dir));
-       int a_stride = a * stride[a_axis];
-       for (int b = 0; b < side_length; ++b) {
-               int b_stride = b * stride[b_axis];
-               for (int c = 0; c < side_length; ++c) {
-                       int bc_stride = b_stride + c * stride[c_axis];
-                       int index = a_stride + bc_stride;
-                       if (chunks[index]) {
-                               chunks[index] = nullptr;
-                               --total_indexed;
-                       }
-                       int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
-                       if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
-                               chunks[index] = &chunks[neighbor]->GetNeighbor(f);
-                               chunks[index]->InvalidateModel();
-                               ++total_indexed;
-                       }
-               }
-       }
-}
-
-
-void ChunkRenderer::Rescan() {
-       chunks.assign(total_length, nullptr);
-       total_indexed = 0;
-       Scan();
-}
-
-void ChunkRenderer::Scan() {
-       for (Chunk &chunk : world.Loader().Loaded()) {
-               if (!InRange(chunk.Position())) continue;
-               int index = IndexOf(chunk.Position());
-               if (!chunks[index]) {
-                       chunks[index] = &chunk;
-                       chunk.InvalidateModel();
-                       ++total_indexed;
-               }
-       }
-}
-
-void ChunkRenderer::Update(int dt) {
-       if (MissingChunks()) {
-               Scan();
-       }
-
-       // maximum of 1000 per second too high?
-       for (int i = 0, updates = 0; i < total_length && updates < dt; ++i) {
-               if (chunks[i] && chunks[i]->ShouldUpdateModel()) {
-                       chunks[i]->Update(models[i]);
-                       ++updates;
-               }
-       }
-}
-
-
-void ChunkRenderer::Render(Viewport &viewport) {
-       BlockLighting &chunk_prog = viewport.ChunkProgram();
-       chunk_prog.SetTexture(block_tex);
-       chunk_prog.SetFogDensity(fog_density);
-
-       for (int i = 0; i < total_length; ++i) {
-               if (!chunks[i]) continue;
-               glm::mat4 m(chunks[i]->Transform(base));
-               glm::mat4 mvp(chunk_prog.GetVP() * m);
-               if (!CullTest(Chunk::Bounds(), mvp)) {
-                       if (chunks[i]->ShouldUpdateModel()) {
-                               chunks[i]->Update(models[i]);
-                       }
-                       chunk_prog.SetM(m);
-                       models[i].Draw();
-               }
-       }
-}
-
-}
index c8dac8319f30835cbacf20a6b9c3d4391db1531a..0da68d045d0dd04b0dada4d0150c9435355fade8 100644 (file)
@@ -305,7 +305,7 @@ void ChunkTest::testNeighbor() {
        for (int i = 0; i < Block::FACE_COUNT; ++i) {
                Block::Face face = Block::Face(i);
                neighbor->Position(Block::FaceNormal(face));
-               chunk->SetNeighbor(*neighbor);
+               chunk->SetNeighbor(face, *neighbor);
                CPPUNIT_ASSERT_MESSAGE(
                        "chunk did not link right neighbor",
                        chunk->HasNeighbor(face)
@@ -322,16 +322,7 @@ void ChunkTest::testNeighbor() {
                        "chunk did not link correct neighbor",
                        &*chunk, &neighbor->GetNeighbor(Block::Opposite(face))
                );
-               chunk->ClearNeighbors();
-       }
-
-       neighbor->Position({1, 1, 1});
-       chunk->SetNeighbor(*neighbor);
-       for (int i = 0; i < Block::FACE_COUNT; ++i) {
-               CPPUNIT_ASSERT_MESSAGE(
-                       "chunk linked with non-neighbor",
-                       !chunk->HasNeighbor(Block::Face(i))
-               );
+               chunk->Unlink();
        }
 }