Json telemetry
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m52s
CI / Windows Release Package (push) Successful in 3m19s

This commit is contained in:
Aiden
2026-05-12 12:13:21 +10:00
parent 44b198b14d
commit 79f7ac6c86
7 changed files with 536 additions and 0 deletions

View File

@@ -44,6 +44,8 @@ Included now:
- render-thread-only GL commit once the artifact is ready
- manifest-driven stateless single-pass shader packages
- default float, vec2, color, boolean, enum, and trigger parameters
- small JSON writer for future HTTP/WebSocket payloads
- JSON serialization for cadence telemetry snapshots
- background logging with `log`, `warning`, and `error` levels
- compact telemetry
- non-GL frame-exchange tests
@@ -189,6 +191,7 @@ This app keeps the same core behavior but splits it into modules that can grow:
- `frames/`: system-memory handoff
- `platform/`: COM/Win32/hidden GL context support
- `render/`: cadence, simple rendering, PBO readback
- `json/`: compact JSON serialization helpers
- `video/`: DeckLink output wrapper and scheduling thread
- `telemetry/`: cadence telemetry
- `app/`: startup/shutdown orchestration

View File

@@ -0,0 +1,235 @@
#include "JsonWriter.h"
#include <iomanip>
#include <stdexcept>
namespace RenderCadenceCompositor
{
namespace
{
constexpr int kMaxDepth = 32;
void AppendHexEscape(std::ostringstream& stream, unsigned char value)
{
stream << "\\u"
<< std::hex << std::uppercase << std::setw(4) << std::setfill('0')
<< static_cast<int>(value)
<< std::dec << std::nouppercase << std::setfill(' ');
}
}
void JsonWriter::BeginObject()
{
BeginValue();
mStream << "{";
PushScope(ScopeKind::Object);
}
void JsonWriter::EndObject()
{
PopScope(ScopeKind::Object);
mStream << "}";
}
void JsonWriter::BeginArray()
{
BeginValue();
mStream << "[";
PushScope(ScopeKind::Array);
}
void JsonWriter::EndArray()
{
PopScope(ScopeKind::Array);
mStream << "]";
}
void JsonWriter::Key(const std::string& name)
{
BeginKey();
mStream << "\"" << EscapeString(name) << "\":";
}
void JsonWriter::String(const std::string& value)
{
BeginValue();
mStream << "\"" << EscapeString(value) << "\"";
}
void JsonWriter::Bool(bool value)
{
BeginValue();
mStream << (value ? "true" : "false");
}
void JsonWriter::Null()
{
BeginValue();
mStream << "null";
}
void JsonWriter::Int(int64_t value)
{
BeginValue();
mStream << value;
}
void JsonWriter::UInt(uint64_t value)
{
BeginValue();
mStream << value;
}
void JsonWriter::Double(double value)
{
BeginValue();
mStream << std::setprecision(15) << value;
}
void JsonWriter::KeyString(const std::string& name, const std::string& value)
{
Key(name);
String(value);
}
void JsonWriter::KeyBool(const std::string& name, bool value)
{
Key(name);
Bool(value);
}
void JsonWriter::KeyNull(const std::string& name)
{
Key(name);
Null();
}
void JsonWriter::KeyInt(const std::string& name, int64_t value)
{
Key(name);
Int(value);
}
void JsonWriter::KeyUInt(const std::string& name, uint64_t value)
{
Key(name);
UInt(value);
}
void JsonWriter::KeyDouble(const std::string& name, double value)
{
Key(name);
Double(value);
}
std::string JsonWriter::StringValue() const
{
if (mScopeDepth != 0)
throw std::logic_error("JSON document has unclosed scopes.");
return mStream.str();
}
void JsonWriter::Reset()
{
mStream.str(std::string());
mStream.clear();
mScopeDepth = 0;
}
std::string JsonWriter::EscapeString(const std::string& value)
{
std::ostringstream stream;
for (unsigned char character : value)
{
switch (character)
{
case '"':
stream << "\\\"";
break;
case '\\':
stream << "\\\\";
break;
case '\b':
stream << "\\b";
break;
case '\f':
stream << "\\f";
break;
case '\n':
stream << "\\n";
break;
case '\r':
stream << "\\r";
break;
case '\t':
stream << "\\t";
break;
default:
if (character < 0x20)
AppendHexEscape(stream, character);
else
stream << character;
break;
}
}
return stream.str();
}
void JsonWriter::BeginValue()
{
if (mScopeDepth == 0)
return;
Scope& scope = mScopes[mScopeDepth - 1];
if (scope.kind == ScopeKind::Object)
{
if (!scope.expectingValue)
throw std::logic_error("JSON object value must follow a key.");
scope.expectingValue = false;
return;
}
if (!scope.first)
mStream << ",";
scope.first = false;
}
void JsonWriter::BeginKey()
{
if (mScopeDepth == 0)
throw std::logic_error("JSON key cannot be written outside an object.");
Scope& scope = mScopes[mScopeDepth - 1];
if (scope.kind != ScopeKind::Object)
throw std::logic_error("JSON key cannot be written inside an array.");
if (scope.expectingValue)
throw std::logic_error("JSON object key cannot be written before its previous value.");
if (!scope.first)
mStream << ",";
scope.first = false;
scope.expectingValue = true;
}
void JsonWriter::PushScope(ScopeKind kind)
{
if (mScopeDepth >= kMaxDepth)
throw std::logic_error("JSON nesting is too deep.");
mScopes[mScopeDepth++] = Scope{ kind, true, false };
}
void JsonWriter::PopScope(ScopeKind kind)
{
if (mScopeDepth == 0)
throw std::logic_error("JSON scope underflow.");
Scope& scope = mScopes[mScopeDepth - 1];
if (scope.kind != kind)
throw std::logic_error("JSON scope kind mismatch.");
if (scope.expectingValue)
throw std::logic_error("JSON object key is missing a value.");
--mScopeDepth;
}
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <cstdint>
#include <sstream>
#include <string>
namespace RenderCadenceCompositor
{
class JsonWriter
{
public:
void BeginObject();
void EndObject();
void BeginArray();
void EndArray();
void Key(const std::string& name);
void String(const std::string& value);
void Bool(bool value);
void Null();
void Int(int64_t value);
void UInt(uint64_t value);
void Double(double value);
void KeyString(const std::string& name, const std::string& value);
void KeyBool(const std::string& name, bool value);
void KeyNull(const std::string& name);
void KeyInt(const std::string& name, int64_t value);
void KeyUInt(const std::string& name, uint64_t value);
void KeyDouble(const std::string& name, double value);
std::string StringValue() const;
void Reset();
static std::string EscapeString(const std::string& value);
private:
enum class ScopeKind
{
Object,
Array
};
struct Scope
{
ScopeKind kind = ScopeKind::Object;
bool first = true;
bool expectingValue = false;
};
void BeginValue();
void BeginKey();
void PushScope(ScopeKind kind);
void PopScope(ScopeKind kind);
std::ostringstream mStream;
Scope mScopes[32];
int mScopeDepth = 0;
};
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "CadenceTelemetry.h"
#include "../json/JsonWriter.h"
#include <cstdint>
#include <string>
namespace RenderCadenceCompositor
{
inline void WriteCadenceTelemetryJson(JsonWriter& writer, const CadenceTelemetrySnapshot& snapshot)
{
writer.BeginObject();
writer.KeyDouble("sampleSeconds", snapshot.sampleSeconds);
writer.KeyDouble("renderFps", snapshot.renderFps);
writer.KeyDouble("scheduleFps", snapshot.scheduleFps);
writer.KeyUInt("free", static_cast<uint64_t>(snapshot.freeFrames));
writer.KeyUInt("completed", static_cast<uint64_t>(snapshot.completedFrames));
writer.KeyUInt("scheduled", static_cast<uint64_t>(snapshot.scheduledFrames));
writer.KeyUInt("renderedTotal", snapshot.renderedTotal);
writer.KeyUInt("scheduledTotal", snapshot.scheduledTotal);
writer.KeyUInt("completedPollMisses", snapshot.completedPollMisses);
writer.KeyUInt("scheduleFailures", snapshot.scheduleFailures);
writer.KeyUInt("completions", snapshot.completions);
writer.KeyUInt("late", snapshot.displayedLate);
writer.KeyUInt("dropped", snapshot.dropped);
writer.KeyUInt("shaderCommitted", snapshot.shaderBuildsCommitted);
writer.KeyUInt("shaderFailures", snapshot.shaderBuildFailures);
writer.KeyBool("deckLinkBufferedAvailable", snapshot.deckLinkBufferedAvailable);
writer.Key("deckLinkBuffered");
if (snapshot.deckLinkBufferedAvailable)
writer.UInt(snapshot.deckLinkBuffered);
else
writer.Null();
writer.KeyDouble("scheduleCallMs", snapshot.deckLinkScheduleCallMilliseconds);
writer.EndObject();
}
inline std::string CadenceTelemetryToJson(const CadenceTelemetrySnapshot& snapshot)
{
JsonWriter writer;
WriteCadenceTelemetryJson(writer, snapshot);
return writer.StringValue();
}
}