#pragma once #include #include #include #include #include #include #include #include #include #include namespace mln { inline std::optional parseRenderSegmentPlanJSON(const std::string& json) { rapidjson::Document document; if (document.Parse(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 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 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 inline void writeOptionalString(Writer& writer, const char* key, const std::optional& value) { writer.Key(key); if (value) writer.String(value->c_str()); else writer.Null(); } template inline void writeOptionalUint32(Writer& writer, const char* key, const std::optional& value) { writer.Key(key); if (value) writer.Uint(*value); else writer.Null(); } template inline void writeStringArray(Writer& writer, const char* key, const std::vector& values) { writer.Key(key); writer.StartArray(); for (const auto& value : values) writer.String(value.c_str()); writer.EndArray(); } template 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 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 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(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 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(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 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(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