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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user