summaryrefslogtreecommitdiff
path: root/test/algorithm/update_renderables.test.cpp
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2016-09-28 11:45:33 +0200
committerKonstantin Käfer <mail@kkaefer.com>2016-09-28 16:34:22 +0200
commit3f3fc7b7723698e44427e2a14a2f4906832800bf (patch)
tree5acadfa4d77817c41f612c89c93925a149cbcfc0 /test/algorithm/update_renderables.test.cpp
parenta8b007daa0e85ea4b1a4898fd591d55d0117cc85 (diff)
downloadqtlocation-mapboxgl-3f3fc7b7723698e44427e2a14a2f4906832800bf.tar.gz
[test] add .test.cpp suffix to test case files
Diffstat (limited to 'test/algorithm/update_renderables.test.cpp')
-rw-r--r--test/algorithm/update_renderables.test.cpp1228
1 files changed, 1228 insertions, 0 deletions
diff --git a/test/algorithm/update_renderables.test.cpp b/test/algorithm/update_renderables.test.cpp
new file mode 100644
index 0000000000..c284c37475
--- /dev/null
+++ b/test/algorithm/update_renderables.test.cpp
@@ -0,0 +1,1228 @@
+#include "mock.hpp"
+#include <mbgl/test/util.hpp>
+#include <mbgl/util/variant.hpp>
+
+#include <mapbox/variant_io.hpp>
+
+#include <mbgl/algorithm/update_renderables.hpp>
+
+using namespace mbgl;
+
+enum LookupResult : bool {
+ NotFound = false,
+ Found = true,
+};
+
+struct GetTileDataAction {
+ const OverscaledTileID tileID;
+ const LookupResult 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 == Found ? "Found" : "NotFound") << " }";
+}
+
+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;
+ const Resource::Necessity necessity;
+
+ bool operator==(const RetainTileDataAction& rhs) const {
+ return tileID == rhs.tileID && necessity == rhs.necessity;
+ }
+};
+
+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 << " } }, "
+ << (action.necessity == Resource::Necessity::Required ? "Required" : "Optional") << " }";
+}
+
+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<GetTileDataAction, CreateTileDataAction, RetainTileDataAction, RenderTileAction>;
+using ActionLog = std::vector<ActionLogEntry>;
+
+template <typename T>
+auto getTileDataFn(ActionLog& log, const T& dataTiles) {
+ return [&](const auto& id) {
+ auto it = dataTiles.find(id);
+ log.emplace_back(GetTileDataAction{ id, it != dataTiles.end() ? Found : NotFound });
+ return (it != dataTiles.end()) ? it->second.get() : nullptr;
+ };
+}
+
+template <typename T>
+auto createTileDataFn(ActionLog& log, T& dataTiles) {
+ return [&](const auto& id) {
+ log.emplace_back(CreateTileDataAction{ id });
+ return (dataTiles[id] = std::make_unique<MockTileData>(id)).get();
+ };
+}
+
+auto retainTileDataFn(ActionLog& log) {
+ return [&](auto& tileData, Resource::Necessity necessity) {
+ log.emplace_back(RetainTileDataAction{ tileData.tileID, necessity });
+ };
+}
+
+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_1 = source.createTileData(OverscaledTileID{ 1, 1, 1 });
+ tile_1_1_1_1->renderable = true;
+
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile
+ }),
+ log);
+
+ // Check a repeated render with the same data.
+ log.clear();
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile
+ }),
+ log);
+
+ // Insert a tile we don't have data for.
+ log.clear();
+ source.idealTiles.emplace(UnwrappedTileID{ 1, 0, 1 });
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, NotFound }, // missing ideal tile
+ CreateTileDataAction{ { 1, { 1, 0, 1 } } }, // create ideal tile
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // four child tiles
+ GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile
+
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile
+ }),
+ log);
+
+ // Mark the created tile as having the optional request tried.
+ log.clear();
+ source.dataTiles[{ 1, { 1, 0, 1 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // missing ideal tile
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // four child tiles
+ GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile
+ CreateTileDataAction{ { 0, { 0, 0, 0 } } }, // load parent tile
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // found ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile
+ }),
+ log);
+
+ // Now insert the missing tile and check that we're rendering it.
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // newly added tile
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile
+
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile
+ }),
+ 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_1_0_0 = source.createTileData(OverscaledTileID{ 1, 0, 0 });
+
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // found tile, not ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // four child tiles
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ // optional parent tile was already created before, but is not renderable
+
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // ideal tile
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile
+
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile
+ }),
+ log);
+
+ // Then, add the bucket and check that it's getting used.
+ log.clear();
+ tile_1_1_0_0->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // found tile, now ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, //
+
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // ideal tile
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, //
+
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, Found }, // ideal tile
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ 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_0 = source.createTileData(OverscaledTileID{ 0, 0, 0 });
+ tile_0_0_0_0->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, NotFound }, // missing ideal tile
+ CreateTileDataAction{ { 1, { 1, 0, 1 } } }, //
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // child tile
+ GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent found!
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent
+ GetTileDataAction{ { 1, { 1, 1, 0 } }, NotFound }, // missing ideal tile
+ CreateTileDataAction{ { 1, { 1, 1, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 2, { 2, 2, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 3, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 3, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, NotFound }, // missing tile
+ CreateTileDataAction{ { 1, { 1, 1, 1 } } }, //
+ RetainTileDataAction{ { 1, { 1, 1, 1 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 2, 2 } }, NotFound }, // child tile
+ GetTileDataAction{ { 2, { 2, 2, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 3, 2 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 3, 3 } }, NotFound }, // ...
+ }),
+ 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_1_0 = source.createTileData(OverscaledTileID{ 1, 1, 0 });
+ tile_1_1_1_0->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // missing ideal tile
+ CreateTileDataAction{ { 2, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // parent tile, missing
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // parent tile, missing
+ }),
+ log);
+
+ // Now mark the created tile as having the optional request tried.
+ log.clear();
+ source.dataTiles[{ 2, { 2, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // non-ready ideal tile
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // parent tile, missing
+ CreateTileDataAction{ { 1, { 1, 0, 0 } } }, // find optional parent
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // 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 });
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // non-ready ideal tile
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ // this tile was added by the previous invocation of updateRenderables
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // parent tile not ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // missing parent tile
+
+ GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, // missing ideal tile
+ CreateTileDataAction{ { 2, { 2, 2, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 2, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 3, 4, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 3, { 3, 4, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 5, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 5, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 1, 0 } }, Found }, // found parent tile
+ RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render parent tile
+ }),
+ 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_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 });
+ // Don't create bucket.
+
+ // Make sure that it renders the parent tile.
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // found, but not ready
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 2 } }, NotFound }, // child tile
+ GetTileDataAction{ { 2, { 2, 0, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 2 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 3 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, ready
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent tile
+ }),
+ log);
+
+ // Now insert the bucket and make sure we're now using the matching tile
+ log.clear();
+ tile_1_1_0_1->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // found and ready
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile
+ }),
+ 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_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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile not found
+ CreateTileDataAction{ { 1, { 1, 0, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile found
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, //
+
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, Found }, // ideal tile found
+ RetainTileDataAction{ { 1, { 1, 0, 1 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, //
+ }),
+ log);
+}
+
+TEST(UpdateRenderables, UseChildTiles) {
+ 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{ 0, 0, 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.zoomRange, 0);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ideal tile, missing
+ CreateTileDataAction{ { 0, { 0, 0, 0 } } }, //
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // child tile found
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // render child tile
+ GetTileDataAction{ { 1, { 1, 0, 1 } }, NotFound }, // child tile not found
+ GetTileDataAction{ { 1, { 1, 1, 0 } }, Found }, // child tile found
+ RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render child tile
+ GetTileDataAction{ { 1, { 1, 1, 1 } }, NotFound }, // child tile not found
+ // no parent tile of 0 to consider
+ }),
+ log);
+}
+
+TEST(UpdateRenderables, PreferChildTiles) {
+ 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 });
+
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile, not found
+ CreateTileDataAction{ { 1, { 1, 0, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, // child tile, not found
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, found
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, //
+ }),
+ 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.
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready
+ // ideal tile was added in previous invocation, but is not yet ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, Found }, // ...
+ RetainTileDataAction{ { 2, { 2, 0, 1 } }, Resource::Necessity::Optional }, // ...
+ RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, // child tile, not found
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, found
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready
+ // ideal tile was added in first invocation, but is not yet ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, Found }, // ...
+ RetainTileDataAction{ { 2, { 2, 0, 1 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 2, { 2, 1, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, //
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, // child tile, not found
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile, found
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ 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.
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready
+ // ideal tile was added in first invocation, but is not yet ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile, found
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, Found }, // ...
+ RetainTileDataAction{ { 2, { 2, 0, 1 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 2, { 2, 1, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, //
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, Found }, // ...
+ RetainTileDataAction{ { 2, { 2, 1, 1 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 1, 1 }, *tile_2_2_1_1 }, //
+ }),
+ log);
+}
+
+TEST(UpdateRenderables, UseParentAndChildTiles) {
+ 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 });
+
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile, missing
+ CreateTileDataAction{ { 1, { 1, 0, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // child tile
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, //
+ }),
+ log);
+
+ // Then, remove the child tile and check that it now only the parent tile.
+ log.clear();
+ source.dataTiles.erase(OverscaledTileID{ 2, 0, 0 });
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, // parent tile
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, //
+ }),
+ 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.zoomRange.min = 2;
+ source.idealTiles.emplace(UnwrappedTileID{ 2, 0, 0 });
+
+ 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.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ideal tile, missing
+ CreateTileDataAction{ { 2, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, //
+ GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, //
+ GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, //
+ // 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.zoomRange.max = 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->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(
+ ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ideal tile, missing
+ CreateTileDataAction{ { 2, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children!
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, //
+ }),
+ log);
+
+ // Mark the created tile as having tried the optional request.
+ log.clear();
+ source.dataTiles[{ 2, { 2, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(
+ ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // ideal tile, missing
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children!
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, //
+ CreateTileDataAction{ { 1, { 1, 0, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, //
+ }),
+ 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->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 3);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, // ideal tile, missing
+ CreateTileDataAction{ { 3, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ }),
+ 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->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 3);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, //
+ }),
+ log);
+
+ // Check that it's switching back to the tile that has the matching overzoom value.
+ log.clear();
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ }),
+ 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.
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, //
+ CreateTileDataAction{ { 2, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // use overzoomed tile!
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, //
+ }),
+ 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.zoomRange.max = 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->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 3);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, //
+ }),
+ 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->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 3);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, NotFound }, //
+ CreateTileDataAction{ { 3, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, // prefer using a child first
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, //
+ }),
+ 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->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 3);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, //
+ }),
+ log);
+
+ // Now, mark the created tile as found.
+ log.clear();
+ source.dataTiles[{ 3, { 2, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 3);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 3, { 2, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 3, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 4, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, //
+ CreateTileDataAction{ { 2, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ 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.zoomRange, 8);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 8, { 8, 0, 0 } }, NotFound }, // ideal tile
+ CreateTileDataAction{ { 8, { 8, 0, 0 } } }, //
+ RetainTileDataAction{ { 8, { 8, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 9, { 9, 0, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 9, { 9, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // ascent
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+
+ GetTileDataAction{ { 8, { 8, 1, 0 } }, NotFound }, // ideal tile
+ CreateTileDataAction{ { 8, { 8, 1, 0 } } }, //
+ RetainTileDataAction{ { 8, { 8, 1, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 9, { 9, 2, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 9, { 9, 2, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 3, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 3, 1 } }, NotFound }, // ...
+ // 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.zoomRange, 8);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 8, { 8, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 8, { 8, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 9, { 9, 0, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 9, { 9, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // ascent
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // stops ascent
+ RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 4, 0, 0 }, *tile_4_0_0_0 }, //
+
+ GetTileDataAction{ { 8, { 8, 1, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 8, { 8, 1, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 9, { 9, 2, 0 } }, NotFound }, // child tile
+ GetTileDataAction{ { 9, { 9, 2, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 3, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 9, { 9, 3, 1 } }, NotFound }, // ...
+ // 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.zoomRange, 2);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 3, { 3, 0, 1 } }, NotFound }, //
+ GetTileDataAction{ { 3, { 3, 1, 0 } }, NotFound }, //
+ GetTileDataAction{ { 3, { 3, 1, 1 } }, NotFound }, //
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // parent tile, not ready
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, //
+ }),
+ 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.zoomRange, 1);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 1, { 1, 1, 0 } }, NotFound }, // ideal tile 1/-1/0
+ CreateTileDataAction{ { 1, { 1, 1, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 2, 1 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 3, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 3, 1 } }, NotFound }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, -1, 0 }, *tile_0_0_0_0 }, //
+
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ideal tile 1/0/0
+ CreateTileDataAction{ { 1, { 1, 0, 0 } } }, //
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, //
+
+ GetTileDataAction{ { 1, { 1, 1, 0 } }, Found }, // ideal tile 1/1/0
+ RetainTileDataAction{ { 1, { 1, 1, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 2, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 2, 1 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 3, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 3, 1 } }, NotFound }, //
+ // do not ascent; 0/0/0 has been rendered already for 1/0/0
+
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, Found }, // ideal tile 1/2/0
+ RetainTileDataAction{ { 1, { 1, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 0, 1 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 0 } }, NotFound }, //
+ GetTileDataAction{ { 2, { 2, 1, 1 } }, NotFound }, //
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, Found }, //
+ RetainTileDataAction{ { 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 0, 1, 0 }, *tile_0_0_0_0 }, //
+ }),
+ log);
+}
+
+TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) {
+ 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{ 6, 0, 0 });
+
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, NotFound }, // ideal tile, not found
+ CreateTileDataAction{ { 6, { 6, 0, 0 } } }, //
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Repeat.
+ log.clear();
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Mark next level has having tried optional.
+ log.clear();
+ source.dataTiles[{ 6, { 6, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, NotFound }, // ascent
+ CreateTileDataAction{ { 5, { 5, 0, 0 } } }, //
+ RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Repeat.
+ log.clear();
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent
+ RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Mark next level has having tried optional.
+ log.clear();
+ source.dataTiles[{ 5, { 5, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent
+ RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, NotFound }, // ...
+ CreateTileDataAction{ { 4, { 4, 0, 0 } } }, //
+ RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Mark next level has having tried optional.
+ log.clear();
+ source.dataTiles[{ 4, { 4, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent
+ RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, NotFound }, // ...
+ CreateTileDataAction{ { 3, { 3, 0, 0 } } }, //
+ RetainTileDataAction{ { 3, { 3, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Mark next level has having tried optional.
+ log.clear();
+ source.dataTiles[{ 3, { 3, 0, 0 } }]->triedOptional = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent
+ RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 3, { 3, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 2, { 2, 0, 0 } }, NotFound }, // ...
+ CreateTileDataAction{ { 2, { 2, 0, 0 } } }, //
+ RetainTileDataAction{ { 2, { 2, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 1, { 1, 0, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 0, { 0, 0, 0 } }, NotFound }, // ...
+ }),
+ log);
+
+ // Mark as found
+ log.clear();
+ auto tile_3_3_0_0 = source.dataTiles[{ 3, { 3, 0, 0 } }].get();
+ tile_3_3_0_0->renderable = true;
+ algorithm::updateRenderables(getTileData, createTileData, retainTileData, renderTile,
+ source.idealTiles, source.zoomRange, 6);
+ EXPECT_EQ(ActionLog({
+ GetTileDataAction{ { 6, { 6, 0, 0 } }, Found }, // ideal tile, not ready
+ RetainTileDataAction{ { 6, { 6, 0, 0 } }, Resource::Necessity::Required }, //
+ GetTileDataAction{ { 7, { 7, 0, 0 } }, NotFound }, // children
+ GetTileDataAction{ { 7, { 7, 0, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 0 } }, NotFound }, // ...
+ GetTileDataAction{ { 7, { 7, 1, 1 } }, NotFound }, // ...
+ GetTileDataAction{ { 5, { 5, 0, 0 } }, Found }, // ascent
+ RetainTileDataAction{ { 5, { 5, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 4, { 4, 0, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 4, { 4, 0, 0 } }, Resource::Necessity::Optional }, //
+ GetTileDataAction{ { 3, { 3, 0, 0 } }, Found }, // ...
+ RetainTileDataAction{ { 3, { 3, 0, 0 } }, Resource::Necessity::Optional }, //
+ RenderTileAction{ { 3, 0, 0 }, *tile_3_3_0_0 }, //
+ }),
+ log);
+}