362 lines
18 KiB
C++
362 lines
18 KiB
C++
#pragma once
|
|
|
|
#include <mln/renderer/render_segment.hpp>
|
|
#include <mln/renderer/particle_fast_path.hpp>
|
|
|
|
#include <rapidjson/document.h>
|
|
#include <rapidjson/stringbuffer.h>
|
|
#include <rapidjson/writer.h>
|
|
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
namespace mln {
|
|
|
|
inline std::optional<RenderSegmentPlan> parseRenderSegmentPlanJSON(const std::string& json) {
|
|
rapidjson::Document document;
|
|
if (document.Parse<rapidjson::kParseValidateEncodingFlag>(json.data(), json.size()).HasParseError() ||
|
|
!document.IsObject() ||
|
|
!document.HasMember("schemaVersion") || !document["schemaVersion"].IsUint() ||
|
|
document["schemaVersion"].GetUint() != 1 || !document.HasMember("segments") ||
|
|
!document["segments"].IsArray() || document.MemberCount() != 2) {
|
|
return std::nullopt;
|
|
}
|
|
std::set<std::string_view> rootKeys;
|
|
for (auto member = document.MemberBegin(); member != document.MemberEnd(); ++member) {
|
|
const std::string_view name(member->name.GetString(), member->name.GetStringLength());
|
|
if (!rootKeys.insert(name).second || (name != "schemaVersion" && name != "segments")) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
RenderSegmentPlan plan;
|
|
for (const auto& value : document["segments"].GetArray()) {
|
|
if (!value.IsObject() || !value.HasMember("strategy") || !value["strategy"].IsString() ||
|
|
value.MemberCount() > 2) {
|
|
return std::nullopt;
|
|
}
|
|
std::set<std::string_view> segmentKeys;
|
|
for (auto member = value.MemberBegin(); member != value.MemberEnd(); ++member) {
|
|
const std::string_view name(member->name.GetString(), member->name.GetStringLength());
|
|
if (!segmentKeys.insert(name).second || (name != "strategy" && name != "startLayerId")) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
RenderSegmentDefinition definition;
|
|
const auto& strategyValue = value["strategy"];
|
|
const std::string strategy(strategyValue.GetString(), strategyValue.GetStringLength());
|
|
if (strategy.find('\0') != std::string::npos) return std::nullopt;
|
|
if (strategy == "RETAINED") {
|
|
definition.strategy = RenderSegmentStrategy::Retained;
|
|
} else if (strategy == "CONTINUOUS") {
|
|
definition.strategy = RenderSegmentStrategy::Continuous;
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
if (value.HasMember("startLayerId")) {
|
|
const auto& startLayerId = value["startLayerId"];
|
|
if (!startLayerId.IsString()) return std::nullopt;
|
|
std::string layerId(startLayerId.GetString(), startLayerId.GetStringLength());
|
|
if (layerId.find('\0') != std::string::npos) return std::nullopt;
|
|
definition.startLayerId = std::move(layerId);
|
|
}
|
|
plan.segments.push_back(std::move(definition));
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
// Android and iOS bridges use the same fail-closed conversion so malformed
|
|
// JSON always replaces (and therefore clears) a previously accepted plan.
|
|
inline RenderSegmentPlan renderSegmentPlanFromJSONOrInvalid(const std::string& json) {
|
|
if (auto plan = parseRenderSegmentPlanJSON(json)) return std::move(*plan);
|
|
RenderSegmentPlan invalid;
|
|
invalid.schemaVersion = 0;
|
|
return invalid;
|
|
}
|
|
|
|
template <typename Writer>
|
|
inline void writeOptionalString(Writer& writer, const char* key, const std::optional<std::string>& value) {
|
|
writer.Key(key);
|
|
if (value) writer.String(value->c_str()); else writer.Null();
|
|
}
|
|
|
|
template <typename Writer>
|
|
inline void writeOptionalUint32(Writer& writer, const char* key, const std::optional<uint32_t>& value) {
|
|
writer.Key(key);
|
|
if (value) writer.Uint(*value); else writer.Null();
|
|
}
|
|
|
|
template <typename Writer>
|
|
inline void writeStringArray(Writer& writer, const char* key, const std::vector<std::string>& values) {
|
|
writer.Key(key);
|
|
writer.StartArray();
|
|
for (const auto& value : values) writer.String(value.c_str());
|
|
writer.EndArray();
|
|
}
|
|
|
|
template <typename Writer>
|
|
inline void writeStyleLayerDiagnostics(Writer& writer, const StyleLayerDiagnostics& layer) {
|
|
writer.StartObject();
|
|
writer.Key("layerIndex"); writer.Int(layer.layerIndex);
|
|
writer.Key("layerId"); writer.String(layer.layerId.c_str());
|
|
writer.Key("layerType"); writer.String(layer.layerType.c_str());
|
|
writer.Key("visible"); writer.Bool(layer.visible);
|
|
writeOptionalString(writer, "sourceId", layer.sourceId);
|
|
writeOptionalString(writer, "sourceLayer", layer.sourceLayer);
|
|
writer.EndObject();
|
|
}
|
|
|
|
template <typename Writer>
|
|
inline void writeFastGatePhase(Writer& w, const FastGatePhaseSample& p, const char* phase,
|
|
const char* placementBasis = "previous-completed-render-tree") {
|
|
w.StartObject();
|
|
w.Key("phase"); w.String(phase);
|
|
w.Key("placementBasis"); w.String(!p.hasPlacementSnapshot ? "UNKNOWN/no-completed-snapshot"
|
|
: placementBasis);
|
|
w.Key("placementChanged"); w.Bool(p.placementChanged);
|
|
w.Key("symbolFadeChange"); w.Double(p.symbolFadeChange);
|
|
w.Key("placementTransitionActive"); w.Bool(p.placementTransitionActive);
|
|
w.Key("contentRevision"); w.Uint64(p.contentRevision);
|
|
w.Key("topologyRevision"); w.Uint64(p.topologyRevision);
|
|
w.Key("configRevision"); w.Uint64(p.configRevision);
|
|
w.Key("fullCpuFrame"); w.Uint64(p.fullCpuFrame);
|
|
w.Key("processSimulationAdvance");
|
|
if (p.processSimulationAdvance) w.Uint64(*p.processSimulationAdvance); else w.Null();
|
|
w.Key("retainedSamplesTruncated"); w.Bool(p.retainedSamplesTruncated);
|
|
w.Key("retained"); w.StartArray();
|
|
for (const auto& r : p.retained) {
|
|
const auto& c = r.cache;
|
|
w.StartObject();
|
|
w.Key("segmentIndex"); w.Uint(r.segmentIndex);
|
|
w.Key("cacheState"); w.String(c.cacheState.c_str());
|
|
w.Key("build"); w.Uint64(c.build);
|
|
w.Key("reuse"); w.Uint64(c.reuse);
|
|
w.Key("invalidation"); w.Uint64(c.invalidation);
|
|
w.Key("fallbackCount"); w.Uint64(c.fallbackCount);
|
|
w.Key("contentRevision"); w.Uint64(c.contentRevision);
|
|
w.Key("sourceRevision"); w.Uint64(c.sourceRevision);
|
|
w.Key("sourceReady"); w.Bool(c.sourceReady);
|
|
w.Key("sourceStable"); w.Bool(c.sourceStable);
|
|
w.Key("supported"); w.Bool(c.supported);
|
|
w.Key("finalDecision"); w.String(c.finalDecision.c_str());
|
|
w.Key("lastInvalidationReason"); w.String(c.lastInvalidationReason.c_str());
|
|
w.Key("lastFallbackReason"); w.String(c.lastFallbackReason.c_str());
|
|
w.Key("placementGatePassed"); w.Bool(r.placementGatePassed);
|
|
w.Key("actualExecution"); w.String(r.actualExecution.c_str());
|
|
w.EndObject();
|
|
}
|
|
w.EndArray();
|
|
w.EndObject();
|
|
}
|
|
|
|
template <typename Writer>
|
|
inline void writeFastGateTrace(Writer& w, const FastGateTraceDiagnostics& t) {
|
|
w.StartObject();
|
|
w.Key("schemaVersion"); w.Uint(t.schemaVersion);
|
|
w.Key("capacity"); w.Uint(120);
|
|
w.Key("retainedLimitPerPhase"); w.Uint(16);
|
|
w.Key("lifecycle"); w.Uint64(t.lifecycle);
|
|
w.Key("attempts"); w.Uint64(t.attempts);
|
|
w.Key("overwritten"); w.Uint64(t.overwritten);
|
|
w.Key("decisionMismatch"); w.Uint64(t.decisionMismatch);
|
|
w.Key("fullRendered"); w.Uint64(t.fullRendered);
|
|
w.Key("fastRendered"); w.Uint64(t.fastRendered);
|
|
w.Key("noRender"); w.Uint64(t.noRender);
|
|
w.Key("windowCount"); w.Uint(static_cast<unsigned>(t.frames.size()));
|
|
w.Key("windowBeginSequence"); w.Uint64(t.frames.empty() ? 0 : t.frames.front().sequence);
|
|
w.Key("windowEndSequence"); w.Uint64(t.frames.empty() ? 0 : t.frames.back().sequence);
|
|
uint64_t windowFull = 0, windowFast = 0, windowNoRender = 0;
|
|
std::array<uint64_t, 24> windowFailures{};
|
|
for (const auto& f : t.frames) {
|
|
if (f.completed && f.outcome == "full-rendered") ++windowFull;
|
|
else if (f.completed && f.outcome == "fast-rendered") ++windowFast;
|
|
else if (f.completed) ++windowNoRender;
|
|
for (std::size_t i = 0; i < 24; ++i) if (f.failMask & (uint32_t{1} << i)) ++windowFailures[i];
|
|
}
|
|
w.Key("windowFullRendered"); w.Uint64(windowFull);
|
|
w.Key("windowFastRendered"); w.Uint64(windowFast);
|
|
w.Key("windowNoRender"); w.Uint64(windowNoRender);
|
|
w.Key("gates"); w.StartArray();
|
|
for (std::size_t i = 0; i < particleFastGateReasons.size(); ++i) {
|
|
w.StartObject();
|
|
w.Key("order"); w.Uint(static_cast<unsigned>(i + 1));
|
|
w.Key("reason"); w.String(particleFastPathMissReasonText(particleFastGateReasons[i]));
|
|
w.Key("failures"); w.Uint64(t.failureCounts[i]);
|
|
w.Key("firstFailures"); w.Uint64(t.firstFailureCounts[i]);
|
|
w.Key("windowFailures"); w.Uint64(windowFailures[i]);
|
|
w.EndObject();
|
|
}
|
|
w.EndArray();
|
|
w.Key("frames"); w.StartArray();
|
|
for (const auto& f : t.frames) {
|
|
w.StartObject();
|
|
w.Key("sequence"); w.Uint64(f.sequence);
|
|
w.Key("frame"); w.Uint64(f.frame);
|
|
w.Key("updateTimeNs"); w.Int64(f.updateTimeNs);
|
|
w.Key("knownMask"); w.Uint(f.knownMask);
|
|
w.Key("failMask"); w.Uint(f.failMask);
|
|
w.Key("firstFailure"); w.String(f.firstFailure.c_str());
|
|
w.Key("decisionConsistent"); w.Bool(f.decisionConsistent);
|
|
w.Key("candidate"); w.Bool(f.candidate);
|
|
w.Key("preFastPlacementTransition"); w.Bool(f.preFastPlacementTransition);
|
|
w.Key("placementTransitionsEnabled"); w.Bool(f.placementTransitionsEnabled);
|
|
w.Key("completed"); w.Bool(f.completed);
|
|
w.Key("outcome"); w.String(f.outcome.c_str());
|
|
w.Key("gateStatus"); w.StartArray();
|
|
for (std::size_t i = 0; i < 24; ++i) {
|
|
const auto bit = uint32_t{1} << i;
|
|
w.String(!(f.knownMask & bit) ? "UNKNOWN" : (f.failMask & bit) ? "FAIL" : "PASS");
|
|
}
|
|
w.EndArray();
|
|
w.Key("cacheInputsTruncated"); w.Bool(f.cacheInputsTruncated);
|
|
w.Key("cacheInputs"); w.StartArray();
|
|
for (const auto& c : f.cacheInputs) {
|
|
w.StartObject();
|
|
w.Key("segmentIndex"); w.Uint(c.segmentIndex);
|
|
w.Key("exists"); w.Bool(c.exists);
|
|
w.Key("valid"); w.Bool(c.valid);
|
|
w.Key("buildRevision"); w.Uint64(c.buildRevision);
|
|
w.Key("staticRevision"); w.Uint64(c.staticRevision);
|
|
w.EndObject();
|
|
}
|
|
w.EndArray();
|
|
w.Key("pre"); writeFastGatePhase(w, f.pre, "pre-fast");
|
|
w.Key("post");
|
|
if (f.completed) {
|
|
writeFastGatePhase(w, f.post, "post-render", f.outcome == "full-rendered"
|
|
? "current-full-render-tree" : f.outcome == "fast-rendered"
|
|
? "current-fast-render-tree/no-new-placement" : "previous-completed-render-tree");
|
|
} else w.Null();
|
|
w.EndObject();
|
|
}
|
|
w.EndArray();
|
|
w.EndObject();
|
|
}
|
|
|
|
inline std::string renderSegmentDiagnosticsToJSON(const RenderSegmentDiagnostics& snapshot) {
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
writer.StartObject();
|
|
writer.Key("schemaVersion"); writer.Uint(snapshot.schemaVersion);
|
|
if (snapshot.fastGateTrace) {
|
|
writer.Key("fastGateTrace"); writeFastGateTrace(writer, *snapshot.fastGateTrace);
|
|
}
|
|
writer.Key("enabled"); writer.Bool(snapshot.enabled);
|
|
writer.Key("configured"); writer.Bool(snapshot.configured);
|
|
writer.Key("valid"); writer.Bool(snapshot.valid);
|
|
writer.Key("configSource"); writer.String(snapshot.configSource.c_str());
|
|
writer.Key("configRevision"); writer.Uint64(snapshot.configRevision);
|
|
writer.Key("topologyRevision"); writer.Uint64(snapshot.topologyRevision);
|
|
writer.Key("styleLayerCount"); writer.Uint(snapshot.styleLayerCount);
|
|
writer.Key("segmentCount"); writer.Uint(snapshot.segmentCount);
|
|
writer.Key("retainedSegmentCount"); writer.Uint(snapshot.retainedSegmentCount);
|
|
writer.Key("continuousSegmentCount"); writer.Uint(snapshot.continuousSegmentCount);
|
|
writer.Key("coverageComplete"); writer.Bool(snapshot.coverageComplete);
|
|
writer.Key("fallback"); writer.Bool(snapshot.fallback);
|
|
writer.Key("lastReason"); writer.String(snapshot.lastReason.c_str());
|
|
writer.Key("fastPathEligible"); writer.Bool(snapshot.fastPathEligible);
|
|
writer.Key("fastPathActive"); writer.Bool(snapshot.fastPathActive);
|
|
writer.Key("fastPathFallbackReason"); writer.String(snapshot.fastPathFallbackReason.c_str());
|
|
writer.Key("fastPathHit"); writer.Uint64(snapshot.fastPathHit);
|
|
writer.Key("fastPathMiss"); writer.Uint64(snapshot.fastPathMiss);
|
|
writer.Key("fullCpuFrame"); writer.Uint64(snapshot.fullCpuFrame);
|
|
writer.Key("placementChanged"); writer.Bool(snapshot.placementChanged);
|
|
writer.Key("symbolFadeChange"); writer.Double(snapshot.symbolFadeChange);
|
|
writer.Key("placementTransitionActive"); writer.Bool(snapshot.placementTransitionActive);
|
|
writer.Key("crossSourceCollisions"); writer.Bool(snapshot.crossSourceCollisions);
|
|
writer.Key("mainBufferExecutionMode"); writer.String(snapshot.mainBufferExecutionMode.c_str());
|
|
writer.Key("globalLiveFallbackReason"); writer.String(snapshot.globalLiveFallbackReason.c_str());
|
|
writer.Key("styleLoaded"); writer.Bool(snapshot.styleLoaded);
|
|
writer.Key("imageManagerLoaded"); writer.Bool(snapshot.imageManagerLoaded);
|
|
writer.Key("renderOrchestratorLoaded"); writer.Bool(snapshot.renderOrchestratorLoaded);
|
|
writer.Key("spriteLoaded"); writer.Bool(snapshot.spriteLoaded);
|
|
writer.Key("hasFadingTiles"); writer.Bool(snapshot.hasFadingTiles);
|
|
writer.Key("contentRevision"); writer.Uint64(snapshot.contentRevision);
|
|
writer.Key("sources"); writer.StartArray();
|
|
for (const auto& source : snapshot.sources) {
|
|
writer.StartObject();
|
|
writer.Key("sourceId"); writer.String(source.sourceId.c_str());
|
|
writer.Key("sourceType"); writer.String(source.sourceType.c_str());
|
|
writer.Key("enabled"); writer.Bool(source.enabled);
|
|
writer.Key("isLoaded"); writer.Bool(source.isLoaded);
|
|
writer.Key("tileBacked"); writer.Bool(source.tileBacked);
|
|
writeOptionalUint32(writer, "tileCount", source.tileCount);
|
|
writeOptionalUint32(writer, "incompleteTileCount", source.incompleteTileCount);
|
|
writeOptionalUint32(writer, "loadedTileCount", source.loadedTileCount);
|
|
writer.Key("hasFadingTiles"); writer.Bool(source.hasFadingTiles);
|
|
writer.Key("lastSourceMutationSequence"); writer.Uint64(source.lastSourceMutationSequence);
|
|
writer.Key("lastTileChangedSequence"); writer.Uint64(source.lastTileChangedSequence);
|
|
writer.Key("lastTileActionSequence"); writer.Uint64(source.lastTileActionSequence);
|
|
writer.Key("lastTileAction"); writer.String(source.lastTileAction.c_str());
|
|
writer.Key("lastMutationReason"); writer.String(source.lastMutationReason.c_str());
|
|
writer.EndObject();
|
|
}
|
|
writer.EndArray();
|
|
writer.Key("lastMutation"); writer.StartObject();
|
|
writer.Key("sequence"); writer.Uint64(snapshot.lastMutation.sequence);
|
|
writer.Key("contentRevision"); writer.Uint64(snapshot.lastMutation.contentRevision);
|
|
writer.Key("eventType"); writer.String(snapshot.lastMutation.eventType.c_str());
|
|
writer.Key("reason"); writer.String(snapshot.lastMutation.reason.c_str());
|
|
writeOptionalString(writer, "sourceId", snapshot.lastMutation.sourceId);
|
|
writer.Key("affectedSegmentIndices"); writer.StartArray();
|
|
for (const auto index : snapshot.lastMutation.affectedSegmentIndices) writer.Uint(index);
|
|
writer.EndArray();
|
|
writer.Key("dependencyScope"); writer.String(snapshot.lastMutation.dependencyScope.c_str());
|
|
writer.EndObject();
|
|
writer.Key("styleLayers"); writer.StartArray();
|
|
for (const auto& layer : snapshot.styleLayers) writeStyleLayerDiagnostics(writer, layer);
|
|
writer.EndArray();
|
|
writer.Key("segments"); writer.StartArray();
|
|
for (const auto& segment : snapshot.segments) {
|
|
writer.StartObject();
|
|
writer.Key("segmentIndex"); writer.Uint(segment.segmentIndex);
|
|
writer.Key("strategy"); writer.String(renderSegmentStrategyText(segment.strategy));
|
|
writeOptionalString(writer, "configuredStartLayerId", segment.configuredStartLayerId);
|
|
writer.Key("beginIndex"); writer.Int(segment.beginIndex);
|
|
writer.Key("endExclusive"); writer.Int(segment.endExclusive);
|
|
writeOptionalString(writer, "resolvedStartLayerId", segment.resolvedStartLayerId);
|
|
writeOptionalString(writer, "resolvedEndLayerId", segment.resolvedEndLayerId);
|
|
writer.Key("layerCount"); writer.Uint(static_cast<uint32_t>(segment.layers.size()));
|
|
writer.Key("layers"); writer.StartArray();
|
|
for (const auto& layer : segment.layers) writeStyleLayerDiagnostics(writer, layer);
|
|
writer.EndArray();
|
|
writeStringArray(writer, "dependentSourceIds", segment.dependentSourceIds);
|
|
writeStringArray(writer, "unknownDependencies", segment.unknownDependencies);
|
|
writer.Key("placementCapable"); writer.Bool(segment.placementCapable);
|
|
writer.Key("placementGatePassed"); writer.Bool(segment.placementGatePassed);
|
|
writer.Key("actualExecution"); writer.String(segment.actualExecution.c_str());
|
|
writer.Key("cache");
|
|
if (!segment.cache) {
|
|
writer.Null();
|
|
} else {
|
|
const auto& cache = *segment.cache;
|
|
writer.StartObject();
|
|
writer.Key("cacheState"); writer.String(cache.cacheState.c_str());
|
|
writer.Key("contentRevision"); writer.Uint64(cache.contentRevision);
|
|
writer.Key("build"); writer.Uint64(cache.build);
|
|
writer.Key("reuse"); writer.Uint64(cache.reuse);
|
|
writer.Key("invalidation"); writer.Uint64(cache.invalidation);
|
|
writer.Key("fallbackCount"); writer.Uint64(cache.fallbackCount);
|
|
writer.Key("finalDecision"); writer.String(cache.finalDecision.c_str());
|
|
writer.Key("lastFallbackReason"); writer.String(cache.lastFallbackReason.c_str());
|
|
writer.Key("sourceReady"); writer.Bool(cache.sourceReady);
|
|
writer.Key("sourceStable"); writer.Bool(cache.sourceStable);
|
|
writer.Key("supported"); writer.Bool(cache.supported);
|
|
writer.Key("sourceRevision"); writer.Uint64(cache.sourceRevision);
|
|
writer.Key("lastInvalidationReason"); writer.String(cache.lastInvalidationReason.c_str());
|
|
writer.Key("textureCreate"); writer.Uint64(cache.textureCreate);
|
|
writer.Key("compositeCreate"); writer.Uint64(cache.compositeCreate);
|
|
writer.Key("resourceRelease"); writer.Uint64(cache.resourceRelease);
|
|
writer.EndObject();
|
|
}
|
|
writer.EndObject();
|
|
}
|
|
writer.EndArray();
|
|
writer.EndObject();
|
|
return buffer.GetString();
|
|
}
|
|
|
|
} // namespace mln
|