fix: localize retained segment source invalidation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-10-06 17:18:09 +09:00
parent c4b481fd2c
commit 524aaf4af5
16 changed files with 1736 additions and 71 deletions

View File

@@ -957,6 +957,7 @@ TEST(RenderSegmentDiagnostics, ReadinessSnapshotMapsSourcesToSegmentsWithoutSide
state.recordRenderSegmentResolution(resolved, layers);
state.recordSegmentBuild(0);
state.recordSegmentCacheState(0, RetainedRangeCacheState::Ready, 41);
state.recordSegmentExecution(0, true, true, true, 17, "reuse", nullptr);
RendererReadinessDiagnostics readiness;
readiness.styleLoaded = true;
@@ -1029,6 +1030,8 @@ TEST(RenderSegmentDiagnostics, ReadinessSnapshotMapsSourcesToSegmentsWithoutSide
ASSERT_TRUE(second.segments[0].cache);
EXPECT_EQ(second.segments[0].cache->build, cacheBuild);
EXPECT_EQ(second.segments[0].cache->contentRevision, 41u);
EXPECT_EQ(second.segments[0].cache->sourceRevision, 17u);
EXPECT_EQ(second.segments[0].cache->finalDecision, "reuse");
const auto third = state.renderSegmentSnapshot();
EXPECT_EQ(third.topologyRevision, second.topologyRevision);
EXPECT_EQ(third.contentRevision, second.contentRevision);
@@ -1041,6 +1044,109 @@ TEST(RenderSegmentDiagnostics, ReadinessSnapshotMapsSourcesToSegmentsWithoutSide
EXPECT_NE(json.find("\"incompleteTileCount\":1"), std::string::npos);
EXPECT_NE(json.find("\"dependentSourceIds\":[\"dynamic\"]"), std::string::npos);
EXPECT_NE(json.find("\"affectedSegmentIndices\":[1]"), std::string::npos);
EXPECT_NE(json.find("\"sourceRevision\":17"), std::string::npos);
}
TEST(RenderSegmentLocality, SourceRevisionsAreScopedAndRecreationUsesNewIdentity) {
const std::vector<StyleLayerDiagnostics> layers{
{0, "background", "background", true, std::nullopt, std::nullopt},
{1, "a0", "fill", true, "a", std::nullopt},
{2, "b", "circle", true, "b", std::nullopt},
{3, "a1", "line", true, "a", std::nullopt}};
const auto r0 = renderSegmentDependencies(layers, 0, 2);
const auto r1 = renderSegmentDependencies(layers, 2, 3);
const auto r2 = renderSegmentDependencies(layers, 3, 4);
const auto sourceLess = renderSegmentDependencies(layers, 0, 1);
EXPECT_EQ(r0.sourceIds, std::vector<std::string>({"a"}));
EXPECT_EQ(r2.sourceIds, r0.sourceIds);
EXPECT_TRUE(sourceLess.sourceIds.empty());
RenderSegmentRevisionBook book;
book.advanceSource("a");
book.advanceSource("b");
const auto beforeA = book.revisionFor(r0);
const auto beforeB = book.revisionFor(r1);
const auto beforeA2 = book.revisionFor(r2);
const auto beforeSourceLess = book.revisionFor(sourceLess);
book.advanceSource("a");
EXPECT_NE(book.revisionFor(r0), beforeA);
EXPECT_NE(book.revisionFor(r2), beforeA2);
EXPECT_EQ(book.revisionFor(r1), beforeB);
EXPECT_EQ(book.revisionFor(sourceLess), beforeSourceLess);
const auto beforeRecreate = book.revisionFor(r0);
book.advanceSource("a"); // removal
book.advanceSource("a"); // recreation with the same ID
EXPECT_NE(book.revisionFor(r0), beforeRecreate);
book.advanceGlobal(); // image, glyph, style, context or unknown mutation
EXPECT_NE(book.revisionFor(r1), beforeB);
EXPECT_NE(book.revisionFor(sourceLess), beforeSourceLess);
const auto unknown = renderSegmentDependencies(
{{0, "custom", "custom", true, std::nullopt, std::nullopt}}, 0, 1);
EXPECT_TRUE(unknown.unknown);
EXPECT_FALSE(book.revisionFor(unknown));
const auto symbol = renderSegmentDependencies(
{{0, "label", "symbol", true, "a", std::nullopt}}, 0, 1);
EXPECT_FALSE(symbol.unknown);
EXPECT_EQ(symbol.sourceIds, std::vector<std::string>({"a"}));
EXPECT_EQ(symbol.globalPrerequisites,
std::vector<std::string>({"symbol/glyph/image/placement"}));
}
TEST(RenderSegmentLocality, ReadinessAndFadeFollowOnlyDependencies) {
const RenderSegmentDependencies r0{{"static"}, {}, false};
const RenderSegmentDependencies r1{{"dynamic"}, {}, false};
const auto lookup = [](bool dynamicLoaded, bool dynamicFading) {
return [=](const std::string& id) -> std::optional<RenderSegmentSourceStatus> {
if (id == "static") return RenderSegmentSourceStatus{true, false};
if (id == "dynamic") return RenderSegmentSourceStatus{dynamicLoaded, dynamicFading};
return std::nullopt;
};
};
EXPECT_TRUE(assessRenderSegmentSources(r0, lookup(false, false)).stable);
EXPECT_FALSE(assessRenderSegmentSources(r1, lookup(false, false)).ready);
EXPECT_TRUE(assessRenderSegmentSources(r0, lookup(true, true)).stable);
const auto fading = assessRenderSegmentSources(r1, lookup(true, true));
EXPECT_TRUE(fading.ready);
EXPECT_FALSE(fading.stable);
EXPECT_STREQ(fading.reason, "source-fading");
EXPECT_FALSE(assessRenderSegmentSources({{"missing"}, {}, false}, lookup(true, false)).ready);
EXPECT_FALSE(assessRenderSegmentSources({{}, {}, true}, lookup(true, false)).ready);
}
TEST(RenderSegmentLocality, MixedExecutionDrawsEachLiveRangeOnceInStyleOrder) {
ResolvedRenderSegmentPlan plan;
plan.valid = true;
plan.layerCount = 5;
plan.segments = {{0, RenderSegmentStrategy::Retained, std::nullopt, 0, 1},
{1, RenderSegmentStrategy::Continuous, std::nullopt, 1, 3},
{2, RenderSegmentStrategy::Retained, std::nullopt, 3, 5}};
std::vector<std::string> events;
visitRenderSegmentExecution(plan,
[](uint32_t index) { return index == 0; },
[&](uint32_t index) { events.push_back("composite:" + std::to_string(index)); },
[&](int32_t begin, int32_t end) {
for (auto i = begin; i < end; ++i) events.push_back("live:" + std::to_string(i));
});
EXPECT_EQ(events, (std::vector<std::string>{"composite:0", "live:1", "live:2", "live:3", "live:4"}));
events.clear();
plan.valid = false;
visitRenderSegmentExecution(plan, [](uint32_t) { return true; },
[&](uint32_t) { ADD_FAILURE() << "invalid plan composite"; },
[&](int32_t begin, int32_t end) {
for (auto i = begin; i < end; ++i) events.push_back("live:" + std::to_string(i));
});
EXPECT_EQ(events.size(), 5u);
}
TEST(RenderSegmentLocality, FailedBuildCannotReuseOldTexture) {
RetainedUnderlayCache cache;
const auto viewportKey = key();
EXPECT_EQ(cache.evaluate(viewportKey, 7, true), RetainedUnderlayDecision::Build);
cache.markBuildSucceeded();
EXPECT_EQ(cache.evaluate(viewportKey, 7, true), RetainedUnderlayDecision::Reuse);
EXPECT_EQ(cache.evaluate(viewportKey, 8, true), RetainedUnderlayDecision::Build);
cache.markBuildFailed("resource-or-compositor");
EXPECT_FALSE(cache.isValid());
EXPECT_EQ(cache.evaluate(viewportKey, 8, true), RetainedUnderlayDecision::Build);
}
TEST(RenderSegmentPlan, StrictJSONRejectsUnknownFieldsStrategyAndInvalidFirstBoundary) {