X-Git-Url: https://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fworld%2Fchunk.cpp;h=1d1a118a80030131b94e31534d2b5b7115591b80;hb=13e676a6e49128ebc6c63b8dd08bef51d360e8e9;hp=11c9d36bdaedee1682725db27c31d3341b89b305;hpb=43820582217f7e4b707d98f2e69d969eb77fc7c3;p=blank.git diff --git a/src/world/chunk.cpp b/src/world/chunk.cpp index 11c9d36..1d1a118 100644 --- a/src/world/chunk.cpp +++ b/src/world/chunk.cpp @@ -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 #include @@ -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 ®, + 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::iterator ChunkLoader::Remove(std::list::iterator chunk) noexcept { - // fetch next entry while chunk's still in the list - std::list::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; } }