Add extra observer hooks for interesting internal processes (#2694)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
mwilsnd
2024-09-03 17:48:03 -04:00
committed by GitHub
parent b90b2df920
commit bb34568af2
93 changed files with 1951 additions and 177 deletions

View File

@@ -1,3 +1,5 @@
#include <gmock/gmock.h>
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_file_source.hpp>
#include <mbgl/test/stub_map_observer.hpp>
@@ -28,6 +30,7 @@
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/style_impl.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/client_options.hpp>
#include <mbgl/util/color.hpp>
@@ -1684,3 +1687,162 @@ TEST(Map, InvalidUTF8InTile) {
test.runLoop.run();
}
TEST(Map, ObserveTileLifecycle) {
util::RunLoop runLoop;
struct TileEntry {
OverscaledTileID id;
std::string sourceID;
TileOperation op;
};
std::mutex tileMutex;
std::vector<TileEntry> tileOps;
struct ShaderEntry {
shaders::BuiltIn id;
gfx::Backend::Type type;
std::string defines;
bool isPostCompile;
};
std::vector<ShaderEntry> shaderOps;
StubMapObserver observer;
observer.onTileActionCallback = [&](TileOperation op, const OverscaledTileID& id, const std::string& sourceID) {
if (sourceID != "mapbox") return;
std::lock_guard<std::mutex> lock(tileMutex);
tileOps.push_back(TileEntry{id, sourceID, op});
};
observer.onPreCompileShaderCallback =
[&](shaders::BuiltIn id, gfx::Backend::Type type, const std::string& additionalDefines) {
shaderOps.push_back(ShaderEntry{id, type, additionalDefines, false});
};
observer.onPostCompileShaderCallback =
[&](shaders::BuiltIn id, gfx::Backend::Type type, const std::string& additionalDefines) {
shaderOps.push_back(ShaderEntry{id, type, additionalDefines, true});
};
HeadlessFrontend frontend{{512, 512}, 1};
MapAdapter map(
frontend,
observer,
std::make_shared<MainResourceLoader>(
ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets"), ClientOptions()),
MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize()));
map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json"));
auto layer = std::make_unique<FillLayer>("landcover", "mapbox");
layer->setSourceLayer("landcover");
layer->setFillColor(Color{0.0, 1.0, 0.0, 1.0});
map.getStyle().addLayer(std::move(layer));
map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.5}).withZoom(10.0));
(void)frontend.render(map);
// We expect to see a valid shader lifecycle for every entry in this list.
const std::vector<std::pair<shaders::BuiltIn, size_t>> expectedShaders = {
{shaders::BuiltIn::FillShader, 16114602744458825542ULL},
{shaders::BuiltIn::FillOutlineShader, 16114602744458825542ULL},
};
for (const auto& [id, defineHash] : expectedShaders) {
bool seenPreEvent = false;
for (const auto& op : shaderOps) {
if (op.id != id || std::hash<std::string>()(op.defines) != defineHash) {
continue;
}
if (!seenPreEvent) {
EXPECT_EQ(op.isPostCompile, false);
seenPreEvent = true;
} else {
EXPECT_EQ(op.isPostCompile, true);
break;
}
}
}
// We expect to see a valid lifecycle for every tile in this list.
const std::vector<OverscaledTileID> expectedTiles = {
{10, 0, 10, 163, 395}, {10, 0, 10, 163, 396}, {10, 0, 10, 164, 395}, {10, 0, 10, 164, 396},
// Lower zooms can also be seen, but not always, so we
// ignore them.
};
for (const auto& tile : expectedTiles) {
TileOperation stage = TileOperation::NullOp;
for (const auto& op : tileOps) {
if (op.id != tile) continue;
switch (op.op) {
case TileOperation::RequestedFromCache: {
EXPECT_EQ(stage, TileOperation::NullOp);
stage = TileOperation::RequestedFromCache;
break;
}
case TileOperation::RequestedFromNetwork: {
EXPECT_THAT(
stage,
testing::AnyOf(
TileOperation::StartParse, TileOperation::EndParse, TileOperation::RequestedFromCache));
stage = TileOperation::RequestedFromNetwork;
break;
}
case TileOperation::LoadFromNetwork: {
EXPECT_THAT(stage, TileOperation::RequestedFromNetwork);
stage = TileOperation::LoadFromNetwork;
break;
}
case TileOperation::LoadFromCache: {
EXPECT_EQ(stage, TileOperation::RequestedFromCache);
stage = TileOperation::LoadFromCache;
break;
}
case TileOperation::StartParse: {
EXPECT_THAT(stage, testing::AnyOf(TileOperation::LoadFromNetwork, TileOperation::LoadFromCache));
stage = TileOperation::StartParse;
break;
}
case TileOperation::Cancelled: {
EXPECT_THAT(stage,
testing::AnyOf(TileOperation::RequestedFromCache,
TileOperation::RequestedFromNetwork,
TileOperation::LoadFromNetwork,
TileOperation::LoadFromCache,
TileOperation::StartParse));
stage = TileOperation::Cancelled;
break;
}
case TileOperation::EndParse: {
// The following are both possible:
// * RequestedFromCache -> LoadFromCache -> StartParse -> RequestedFromNetwork -> EndParse ->
// LoadFromNetwork -> StartParse -> EndParse
// The parsing stage is initiated by the cached version. The network request doesn't arrive
// until after the cached version has completed parsing. A new parsing task is spawned, resulting
// in a pair of Start and EndParse events.
//
// * RequestedFromCache -> LoadFromCache -> StartParse ->
// RequestedFromNetwork -> LoadFromNetwork -> EndParse
// The parsing stage is initiated by the cached version before being interrupted
// by data from a new version downloaded over the network. This interruption doesn't cancel the
// ongoing parsing task, so ::EndParse will end up being emitted only once by completion of the
// newer tile's data.
EXPECT_THAT(stage,
testing::AnyOf(TileOperation::StartParse,
TileOperation::LoadFromNetwork,
TileOperation::RequestedFromNetwork));
stage = TileOperation::EndParse;
break;
}
case TileOperation::NullOp:
[[fallthrough]];
case TileOperation::Error: {
ADD_FAILURE();
break;
}
}
}
EXPECT_THAT(stage, testing::AnyOf(TileOperation::EndParse, TileOperation::Cancelled));
}
}