feat(step102): isolate placement-free retained segments
This commit is contained in:
@@ -1157,6 +1157,149 @@ TEST(RenderSegmentLocality, FailedBuildCannotReuseOldTexture) {
|
||||
EXPECT_EQ(cache.evaluate(viewportKey, 8, true), RetainedUnderlayDecision::Build);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlacementFree, StructuralCapabilityAndConservativeGate) {
|
||||
const std::vector<StyleLayerDiagnostics> layers{
|
||||
{0, "background", "background", true, std::nullopt, std::nullopt},
|
||||
{1, "particle", "raster-particle", true, "field", std::nullopt},
|
||||
{2, "label", "symbol", true, "chart", std::nullopt}};
|
||||
EXPECT_FALSE(renderSegmentHasPlacementCapableLayer(layers, 0, 1));
|
||||
EXPECT_TRUE(renderSegmentHasPlacementCapableLayer(layers, 2, 3));
|
||||
// The symbol's current placementData may be empty; style structure still wins.
|
||||
EXPECT_TRUE(renderSegmentHasPlacementCapableLayer(layers, 2, 3));
|
||||
EXPECT_TRUE(renderSegmentHasPlacementCapableLayer(layers, -1, 1));
|
||||
EXPECT_TRUE(renderSegmentPlacementGatePassed(false, true, 1.0f, true));
|
||||
EXPECT_FALSE(renderSegmentPlacementGatePassed(true, true, 0.0f, false));
|
||||
EXPECT_FALSE(renderSegmentPlacementGatePassed(true, false, 1.0f, false));
|
||||
EXPECT_FALSE(renderSegmentPlacementGatePassed(true, false, 0.0f, true));
|
||||
EXPECT_TRUE(renderSegmentPlacementGatePassed(true, false, 0.0f, false));
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlacementFree, MixedExecutionKeepsFreeRangesInStyleOrder) {
|
||||
ResolvedRenderSegmentPlan plan;
|
||||
plan.valid = true;
|
||||
plan.layerCount = 5;
|
||||
plan.segments = {{0, RenderSegmentStrategy::Retained, std::nullopt, 0, 1},
|
||||
{1, RenderSegmentStrategy::Continuous, std::nullopt, 1, 2},
|
||||
{2, RenderSegmentStrategy::Retained, std::nullopt, 2, 3},
|
||||
{3, RenderSegmentStrategy::Continuous, std::nullopt, 3, 4},
|
||||
{4, RenderSegmentStrategy::Retained, std::nullopt, 4, 5}};
|
||||
RetainedUnderlayCache r0, r1, r2;
|
||||
const auto k0 = key({640, 480}, 1);
|
||||
const auto k1 = key({640, 480}, 3);
|
||||
const auto k2 = key({640, 480}, 5);
|
||||
EXPECT_EQ(r0.evaluate(k0, 1, true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(r1.evaluate(k1, 1, true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(r2.evaluate(k2, 1, true), RetainedUnderlayDecision::Build);
|
||||
r0.markBuildSucceeded(); r1.markBuildSucceeded(); r2.markBuildSucceeded();
|
||||
const bool freeGate = renderSegmentPlacementGatePassed(false, true, 1.0f, true);
|
||||
const bool symbolGate = renderSegmentPlacementGatePassed(true, true, 1.0f, true);
|
||||
EXPECT_EQ(r0.evaluate(k0, 1, freeGate), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_EQ(r1.evaluate(k1, 1, symbolGate, "static-placement"), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(r1.getLastInvalidationReason(), "static-placement");
|
||||
EXPECT_EQ(r2.evaluate(k2, 1, freeGate), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_NE(r0.getLastInvalidationReason(), "static-placement");
|
||||
EXPECT_NE(r2.getLastInvalidationReason(), "static-placement");
|
||||
std::vector<std::string> events;
|
||||
visitRenderSegmentExecution(plan,
|
||||
[](uint32_t index) { return index == 0 || index == 4; },
|
||||
[&](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", "composite:4"}));
|
||||
// Source-local mutation/fade in R1 leaves both independent retained caches reusable.
|
||||
EXPECT_EQ(r0.evaluate(k0, 1, freeGate), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_EQ(r2.evaluate(k2, 1, freeGate), RetainedUnderlayDecision::Reuse);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlacementFree, RealGlobalGatesStillForceGlobalLive) {
|
||||
RenderSegmentGlobalLiveGates gates{true, false, false, true, true, true, false};
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "none");
|
||||
gates.has3D = true;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "3d");
|
||||
gates.has3D = false; gates.debugOptions = true;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "debug-options");
|
||||
gates.debugOptions = false; gates.continuousMode = false;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "map-mode");
|
||||
gates.continuousMode = true; gates.spriteLoaded = false;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "sprite-not-loaded");
|
||||
gates.spriteLoaded = true; gates.globalResourcesReady = false;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "global-resource-not-ready");
|
||||
gates.globalResourcesReady = true; gates.globalLightTransition = true;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "global-light-transition");
|
||||
gates.globalLightTransition = false; gates.retainedMode = false;
|
||||
EXPECT_STREQ(renderSegmentGlobalLiveFallbackReason(gates), "no-boundary");
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlacementFree, TopologyAndCameraChangesCannotReuseOldRetainedKey) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
RetainedUnderlayTopologySnapshot topology;
|
||||
topology.staticPrefix = {{0, "RETAINED:1", 0}, {1, "CONTINUOUS:2", 1},
|
||||
{2, "RETAINED:3", 0}, {3, "CONTINUOUS:4", 1}};
|
||||
const auto firstRevision = tracker.observe(topology);
|
||||
RetainedUnderlayCache cache;
|
||||
const auto firstKey = key({640, 480}, 3);
|
||||
EXPECT_EQ(cache.evaluate(firstKey, firstRevision, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.evaluate(firstKey, firstRevision, true), RetainedUnderlayDecision::Reuse);
|
||||
topology.staticPrefix.resize(2); // R-C-R-C becomes R-C.
|
||||
EXPECT_GT(tracker.observe(topology), firstRevision);
|
||||
EXPECT_EQ(cache.evaluate(firstKey, tracker.current(), true, nullptr, "layer-group-mutation"),
|
||||
RetainedUnderlayDecision::Build);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.evaluate(key({640, 480}, 4), tracker.current(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlacementFree, DiagnosticsRecordActualMainBufferAndDoNotMutateOnRead) {
|
||||
RetainedUnderlayRuntimeState state;
|
||||
RenderSegmentPlan configured;
|
||||
configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt},
|
||||
{RenderSegmentStrategy::Continuous, "particle"},
|
||||
{RenderSegmentStrategy::Retained, "label"}};
|
||||
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
|
||||
const std::vector<std::string> ids{"background", "particle", "label"};
|
||||
const auto plan = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), ids, true);
|
||||
ASSERT_TRUE(plan.valid);
|
||||
state.recordRenderSegmentResolution(plan, {
|
||||
{0, "background", "background", true, std::nullopt, std::nullopt},
|
||||
{1, "particle", "raster-particle", true, "field", std::nullopt},
|
||||
{2, "label", "symbol", true, "chart", std::nullopt}});
|
||||
auto snapshot = state.renderSegmentSnapshot();
|
||||
ASSERT_EQ(snapshot.segments.size(), 3u);
|
||||
EXPECT_FALSE(snapshot.segments[0].placementCapable);
|
||||
EXPECT_TRUE(snapshot.segments[2].placementCapable);
|
||||
state.recordSegmentExecution(0, true, true, true, 7, "reuse", nullptr);
|
||||
state.recordMainBufferExecution(snapshot.topologyRevision, true, 1.0f, true, true,
|
||||
"global-live", "global-light-transition",
|
||||
{true, true, false}, {"live", "live", "live"});
|
||||
snapshot = state.renderSegmentSnapshot();
|
||||
ASSERT_TRUE(snapshot.segments[0].cache);
|
||||
EXPECT_EQ(snapshot.segments[0].cache->finalDecision, "reuse");
|
||||
EXPECT_EQ(snapshot.segments[0].actualExecution, "live");
|
||||
EXPECT_EQ(snapshot.mainBufferExecutionMode, "global-live");
|
||||
EXPECT_EQ(snapshot.globalLiveFallbackReason, "global-light-transition");
|
||||
EXPECT_TRUE(snapshot.placementChanged);
|
||||
EXPECT_FLOAT_EQ(snapshot.symbolFadeChange, 1.0f);
|
||||
EXPECT_TRUE(snapshot.placementTransitionActive);
|
||||
EXPECT_TRUE(snapshot.crossSourceCollisions);
|
||||
state.recordMainBufferExecution(snapshot.topologyRevision, true, 1.0f, true, true,
|
||||
"mixed", "none", {true, true, false},
|
||||
{"composite", "live", "live"});
|
||||
const auto mixed = state.renderSegmentSnapshot();
|
||||
EXPECT_EQ(mixed.segments[0].actualExecution, "composite");
|
||||
EXPECT_EQ(mixed.segments[2].actualExecution, "live");
|
||||
const auto json = renderSegmentDiagnosticsToJSON(mixed);
|
||||
EXPECT_EQ(renderSegmentDiagnosticsToJSON(state.renderSegmentSnapshot()), json);
|
||||
EXPECT_NE(json.find("\"placementCapable\":true"), std::string::npos);
|
||||
EXPECT_NE(json.find("\"placementGatePassed\":false"), std::string::npos);
|
||||
EXPECT_NE(json.find("\"actualExecution\":\"composite\""), std::string::npos);
|
||||
EXPECT_NE(json.find("\"mainBufferExecutionMode\":\"mixed\""), std::string::npos);
|
||||
EXPECT_EQ(state.renderSegmentSnapshot().topologyRevision, mixed.topologyRevision);
|
||||
}
|
||||
|
||||
TEST(RenderSegmentPlan, StrictJSONRejectsUnknownFieldsStrategyAndInvalidFirstBoundary) {
|
||||
EXPECT_FALSE(parseRenderSegmentPlanJSON(
|
||||
R"({"schemaVersion":1,"segments":[{"strategy":"RETAINED","unexpected":true}]})"));
|
||||
|
||||
Reference in New Issue
Block a user