feat: add retained underlay revision invalidation
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace mbgl;
|
||||
@@ -15,6 +16,21 @@ RetainedUnderlayKey key(Size size = {640, 480}, int32_t boundary = 4) {
|
||||
result.cameraMatrix[15] = 1.0;
|
||||
return result;
|
||||
}
|
||||
|
||||
RetainedUnderlayRevisionState revisionState(Size size = {640, 480}, int32_t boundary = 4) {
|
||||
RetainedUnderlayRevisionState result;
|
||||
result.viewport = size;
|
||||
result.boundary = boundary;
|
||||
result.pixelRatio = 2.0f;
|
||||
result.contentRevision = 1;
|
||||
result.topologyRevision = 1;
|
||||
result.styleLoaded = true;
|
||||
result.renderTreeLoaded = true;
|
||||
result.staticStable = true;
|
||||
result.placementStable = true;
|
||||
result.cameraMatrix = key(size, boundary).cameraMatrix;
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(RetainedDynamicBoundary, SelectsFirstCandidate) {
|
||||
@@ -64,25 +80,206 @@ TEST(RetainedLayerGroupRange, PreservesForwardReverseOriginalOrdinals) {
|
||||
|
||||
TEST(RetainedUnderlayCache, BuildReuseAndInvalidation) {
|
||||
RetainedUnderlayCache cache;
|
||||
EXPECT_EQ(cache.evaluate(std::nullopt, false), RetainedUnderlayDecision::Disabled);
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(std::nullopt, 1, false), RetainedUnderlayDecision::Disabled);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_EQ(cache.getBuildRevision(), 1u);
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}), 2, true, nullptr, "viewport"), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "viewport");
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}, 6), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}, 6), 3, true, nullptr, "camera-or-boundary"),
|
||||
RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "camera-or-boundary");
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, MonotonicAcrossCameraViewportStyleAndSourceChanges) {
|
||||
RetainedUnderlayRevisionTracker tracker;
|
||||
RetainedUnderlayCache cache;
|
||||
auto state = revisionState();
|
||||
const auto currentKey = [&] {
|
||||
return RetainedUnderlayKey{state.viewport, *state.boundary, state.cameraMatrix};
|
||||
};
|
||||
EXPECT_EQ(tracker.observe(state), 1u);
|
||||
EXPECT_EQ(tracker.observe(state), 1u);
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.cameraMatrix[12] = 4.0;
|
||||
EXPECT_EQ(tracker.observe(state), 2u);
|
||||
EXPECT_EQ(tracker.reason(), "camera-or-boundary");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.viewport = {800, 600};
|
||||
EXPECT_EQ(tracker.observe(state), 3u);
|
||||
EXPECT_EQ(tracker.reason(), "viewport");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.pixelRatio = 3.0f;
|
||||
EXPECT_EQ(tracker.observe(state), 4u);
|
||||
EXPECT_EQ(tracker.reason(), "pixel-ratio");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "style-mutation"), 5u);
|
||||
EXPECT_EQ(tracker.reason(), "style-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "layer-mutation"), 6u);
|
||||
EXPECT_EQ(tracker.reason(), "layer-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "source-mutation"), 7u);
|
||||
EXPECT_EQ(tracker.reason(), "source-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "tile-mutation"), 8u);
|
||||
EXPECT_EQ(tracker.reason(), "tile-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.topologyRevision;
|
||||
EXPECT_EQ(tracker.observe(state), 9u);
|
||||
EXPECT_EQ(tracker.reason(), "layer-group-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, RepeatedFramesWithoutSemanticMutationDoNotAdvanceRevision) {
|
||||
RetainedUnderlayRevisionTracker tracker;
|
||||
auto state = revisionState();
|
||||
const auto settledRevision = tracker.observe(state);
|
||||
|
||||
// Transient drawable add/remove/rebuild and equivalent stencil assignment are
|
||||
// deliberately not inputs to the semantic revision state. Stable frames must
|
||||
// therefore keep the revision used by the retained build.
|
||||
for (int frame = 0; frame < 240; ++frame) {
|
||||
EXPECT_EQ(tracker.observe(state), settledRevision);
|
||||
}
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_GT(tracker.observe(state, "tile-mutation"), settledRevision);
|
||||
EXPECT_EQ(tracker.reason(), "tile-mutation");
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, EquivalentIntraFrameRemoveAddDoesNotAdvanceTopology) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
const RetainedUnderlayTopologySnapshot initial{
|
||||
.boundary = 32,
|
||||
.staticPrefix = {{3, "sea-area-fill", 1}, {31, "clip-outline", 1}},
|
||||
};
|
||||
const auto settledRevision = tracker.observe(initial);
|
||||
|
||||
// The request processor may temporarily remove a group and add an equivalent
|
||||
// replacement. Only the final post-processChanges snapshot is observed.
|
||||
auto finalTopology = initial;
|
||||
finalTopology.staticPrefix.erase(finalTopology.staticPrefix.begin());
|
||||
finalTopology.staticPrefix.insert(finalTopology.staticPrefix.begin(), {3, "sea-area-fill", 1});
|
||||
EXPECT_EQ(finalTopology, initial);
|
||||
EXPECT_EQ(tracker.observe(finalTopology), settledRevision);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, PersistentTopologyChangeAdvancesOnce) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
const RetainedUnderlayTopologySnapshot initial{
|
||||
.boundary = 32,
|
||||
.staticPrefix = {{3, "sea-area-fill", 1}, {31, "clip-outline", 1}},
|
||||
};
|
||||
const auto initialRevision = tracker.observe(initial);
|
||||
|
||||
auto changed = initial;
|
||||
changed.staticPrefix.pop_back();
|
||||
const auto changedRevision = tracker.observe(changed);
|
||||
EXPECT_GT(changedRevision, initialRevision);
|
||||
EXPECT_EQ(tracker.observe(changed), changedRevision);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, OrderingAndIndexChangesAdvanceTopology) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
RetainedUnderlayTopologySnapshot topology{
|
||||
.boundary = 32,
|
||||
.staticPrefix = {{3, "sea-area-fill", 1}, {31, "clip-outline", 1}},
|
||||
};
|
||||
const auto initialRevision = tracker.observe(topology);
|
||||
|
||||
std::swap(topology.staticPrefix[0], topology.staticPrefix[1]);
|
||||
const auto reorderedRevision = tracker.observe(topology);
|
||||
EXPECT_GT(reorderedRevision, initialRevision);
|
||||
|
||||
topology.staticPrefix[0].layerIndex = 30;
|
||||
const auto reindexedRevision = tracker.observe(topology);
|
||||
EXPECT_GT(reindexedRevision, reorderedRevision);
|
||||
EXPECT_EQ(tracker.observe(topology), reindexedRevision);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, MatchingBuildRevisionAllowsReuseAndMismatchRebuilds) {
|
||||
RetainedUnderlayCache cache;
|
||||
EXPECT_EQ(cache.evaluate(key(), 7, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.getBuildRevision(), 7u);
|
||||
EXPECT_EQ(cache.evaluate(key(), 7, true), RetainedUnderlayDecision::Reuse);
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key(), 8, true, nullptr, "tile-mutation"), RetainedUnderlayDecision::Build);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "tile-mutation");
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, PlacementAndFadeActivityRejectsStaleCacheUntilStable) {
|
||||
RetainedUnderlayRevisionTracker tracker;
|
||||
RetainedUnderlayCache cache;
|
||||
auto state = revisionState();
|
||||
auto revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.placementStable = false;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, false, "static-placement", tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
|
||||
state.placementStable = true;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.staticStable = false;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, false, "static-transition", tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
|
||||
state.staticStable = true;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayCache, UnsupportedAndResourceFailureFallBackSafely) {
|
||||
RetainedUnderlayCache cache;
|
||||
EXPECT_EQ(cache.evaluate(key(), false, "3d"), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, false, "3d"), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "3d");
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildFailed("resource-or-compositor");
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
EXPECT_EQ(cache.getFallbackCount(), 2u);
|
||||
@@ -93,14 +290,14 @@ TEST(RetainedUnderlayCache, RuntimeControlDisablesAndReenablesMapScopedCache) {
|
||||
RetainedUnderlayCache cache;
|
||||
cache.synchronizeRuntimeState(state);
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_TRUE(cache.isValid());
|
||||
|
||||
EXPECT_TRUE(state->setEnabled(false));
|
||||
cache.synchronizeRuntimeState(state);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Fallback);
|
||||
auto disabled = state->snapshot();
|
||||
EXPECT_FALSE(disabled.enabled);
|
||||
EXPECT_EQ(disabled.build, 1u);
|
||||
@@ -110,6 +307,6 @@ TEST(RetainedUnderlayCache, RuntimeControlDisablesAndReenablesMapScopedCache) {
|
||||
|
||||
EXPECT_TRUE(state->setEnabled(true));
|
||||
cache.synchronizeRuntimeState(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_TRUE(state->snapshot().enabled);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user