From 1b8d280e3fff3d9be1631dd960b9f426d329b36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 24 May 2016 16:58:09 +0200 Subject: [core] refactor updateRenderables algorithm --- src/mbgl/algorithm/update_renderables.hpp | 115 ++-- src/mbgl/source/source.cpp | 36 +- test/algorithm/mock.hpp | 24 +- test/algorithm/update_renderables.cpp | 1001 +++++++++++++++++++++++------ 4 files changed, 879 insertions(+), 297 deletions(-) diff --git a/src/mbgl/algorithm/update_renderables.hpp b/src/mbgl/algorithm/update_renderables.hpp index 8948b8eafe..22d4205d90 100644 --- a/src/mbgl/algorithm/update_renderables.hpp +++ b/src/mbgl/algorithm/update_renderables.hpp @@ -2,65 +2,69 @@ #include -#include +#include namespace mbgl { namespace algorithm { -namespace { - -template -bool tryTile(const UnwrappedTileID& renderTileID, - const OverscaledTileID& dataTileID, - const DataTiles& dataTiles, - Renderables& renderables) { - if (renderables.find(renderTileID) == renderables.end()) { - const auto it = dataTiles.find(dataTileID); - if (it == dataTiles.end() || !it->second->isRenderable()) { - return false; - } - - using Renderable = typename Renderables::mapped_type; - renderables.emplace(renderTileID, Renderable{ renderTileID, *it->second }); - } - - return true; -} - -} // namespace - -template -std::map updateRenderables(const DataTiles& dataTiles, - const IdealTileIDs& idealTileIDs, - const SourceInfo& info, - const uint8_t z) { - std::map renderables; +template +void updateRenderables(GetTileDataFn getTileData, + CreateTileDataFn createTileData, + RetainTileDataFn retainTileData, + RenderTileFn renderTile, + const IdealTileIDs& idealTileIDs, + const SourceInfo& info, + const uint8_t dataTileZoom) { + std::set checked; + bool covered; + int32_t overscaledZ; // for (all in the set of ideal tiles of the source) { - for (const auto& renderTileID : idealTileIDs) { - assert(renderTileID.canonical.z >= info.minZoom); - assert(renderTileID.canonical.z <= info.maxZoom); - assert(z >= renderTileID.canonical.z); - const auto wrap = renderTileID.wrap; - const OverscaledTileID dataTileID(z, renderTileID.canonical); + for (const auto& idealRenderTileID : idealTileIDs) { + assert(idealRenderTileID.canonical.z >= info.minZoom); + assert(idealRenderTileID.canonical.z <= info.maxZoom); + assert(dataTileZoom >= idealRenderTileID.canonical.z); + + const OverscaledTileID idealDataTileID(dataTileZoom, idealRenderTileID.canonical); + auto data = getTileData(idealDataTileID); + if (!data) { + data = createTileData(idealDataTileID); + assert(data); + } // if (source has the tile and bucket is loaded) { - if (!tryTile(renderTileID, dataTileID, dataTiles, renderables)) { - // The source doesn't have the tile, or the bucket isn't loaded. - bool covered = true; - int32_t overscaledZ = z + 1; + if (data->isRenderable()) { + retainTileData(*data); + renderTile(idealRenderTileID, *data); + } else { + // The tile isn't loaded yet, but retain it anyway because it's an ideal tile. + retainTileData(*data); + covered = true; + overscaledZ = dataTileZoom + 1; if (overscaledZ > info.maxZoom) { // We're looking for an overzoomed child tile. - const auto childDataTileID = dataTileID.scaledTo(overscaledZ); - if (!tryTile(renderTileID, childDataTileID, dataTiles, renderables)) { + const auto childDataTileID = idealDataTileID.scaledTo(overscaledZ); + data = getTileData(childDataTileID); + if (data && data->isRenderable()) { + retainTileData(*data); + renderTile(idealRenderTileID, *data); + } else { covered = false; } } else { // Check all four actual child tiles. - for (const auto& childTileID : dataTileID.canonical.children()) { + for (const auto& childTileID : idealDataTileID.canonical.children()) { const OverscaledTileID childDataTileID(overscaledZ, childTileID); - const UnwrappedTileID childRenderTileID(wrap, childTileID); - if (!tryTile(childRenderTileID, childDataTileID, dataTiles, renderables)) { + data = getTileData(childDataTileID); + if (data && data->isRenderable()) { + retainTileData(*data); + renderTile(childDataTileID.unwrapTo(idealRenderTileID.wrap), *data); + } else { // At least one child tile doesn't exist, so we are going to look for // parents as well. covered = false; @@ -70,10 +74,23 @@ std::map updateRenderables(const DataTiles& dataTil if (!covered) { // We couldn't find child tiles that entirely cover the ideal tile. - for (overscaledZ = z - 1; overscaledZ >= info.minZoom; --overscaledZ) { - const auto parentDataTileID = dataTileID.scaledTo(overscaledZ); - const auto parentRenderTileID = parentDataTileID.unwrapTo(renderTileID.wrap); - if (tryTile(parentRenderTileID, parentDataTileID, dataTiles, renderables)) { + for (overscaledZ = dataTileZoom - 1; overscaledZ >= info.minZoom; --overscaledZ) { + const auto parentDataTileID = idealDataTileID.scaledTo(overscaledZ); + const auto parentRenderTileID = + parentDataTileID.unwrapTo(idealRenderTileID.wrap); + + if (checked.find(parentRenderTileID) != checked.end()) { + // Break parent tile ascent, this route has been checked by another child + // tile before. + break; + } else { + checked.emplace(parentRenderTileID); + } + + data = getTileData(parentDataTileID); + if (data && data->isRenderable()) { + retainTileData(*data); + renderTile(parentRenderTileID, *data); // Break parent tile ascent, since we found one. break; } @@ -81,8 +98,6 @@ std::map updateRenderables(const DataTiles& dataTil } } } - - return renderables; } } // namespace algorithm diff --git a/src/mbgl/source/source.cpp b/src/mbgl/source/source.cpp index 5f2b54485b..1c55b9c024 100644 --- a/src/mbgl/source/source.cpp +++ b/src/mbgl/source/source.cpp @@ -251,7 +251,7 @@ bool Source::update(const StyleUpdateParameters& parameters) { } idealTiles = util::tileCover(parameters.transformState, idealZoom); - } + } // Stores a list of all the data tiles that we're definitely going to retain. There are two // kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in @@ -259,24 +259,26 @@ bool Source::update(const StyleUpdateParameters& parameters) { // we're actively using, e.g. as a replacement for tile that aren't loaded yet. std::set retain; - // Create all tiles that we definitely want to load - for (const auto& unwrappedTileID : idealTiles) { - const OverscaledTileID dataTileID(dataTileZoom, unwrappedTileID.canonical); - retain.emplace(dataTileID); - - auto it = tileDataMap.find(dataTileID); - if (it == tileDataMap.end()) { - if (auto data = createTile(dataTileID, parameters)) { - it = tileDataMap.emplace(dataTileID, std::move(data)).first; - } + auto retainTileDataFn = [&retain](const TileData& tileData) -> void { + retain.emplace(tileData.id); + }; + auto getTileDataFn = [this](const OverscaledTileID& dataTileID) -> TileData* { + return getTileData(dataTileID); + }; + auto createTileDataFn = [this, ¶meters](const OverscaledTileID& dataTileID) -> TileData* { + if (auto data = createTile(dataTileID, parameters)) { + return tileDataMap.emplace(dataTileID, std::move(data)).first->second.get(); + } else { + return nullptr; } - } - - tiles = algorithm::updateRenderables(tileDataMap, idealTiles, *info, overscaledZoom); + }; + auto renderTileFn = [this](const UnwrappedTileID& renderTileID, TileData& tileData) { + tiles.emplace(renderTileID, Tile{ renderTileID, tileData }); + }; - for (auto& pair : tiles) { - retain.emplace(pair.second.data.id); - } + tiles.clear(); + algorithm::updateRenderables(getTileDataFn, createTileDataFn, retainTileDataFn, renderTileFn, + idealTiles, *info, dataTileZoom); if (type != SourceType::Raster && type != SourceType::Annotations && cache.getSize() == 0) { size_t conservativeCacheSize = diff --git a/test/algorithm/mock.hpp b/test/algorithm/mock.hpp index a1ee534f79..28c78854be 100644 --- a/test/algorithm/mock.hpp +++ b/test/algorithm/mock.hpp @@ -15,26 +15,10 @@ struct MockSourceInfo { struct MockTileData; -struct MockRenderable { - MockRenderable(mbgl::UnwrappedTileID id_, MockTileData& data_) : id(id_), data(data_) {} - - const mbgl::UnwrappedTileID id; - MockTileData& data; - - bool operator==(const MockRenderable& rhs) const { - return &data == &rhs.data; - } -}; - -::std::ostream& operator<<(::std::ostream& os, const MockRenderable&) { - return os << "Renderable{}"; -} - struct MockSource { MockSourceInfo info; std::map> dataTiles; std::set idealTiles; - std::map renderables; // Test API inline MockTileData* createTileData(const mbgl::OverscaledTileID& tileID); @@ -44,14 +28,16 @@ struct MockBucket {}; struct MockTileData { + MockTileData(const mbgl::OverscaledTileID& tileID_) : tileID(tileID_) {} bool isRenderable() { - return ready; + return renderable; } - bool ready = false; + bool renderable = false; + const mbgl::OverscaledTileID tileID; }; MockTileData* MockSource::createTileData(const mbgl::OverscaledTileID& tileID) { // Replace the existing MockTileData object, if any. - return (dataTiles[tileID] = std::make_unique()).get(); + return (dataTiles[tileID] = std::make_unique(tileID)).get(); } diff --git a/test/algorithm/update_renderables.cpp b/test/algorithm/update_renderables.cpp index 704cd770db..67c7d79a0a 100644 --- a/test/algorithm/update_renderables.cpp +++ b/test/algorithm/update_renderables.cpp @@ -1,367 +1,946 @@ -#include #include "mock.hpp" +#include +#include + +#include #include using namespace mbgl; +struct GetTileDataAction { + const OverscaledTileID tileID; + const bool found; + + bool operator==(const GetTileDataAction& rhs) const { + return tileID == rhs.tileID && found == rhs.found; + } +}; + +std::ostream& operator<<(std::ostream& os, const GetTileDataAction& action) { + return os << "GetTileDataAction{ { " << int(action.tileID.overscaledZ) << ", { " + << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " + << action.tileID.canonical.y << " } }, " << (action.found ? "true" : "false") << " }"; +} + +struct CreateTileDataAction { + const OverscaledTileID tileID; + + bool operator==(const CreateTileDataAction& rhs) const { + return tileID == rhs.tileID; + } +}; + +std::ostream& operator<<(std::ostream& os, const CreateTileDataAction& action) { + return os << "CreateTileDataAction{ { " << int(action.tileID.overscaledZ) << ", { " + << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " + << action.tileID.canonical.y << " } } }"; +} + +struct RetainTileDataAction { + const OverscaledTileID tileID; + + bool operator==(const RetainTileDataAction& rhs) const { + return tileID == rhs.tileID; + } +}; + +std::ostream& operator<<(std::ostream& os, const RetainTileDataAction& action) { + return os << "RetainTileDataAction{ { " << int(action.tileID.overscaledZ) << ", { " + << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " + << action.tileID.canonical.y << " } } }"; +} + +struct RenderTileAction { + const UnwrappedTileID tileID; + const MockTileData& tileData; + + bool operator==(const RenderTileAction& rhs) const { + return tileID == rhs.tileID && &tileData == &rhs.tileData; + } +}; + +std::ostream& operator<<(std::ostream& os, const RenderTileAction& action) { + const int64_t x = + (1ul << action.tileID.canonical.z) * action.tileID.wrap + action.tileID.canonical.x; + return os << "RenderTileAction{ { " << int(action.tileID.canonical.z) << ", " << x << ", " + << action.tileID.canonical.y << " }, *tile_" + << int(action.tileData.tileID.overscaledZ) << "_" + << int(action.tileData.tileID.canonical.z) << "_" + << action.tileData.tileID.canonical.x << "_" << action.tileData.tileID.canonical.y + << " }"; +} + +using ActionLogEntry = + variant; +using ActionLog = std::vector; + +template +auto getTileDataFn(ActionLog& log, const T& dataTiles) { + return [&](const auto& id) { + auto it = dataTiles.find(id); + log.emplace_back(GetTileDataAction{ id, it != dataTiles.end() }); + return (it != dataTiles.end()) ? it->second.get() : nullptr; + }; +} + +template +auto createTileDataFn(ActionLog& log, T& dataTiles) { + return [&](const auto& id) { + log.emplace_back(CreateTileDataAction{ id }); + return (dataTiles[id] = std::make_unique(id)).get(); + }; +} + +auto retainTileDataFn(ActionLog& log) { + return [&](auto& tileData) { log.emplace_back(RetainTileDataAction{ tileData.tileID }); }; +} + +auto renderTileFn(ActionLog& log) { + return [&](const auto& id, auto& tileData) { + log.emplace_back(RenderTileAction{ id, tileData }); + }; +} + TEST(UpdateRenderables, SingleTile) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.idealTiles.emplace(UnwrappedTileID{ 1, 1, 1 }); // Make sure that we're getting the tile back. - auto tile_1_1_1 = source.createTileData(OverscaledTileID{ 1, 1, 1 }); - tile_1_1_1->ready = true; - - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 1, 1 }, MockRenderable{ { 1, 1, 1 }, *tile_1_1_1 } }, + auto tile_1_1_1_1 = source.createTileData(OverscaledTileID{ 1, 1, 1 }); + tile_1_1_1_1->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 1, 1 } }, true }, // found ideal tile + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), - source.renderables); + log); // Check a repeated render with the same data. - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 1, 1 }, MockRenderable{ { 1, 1, 1 }, *tile_1_1_1 } }, + log.clear(); + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 1, 1 } }, true }, // found ideal tile + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), - source.renderables); + log); // Insert a tile we don't have data for. + log.clear(); source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 1 }); - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 1, 1 }, MockRenderable{ { 1, 1, 1 }, *tile_1_1_1 } }, + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 1 } }, false }, // missing ideal tile + CreateTileDataAction{ { 1, { 1, 0, 1 } } }, // create ideal tile + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + GetTileDataAction{ { 2, { 2, 0, 2 } }, false }, // four child tiles + GetTileDataAction{ { 2, { 2, 0, 3 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 2 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 3 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // parent tile + + GetTileDataAction{ { 1, { 1, 1, 1 } }, true }, // found ideal tile + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), - source.renderables); + log); // Now insert the missing tile and check that we're rendering it. - auto tile_1_0_1 = source.createTileData(OverscaledTileID{ 1, 0, 1 }); - tile_1_0_1->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 0, 1 }, MockRenderable{ { 1, 0, 1 }, *tile_1_0_1 } }, - { { 1, 1, 1 }, MockRenderable{ { 1, 1, 1 }, *tile_1_1_1 } }, + log.clear(); + auto tile_1_1_0_1 = source.createTileData(OverscaledTileID{ 1, 0, 1 }); + tile_1_1_0_1->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 1 } }, true }, // newly added tile + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile + + GetTileDataAction{ { 1, { 1, 1, 1 } }, true }, // ideal tile + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), - source.renderables); + log); // Insert another tile, and another bucket that has a different name and check that we're not // using it. + log.clear(); source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); - auto tile_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 }); - - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 0, 1 }, MockRenderable{ { 1, 0, 1 }, *tile_1_0_1 } }, - { { 1, 1, 1 }, MockRenderable{ { 1, 1, 1 }, *tile_1_1_1 } }, + auto tile_1_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 }); + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // found tile, not ready + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // four child tiles + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // parent tile + + GetTileDataAction{ { 1, { 1, 0, 1 } }, true }, // ideal tile + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile + + GetTileDataAction{ { 1, { 1, 1, 1 } }, true }, // ideal tile + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), - source.renderables); + log); // Then, add the bucket and check that it's getting used. - tile_1_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 0, 0 }, MockRenderable{ { 1, 0, 0 }, *tile_1_0_0 } }, - { { 1, 0, 1 }, MockRenderable{ { 1, 0, 1 }, *tile_1_0_1 } }, - { { 1, 1, 1 }, MockRenderable{ { 1, 1, 1 }, *tile_1_1_1 } }, - }), - source.renderables); + log.clear(); + tile_1_1_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // found tile, now ready + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // + + GetTileDataAction{ { 1, { 1, 0, 1 } }, true }, // ideal tile + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // + + GetTileDataAction{ { 1, { 1, 1, 1 } }, true }, // ideal tile + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // + }), + log); } TEST(UpdateRenderables, UseParentTile) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 1 }); source.idealTiles.emplace(UnwrappedTileID{ 1, 1, 0 }); source.idealTiles.emplace(UnwrappedTileID{ 1, 1, 1 }); // Make sure that we're getting the tile back. - auto tile_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); - tile_0_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, - }), - source.renderables); + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); + tile_0_0_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 1 } }, false }, // missing ideal tile + CreateTileDataAction{ { 1, { 1, 0, 1 } } }, // + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + GetTileDataAction{ { 2, { 2, 0, 2 } }, false }, // child tile + GetTileDataAction{ { 2, { 2, 0, 3 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 2 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 3 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // parent found! + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent + GetTileDataAction{ { 1, { 1, 1, 0 } }, false }, // missing ideal tile + CreateTileDataAction{ { 1, { 1, 1, 0 } } }, // + RetainTileDataAction{ { 1, { 1, 1, 0 } } }, // + GetTileDataAction{ { 2, { 2, 2, 0 } }, false }, // child tile + GetTileDataAction{ { 2, { 2, 2, 1 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 3, 0 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 3, 1 } }, false }, // ... + GetTileDataAction{ { 1, { 1, 1, 1 } }, false }, // missing tile + CreateTileDataAction{ { 1, { 1, 1, 1 } } }, // + RetainTileDataAction{ { 1, { 1, 1, 1 } } }, // + GetTileDataAction{ { 2, { 2, 2, 2 } }, false }, // child tile + GetTileDataAction{ { 2, { 2, 2, 3 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 3, 2 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 3, 3 } }, false }, // ... + }), + log); } TEST(UpdateRenderables, DontUseWrongParentTile) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 }); - auto tile_1_1_0 = source.createTileData(OverscaledTileID{ 1, 1, 0 }); - tile_1_1_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 2); - EXPECT_EQ(decltype(source.renderables)({}), source.renderables); + auto tile_1_1_1_0 = source.createTileData(OverscaledTileID{ 1, 1, 0 }); + tile_1_1_1_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // missing ideal tile + CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + GetTileDataAction{ { 3, { 3, 0, 0 } }, false }, // child tile + GetTileDataAction{ { 3, { 3, 0, 1 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 1, 0 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 1, 1 } }, false }, // ... + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // parent tile, missing + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // parent tile, missing + }), + log); // Add a new child tile and check that it is now used. + log.clear(); source.idealTiles.emplace(UnwrappedTileID{ 2, 2, 0 }); - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 2); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 1, 0 }, MockRenderable{ { 1, 1, 0 }, *tile_1_1_0 } }, + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // non-ready ideal tile + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + // this tile was added by the previous invocation of updateRenderables + GetTileDataAction{ { 3, { 3, 0, 0 } }, false }, // child tile + GetTileDataAction{ { 3, { 3, 0, 1 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 1, 0 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 1, 1 } }, false }, // ... + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // missing parent tile + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // missing parent tile + + GetTileDataAction{ { 2, { 2, 2, 0 } }, false }, // missing ideal tile + CreateTileDataAction{ { 2, { 2, 2, 0 } } }, // + RetainTileDataAction{ { 2, { 2, 2, 0 } } }, // + GetTileDataAction{ { 3, { 3, 4, 0 } }, false }, // child tile + GetTileDataAction{ { 3, { 3, 4, 1 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 5, 0 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 5, 1 } }, false }, // ... + GetTileDataAction{ { 1, { 1, 1, 0 } }, true }, // found parent tile + RetainTileDataAction{ { 1, { 1, 1, 0 } } }, // + RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render parent tile }), - source.renderables); + log); } TEST(UpdateRenderables, UseParentTileWhenChildNotReady) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 1 }); - auto tile_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); - tile_0_0_0->ready = true; + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); + tile_0_0_0_0->renderable = true; - auto tile_1_0_1 = source.createTileData(OverscaledTileID{ 1, 0, 1 }); + auto tile_1_1_0_1 = source.createTileData(OverscaledTileID{ 1, 0, 1 }); // Don't create bucket. // Make sure that it renders the parent tile. - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 1 } }, true }, // found, but not ready + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + GetTileDataAction{ { 2, { 2, 0, 2 } }, false }, // child tile + GetTileDataAction{ { 2, { 2, 0, 3 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 2 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 3 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // parent tile, ready + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent tile }), - source.renderables); + log); // Now insert the bucket and make sure we're now using the matching tile - tile_1_0_1->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 0, 1 }, MockRenderable{ { 1, 0, 1 }, *tile_1_0_1 } }, + log.clear(); + tile_1_1_0_1->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 1 } }, true }, // found and ready + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile }), - source.renderables); + log); } TEST(UpdateRenderables, UseOverlappingParentTile) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 1 }); - auto tile_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); - tile_0_0_0->ready = true; - - auto tile_1_0_1 = source.createTileData(OverscaledTileID{ 1, 0, 1 }); - tile_1_0_1->ready = true; - - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, - { { 1, 0, 1 }, MockRenderable{ { 1, 0, 1 }, *tile_1_0_1 } }, + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); + tile_0_0_0_0->renderable = true; + + auto tile_1_1_0_1 = source.createTileData(OverscaledTileID{ 1, 0, 1 }); + tile_1_1_0_1->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // ideal tile not found + CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // child tile + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // parent tile found + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // + + GetTileDataAction{ { 1, { 1, 0, 1 } }, true }, // ideal tile found + RetainTileDataAction{ { 1, { 1, 0, 1 } } }, // + RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // }), - source.renderables); + log); } TEST(UpdateRenderables, UseChildTiles) { + ActionLog log; MockSource source; - source.idealTiles.emplace(UnwrappedTileID{ 0, 0, 0 }); + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); - auto tile_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 }); - tile_1_0_0->ready = true; - auto tile_1_1_0 = source.createTileData(OverscaledTileID{ 1, 1, 0 }); - tile_1_1_0->ready = true; + source.idealTiles.emplace(UnwrappedTileID{ 0, 0, 0 }); - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 0); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 0, 0 }, MockRenderable{ { 1, 0, 0 }, *tile_1_0_0 } }, - { { 1, 1, 0 }, MockRenderable{ { 1, 1, 0 }, *tile_1_1_0 } }, + auto tile_1_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 }); + tile_1_1_0_0->renderable = true; + auto tile_1_1_1_0 = source.createTileData(OverscaledTileID{ 1, 1, 0 }); + tile_1_1_1_0->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 0); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // ideal tile, missing + CreateTileDataAction{ { 0, { 0, 0, 0 } } }, // + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // child tile found + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // render child tile + GetTileDataAction{ { 1, { 1, 0, 1 } }, false }, // child tile not found + GetTileDataAction{ { 1, { 1, 1, 0 } }, true }, // child tile found + RetainTileDataAction{ { 1, { 1, 1, 0 } } }, // + RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render child tile + GetTileDataAction{ { 1, { 1, 1, 1 } }, false }, // child tile not found + // no parent tile of 0 to consider }), - source.renderables); + log); } TEST(UpdateRenderables, PreferChildTiles) { + ActionLog log; MockSource source; - source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); - auto tile_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); - tile_0_0_0->ready = true; - auto tile_2_0_0 = source.createTileData(OverscaledTileID{ 2, 0, 0 }); - tile_2_0_0->ready = true; + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_0_0 } }, + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); + tile_0_0_0_0->renderable = true; + auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, 0, 0 }); + tile_2_2_0_0->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // ideal tile, not found + CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // child tile, found + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // child tile, not found + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // parent tile, found + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), - source.renderables); + log); // Now add more children to cover the ideal tile fully, until it is covered fully, and verify // that the parent doesn't get rendered. - auto tile_2_0_1 = source.createTileData(OverscaledTileID{ 2, 0, 1 }); - tile_2_0_1->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_0_0 } }, - { { 2, 0, 1 }, MockRenderable{ { 2, 0, 1 }, *tile_2_0_1 } }, - }), - source.renderables); - - auto tile_2_1_0 = source.createTileData(OverscaledTileID{ 2, 1, 0 }); - tile_2_1_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_0_0 } }, - { { 2, 0, 1 }, MockRenderable{ { 2, 0, 1 }, *tile_2_0_1 } }, - { { 2, 1, 0 }, MockRenderable{ { 2, 1, 0 }, *tile_2_1_0 } }, - }), - source.renderables); + log.clear(); + auto tile_2_2_0_1 = source.createTileData(OverscaledTileID{ 2, 0, 1 }); + tile_2_2_0_1->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // ideal tile, not ready + // ideal tile was added in previous invocation, but is not yet ready + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // child tile, found + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, true }, // ... + RetainTileDataAction{ { 2, { 2, 0, 1 } } }, // ... + RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // child tile, not found + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // parent tile, found + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // + }), + log); + + log.clear(); + auto tile_2_2_1_0 = source.createTileData(OverscaledTileID{ 2, 1, 0 }); + tile_2_2_1_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // ideal tile, not ready + // ideal tile was added in first invocation, but is not yet ready + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // child tile, found + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, true }, // ... + RetainTileDataAction{ { 2, { 2, 0, 1 } } }, // + RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, true }, // ... + RetainTileDataAction{ { 2, { 2, 1, 0 } } }, // + RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, // + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // child tile, not found + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // parent tile, found + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // + }), + log); // Adding the last child tile covers 1/0/0 fully, so we don't need 0/0/0 anymore. - auto tile_2_1_1 = source.createTileData(OverscaledTileID{ 2, 1, 1 }); - tile_2_1_1->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_0_0 } }, - { { 2, 0, 1 }, MockRenderable{ { 2, 0, 1 }, *tile_2_0_1 } }, - { { 2, 1, 0 }, MockRenderable{ { 2, 1, 0 }, *tile_2_1_0 } }, - { { 2, 1, 1 }, MockRenderable{ { 2, 1, 1 }, *tile_2_1_1 } }, - }), - source.renderables); + log.clear(); + auto tile_2_2_1_1 = source.createTileData(OverscaledTileID{ 2, 1, 1 }); + tile_2_2_1_1->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // ideal tile, not ready + // ideal tile was added in first invocation, but is not yet ready + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // child tile, found + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, true }, // ... + RetainTileDataAction{ { 2, { 2, 0, 1 } } }, // + RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, true }, // ... + RetainTileDataAction{ { 2, { 2, 1, 0 } } }, // + RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, // + GetTileDataAction{ { 2, { 2, 1, 1 } }, true }, // ... + RetainTileDataAction{ { 2, { 2, 1, 1 } } }, // + RenderTileAction{ { 2, 1, 1 }, *tile_2_2_1_1 }, // + }), + log); } TEST(UpdateRenderables, UseParentAndChildTiles) { + ActionLog log; MockSource source; - source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); - auto tile_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); - tile_0_0_0->ready = true; - auto tile_2_0_0 = source.createTileData(OverscaledTileID{ 2, 0, 0 }); - tile_2_0_0->ready = true; + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); - // Check that it uses the child tile, but not the parent tile. - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_0_0 } }, + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 }); + tile_0_0_0_0->renderable = true; + auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, 0, 0 }); + tile_2_2_0_0->renderable = true; + + // Check that it uses the child tile and the parent tile to cover the rest. + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // ideal tile, missing + CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), - source.renderables); + log); - // Then, remove the child tile and check that it now uses the parent tile. + // Then, remove the child tile and check that it now only the parent tile. + log.clear(); source.dataTiles.erase(OverscaledTileID{ 2, 0, 0 }); - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 1); - EXPECT_EQ(decltype(source.renderables)({ - { { 0, 0, 0 }, MockRenderable{ { 0, 0, 0 }, *tile_0_0_0 } }, + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // ideal tile, not ready + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), - source.renderables); + log); } TEST(UpdateRenderables, DontUseTilesLowerThanMinzoom) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.info.minZoom = 2; source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 }); - auto tile_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 }); - tile_1_0_0->ready = true; - - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 2); - EXPECT_EQ(decltype(source.renderables)({}), source.renderables); + auto tile_1_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 }); + tile_1_1_0_0->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // ideal tile, missing + CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + GetTileDataAction{ { 3, { 3, 0, 0 } }, false }, // + GetTileDataAction{ { 3, { 3, 0, 1 } }, false }, // + GetTileDataAction{ { 3, { 3, 1, 0 } }, false }, // + GetTileDataAction{ { 3, { 3, 1, 1 } }, false }, // + // no requests for zoom 1 tiles + }), + log); } TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.info.maxZoom = 2; source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 }); // Add a child tile (that should never occur in practice) and make sure it's not selected. auto tile_3_3_0_0 = source.createTileData(OverscaledTileID{ 3, 0, 0 }); - tile_3_3_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 2); - EXPECT_EQ(decltype(source.renderables)({}), source.renderables); + tile_3_3_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // ideal tile, missing + CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + GetTileDataAction{ { 3, { 2, 0, 0 } }, false }, // overzoomed tile, not children! + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // + }), + log); // Only add a non-overzoomed ("parent") tile at first. + log.clear(); auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, { 2, 0, 0 } }); - tile_2_2_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 3); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_2_0_0 } }, + tile_2_2_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 3); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 3, { 2, 0, 0 } }, false }, // ideal tile, missing + CreateTileDataAction{ { 3, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 3, { 2, 0, 0 } } }, // + GetTileDataAction{ { 4, { 2, 0, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), - source.renderables); + log); // Then add the overzoomed tile matching the zoom level we're rendering. + log.clear(); auto tile_3_2_0_0 = source.createTileData(OverscaledTileID{ 3, { 2, 0, 0 } }); - tile_3_2_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 3); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_3_2_0_0 } }, + tile_3_2_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 3); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 3, { 2, 0, 0 } }, true }, // + RetainTileDataAction{ { 3, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), - source.renderables); + log); // Check that it's switching back to the tile that has the matching overzoom value. - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 2); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_2_0_0 } }, + log.clear(); + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), - source.renderables); + log); // Now remove the best match. + log.clear(); source.dataTiles.erase(OverscaledTileID{ 2, { 2, 0, 0 } }); tile_2_2_0_0 = nullptr; // Use the overzoomed tile even though it doesn't match the zoom level. - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 2); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_3_2_0_0 } }, + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // + CreateTileDataAction{ { 2, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + GetTileDataAction{ { 3, { 2, 0, 0 } }, true }, // use overzoomed tile! + RetainTileDataAction{ { 3, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), - source.renderables); + log); } TEST(UpdateRenderables, AscendToNonOverzoomedTiles) { + ActionLog log; MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + source.info.maxZoom = 2; source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 }); // Add a matching overzoomed tile and make sure it gets selected. auto tile_3_2_0_0 = source.createTileData(OverscaledTileID{ 3, { 2, 0, 0 } }); - tile_3_2_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 3); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_3_2_0_0 } }, + tile_3_2_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 3); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 3, { 2, 0, 0 } }, true }, // + RetainTileDataAction{ { 3, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), - source.renderables); + log); // Then, swap it with a non-overzoomed tile. + log.clear(); source.dataTiles.erase(OverscaledTileID{ 3, { 2, 0, 0 } }); tile_3_2_0_0 = nullptr; auto tile_2_2_0_0 = source.createTileData(OverscaledTileID{ 2, { 2, 0, 0 } }); - tile_2_2_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 3); - EXPECT_EQ(decltype(source.renderables)({ - { { 2, 0, 0 }, MockRenderable{ { 2, 0, 0 }, *tile_2_2_0_0 } }, + tile_2_2_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 3); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 3, { 2, 0, 0 } }, false }, // + CreateTileDataAction{ { 3, { 2, 0, 0 } } }, // + RetainTileDataAction{ { 3, { 2, 0, 0 } } }, // + GetTileDataAction{ { 4, { 2, 0, 0 } }, false }, // prefer using a child first + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), - source.renderables); + log); // Then, swap it with a parent tile. + log.clear(); source.dataTiles.erase(OverscaledTileID{ 2, { 2, 0, 0 } }); tile_2_2_0_0 = nullptr; auto tile_1_1_0_0 = source.createTileData(OverscaledTileID{ 1, { 1, 0, 0 } }); - tile_1_1_0_0->ready = true; - source.renderables = algorithm::updateRenderables( - source.dataTiles, source.idealTiles, source.info, 3); - EXPECT_EQ(decltype(source.renderables)({ - { { 1, 0, 0 }, MockRenderable{ { 1, 0, 0 }, *tile_1_1_0_0 } }, + tile_1_1_0_0->renderable = true; + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 3); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 3, { 2, 0, 0 } }, true }, // ideal tile found, but not ready + RetainTileDataAction{ { 3, { 2, 0, 0 } } }, // + GetTileDataAction{ { 4, { 2, 0, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // + }), + log); +} + +TEST(UpdateRenderables, DoNotAscendMultipleTimesIfNotFound) { + ActionLog log; + MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + + source.idealTiles.emplace(UnwrappedTileID{ 8, 0, 0 }); + source.idealTiles.emplace(UnwrappedTileID{ 8, 1, 0 }); + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 8); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 8, { 8, 0, 0 } }, false }, // ideal tile + CreateTileDataAction{ { 8, { 8, 0, 0 } } }, // + RetainTileDataAction{ { 8, { 8, 0, 0 } } }, // + GetTileDataAction{ { 9, { 9, 0, 0 } }, false }, // child tile + GetTileDataAction{ { 9, { 9, 0, 1 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 1, 0 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 1, 1 } }, false }, // ... + GetTileDataAction{ { 7, { 7, 0, 0 } }, false }, // ascent + GetTileDataAction{ { 6, { 6, 0, 0 } }, false }, // ... + GetTileDataAction{ { 5, { 5, 0, 0 } }, false }, // ... + GetTileDataAction{ { 4, { 4, 0, 0 } }, false }, // ... + GetTileDataAction{ { 3, { 3, 0, 0 } }, false }, // ... + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // ... + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // ... + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // ... + + GetTileDataAction{ { 8, { 8, 1, 0 } }, false }, // ideal tile + CreateTileDataAction{ { 8, { 8, 1, 0 } } }, // + RetainTileDataAction{ { 8, { 8, 1, 0 } } }, // + GetTileDataAction{ { 9, { 9, 2, 0 } }, false }, // child tile + GetTileDataAction{ { 9, { 9, 2, 1 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 3, 0 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 3, 1 } }, false }, // ... + // no second ascent to 0 + }), + log); + + // Now add a mid-level tile that stops the ascent + log.clear(); + auto tile_4_0_0_0 = source.createTileData(OverscaledTileID{ 4, { 4, 0, 0 } }); + tile_4_0_0_0->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 8); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 8, { 8, 0, 0 } }, true }, // ideal tile found, but not ready + RetainTileDataAction{ { 8, { 8, 0, 0 } } }, // + GetTileDataAction{ { 9, { 9, 0, 0 } }, false }, // child tile + GetTileDataAction{ { 9, { 9, 0, 1 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 1, 0 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 1, 1 } }, false }, // ... + GetTileDataAction{ { 7, { 7, 0, 0 } }, false }, // ascent + GetTileDataAction{ { 6, { 6, 0, 0 } }, false }, // ... + GetTileDataAction{ { 5, { 5, 0, 0 } }, false }, // ... + GetTileDataAction{ { 4, { 4, 0, 0 } }, true }, // stops ascent + RetainTileDataAction{ { 4, { 4, 0, 0 } } }, // + RenderTileAction{ { 4, 0, 0 }, *tile_4_0_0_0 }, + + GetTileDataAction{ { 8, { 8, 1, 0 } }, true }, // ideal tile found, but not ready + RetainTileDataAction{ { 8, { 8, 1, 0 } } }, // + GetTileDataAction{ { 9, { 9, 2, 0 } }, false }, // child tile + GetTileDataAction{ { 9, { 9, 2, 1 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 3, 0 } }, false }, // ... + GetTileDataAction{ { 9, { 9, 3, 1 } }, false }, // ... + // no second ascent to 0 + }), + log); +} + +TEST(UpdateRenderables, DontRetainUnusedNonIdealTiles) { + ActionLog log; + MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + + source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 }); + + source.createTileData(OverscaledTileID{ 1, 0, 0 }); + source.createTileData(OverscaledTileID{ 2, 0, 0 }); + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 2); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 2, { 2, 0, 0 } }, true }, // ideal tile, not ready + RetainTileDataAction{ { 2, { 2, 0, 0 } } }, // + GetTileDataAction{ { 3, { 3, 0, 0 } }, false }, // + GetTileDataAction{ { 3, { 3, 0, 1 } }, false }, // + GetTileDataAction{ { 3, { 3, 1, 0 } }, false }, // + GetTileDataAction{ { 3, { 3, 1, 1 } }, false }, // + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // parent tile, not ready + // don't retain the parent tile + GetTileDataAction{ { 0, { 0, 0, 0 } }, false }, // + }), + log); +} + +TEST(UpdateRenderables, WrappedTiles) { + ActionLog log; + MockSource source; + auto getTileData = getTileDataFn(log, source.dataTiles); + auto createTileData = createTileDataFn(log, source.dataTiles); + auto retainTileData = retainTileDataFn(log); + auto renderTile = renderTileFn(log); + + source.idealTiles.emplace(UnwrappedTileID{ 1, -1, 0 }); + source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 0 }); + source.idealTiles.emplace(UnwrappedTileID{ 1, 1, 0 }); + source.idealTiles.emplace(UnwrappedTileID{ 1, 2, 0 }); + + auto tile_0_0_0_0 = source.createTileData(OverscaledTileID{ 0, { 0, 0, 0 } }); + tile_0_0_0_0->renderable = true; + + algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile, + source.idealTiles, source.info, 1); + EXPECT_EQ(ActionLog({ + GetTileDataAction{ { 1, { 1, 1, 0 } }, false }, // ideal tile 1/-1/0 + CreateTileDataAction{ { 1, { 1, 1, 0 } } }, // + RetainTileDataAction{ { 1, { 1, 1, 0 } } }, // + GetTileDataAction{ { 2, { 2, 2, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 2, 1 } }, false }, // + GetTileDataAction{ { 2, { 2, 3, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 3, 1 } }, false }, // + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, -1, 0 }, *tile_0_0_0_0 }, // + + GetTileDataAction{ { 1, { 1, 0, 0 } }, false }, // ideal tile 1/0/0 + CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // + + GetTileDataAction{ { 1, { 1, 1, 0 } }, true }, // ideal tile 1/1/0 + RetainTileDataAction{ { 1, { 1, 1, 0 } } }, // + GetTileDataAction{ { 2, { 2, 2, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 2, 1 } }, false }, // + GetTileDataAction{ { 2, { 2, 3, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 3, 1 } }, false }, // + // do not ascent; 0/0/0 has been rendered already for 1/0/0 + + GetTileDataAction{ { 1, { 1, 0, 0 } }, true }, // ideal tile 1/2/0 + RetainTileDataAction{ { 1, { 1, 0, 0 } } }, // + GetTileDataAction{ { 2, { 2, 0, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 0, 1 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 0 } }, false }, // + GetTileDataAction{ { 2, { 2, 1, 1 } }, false }, // + GetTileDataAction{ { 0, { 0, 0, 0 } }, true }, // + RetainTileDataAction{ { 0, { 0, 0, 0 } } }, // + RenderTileAction{ { 0, 1, 0 }, *tile_0_0_0_0 }, // }), - source.renderables); + log); } -- cgit v1.2.1