From 95b4a543264fc3196fac4049b8b34339f5757709 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Tue, 12 May 2026 14:57:18 +1000 Subject: [PATCH] Seperation --- CMakeLists.txt | 3 + .../app/RenderCadenceApp.h | 261 ++---------------- .../app/RuntimeLayerController.cpp | 242 ++++++++++++++++ .../app/RuntimeLayerController.h | 59 ++++ .../control/ControlActionResult.h | 12 + .../control/HttpControlServer.h | 8 +- 6 files changed, 334 insertions(+), 251 deletions(-) create mode 100644 apps/RenderCadenceCompositor/app/RuntimeLayerController.cpp create mode 100644 apps/RenderCadenceCompositor/app/RuntimeLayerController.h create mode 100644 apps/RenderCadenceCompositor/control/ControlActionResult.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6243efc..b3f2ef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -307,6 +307,9 @@ set(RENDER_CADENCE_APP_SOURCES "${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp" "${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.h" "${RENDER_CADENCE_APP_DIR}/app/RenderCadenceApp.h" + "${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.cpp" + "${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.h" + "${RENDER_CADENCE_APP_DIR}/control/ControlActionResult.h" "${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.cpp" "${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.h" "${RENDER_CADENCE_APP_DIR}/control/RuntimeStateJson.h" diff --git a/apps/RenderCadenceCompositor/app/RenderCadenceApp.h b/apps/RenderCadenceCompositor/app/RenderCadenceApp.h index 249cbe8..2620fbb 100644 --- a/apps/RenderCadenceCompositor/app/RenderCadenceApp.h +++ b/apps/RenderCadenceCompositor/app/RenderCadenceApp.h @@ -2,21 +2,15 @@ #include "AppConfig.h" #include "AppConfigProvider.h" +#include "RuntimeLayerController.h" #include "../logging/Logger.h" -#include "../runtime/RuntimeLayerModel.h" -#include "../runtime/RuntimeShaderBridge.h" -#include "../runtime/SupportedShaderCatalog.h" #include "../control/RuntimeStateJson.h" #include "../telemetry/TelemetryHealthMonitor.h" #include "../video/DeckLinkOutput.h" #include "../video/DeckLinkOutputThread.h" -#include "RuntimeJson.h" #include #include -#include -#include -#include #include #include #include @@ -60,7 +54,10 @@ public: mFrameExchange(frameExchange), mConfig(config), mOutputThread(mOutput, mFrameExchange, mConfig.outputThread), - mTelemetryHealth(mConfig.telemetry) + mTelemetryHealth(mConfig.telemetry), + mRuntimeLayers([this](const std::vector& layers) { + mRenderThread.SubmitRuntimeRenderLayers(layers); + }) { } @@ -74,8 +71,10 @@ public: bool Start(std::string& error) { - LoadSupportedShaderCatalog(); - InitializeRuntimeLayerModel(); + mRuntimeLayers.Initialize( + mConfig.shaderLibrary, + static_cast(mConfig.maxTemporalHistoryFrames), + mConfig.runtimeShaderId); Log("app", "Starting render thread."); if (!detail::StartRenderThread(mRenderThread, error, 0)) @@ -84,7 +83,7 @@ public: Stop(); return false; } - StartRuntimeShaderBuild(); + mRuntimeLayers.StartStartupBuild(mConfig.runtimeShaderId); Log("app", "Waiting for rendered warmup frames."); if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout)) @@ -109,7 +108,7 @@ public: mTelemetryHealth.Stop(); mOutputThread.Stop(); mOutput.Stop(); - StopRuntimeShaderBuild(); + mRuntimeLayers.Stop(); mRenderThread.Stop(); mOutput.ReleaseResources(); if (mStarted) @@ -180,10 +179,10 @@ private: return BuildStateJson(); }; callbacks.addLayer = [this](const std::string& body) { - return HandleAddLayer(body); + return mRuntimeLayers.HandleAddLayer(body); }; callbacks.removeLayer = [this](const std::string& body) { - return HandleRemoveLayer(body); + return mRuntimeLayers.HandleRemoveLayer(body); }; std::string error; @@ -202,14 +201,14 @@ private: std::string BuildStateJson() { CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread); - RuntimeLayerModelSnapshot layerSnapshot = CopyRuntimeLayerSnapshot(telemetry); + RuntimeLayerModelSnapshot layerSnapshot = mRuntimeLayers.Snapshot(telemetry); return RuntimeStateToJson(RuntimeStateJsonInput{ mConfig, telemetry, mHttpServer.Port(), mVideoOutputEnabled, mVideoOutputStatus, - mShaderCatalog, + mRuntimeLayers.ShaderCatalog(), layerSnapshot }); } @@ -226,230 +225,6 @@ private: return false; } - void StartRuntimeShaderBuild() - { - if (mConfig.runtimeShaderId.empty()) - { - Log("runtime-shader", "Runtime shader build disabled."); - return; - } - - Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'."); - const std::string layerId = FirstRuntimeLayerId(); - if (!layerId.empty()) - StartLayerShaderBuild(layerId, mConfig.runtimeShaderId); - } - - void LoadSupportedShaderCatalog() - { - const std::filesystem::path shaderRoot = FindRepoPath(mConfig.shaderLibrary); - std::string error; - if (!mShaderCatalog.Load(shaderRoot, static_cast(mConfig.maxTemporalHistoryFrames), error)) - { - LogWarning("runtime-shader", "Supported shader catalog is empty: " + error); - return; - } - - Log("runtime-shader", "Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) + " shader(s)."); - } - - void InitializeRuntimeLayerModel() - { - std::lock_guard lock(mRuntimeLayerMutex); - std::string error; - if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, mConfig.runtimeShaderId, error)) - { - LogWarning("runtime-shader", error + " Runtime shader build disabled."); - mConfig.runtimeShaderId.clear(); - mRuntimeLayerModel.Clear(); - } - } - - void StopRuntimeShaderBuild() - { - StopAllRuntimeShaderBuilds(); - } - - void MarkRuntimeBuildStarted(const std::string& message) - { - std::lock_guard lock(mRuntimeLayerMutex); - std::string error; - const std::string layerId = mRuntimeLayerModel.FirstLayerId(); - if (!layerId.empty()) - mRuntimeLayerModel.MarkBuildStarted(layerId, message, error); - } - - bool MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact) - { - std::lock_guard lock(mRuntimeLayerMutex); - std::string error; - if (!mRuntimeLayerModel.MarkBuildReady(artifact, error)) - { - LogWarning("runtime-shader", error); - return false; - } - return true; - } - - void MarkRuntimeBuildFailed(const std::string& message) - { - std::lock_guard lock(mRuntimeLayerMutex); - if (!mRuntimeLayerModel.MarkBuildFailedForShader(mConfig.runtimeShaderId, message)) - LogWarning("runtime-shader", "Runtime shader failed without a matching display layer: " + message); - } - - void MarkRuntimeBuildFailedForLayer(const std::string& layerId, const std::string& message) - { - std::lock_guard lock(mRuntimeLayerMutex); - std::string error; - if (!mRuntimeLayerModel.MarkBuildFailed(layerId, message, error)) - LogWarning("runtime-shader", error); - } - - std::string FirstRuntimeLayerId() const - { - std::lock_guard lock(mRuntimeLayerMutex); - return mRuntimeLayerModel.FirstLayerId(); - } - - void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId) - { - { - std::lock_guard lock(mRuntimeLayerMutex); - std::string error; - mRuntimeLayerModel.MarkBuildStarted(layerId, "Runtime Slang build started for shader '" + shaderId + "'.", error); - } - - auto bridge = std::make_unique(); - RuntimeShaderBridge* bridgePtr = bridge.get(); - { - std::lock_guard lock(mShaderBuildMutex); - auto existingIt = mShaderBuilds.find(layerId); - if (existingIt != mShaderBuilds.end()) - existingIt->second->Stop(); - mShaderBuilds[layerId] = std::move(bridge); - } - - bridgePtr->Start( - layerId, - shaderId, - [this](const RuntimeShaderArtifact& artifact) { - if (MarkRuntimeBuildReady(artifact)) - PublishRuntimeRenderLayers(); - }, - [this, layerId](const std::string& message) { - MarkRuntimeBuildFailedForLayer(layerId, message); - LogError("runtime-shader", "Runtime Slang build failed: " + message); - }); - } - - void StopLayerShaderBuild(const std::string& layerId) - { - std::unique_ptr bridge; - { - std::lock_guard lock(mShaderBuildMutex); - auto bridgeIt = mShaderBuilds.find(layerId); - if (bridgeIt == mShaderBuilds.end()) - return; - bridge = std::move(bridgeIt->second); - mShaderBuilds.erase(bridgeIt); - } - bridge->Stop(); - } - - void StopAllRuntimeShaderBuilds() - { - std::map> builds; - { - std::lock_guard lock(mShaderBuildMutex); - builds.swap(mShaderBuilds); - } - for (auto& entry : builds) - entry.second->Stop(); - } - - ControlActionResult HandleAddLayer(const std::string& body) - { - std::string shaderId; - std::string error; - if (!ExtractStringField(body, "shaderId", shaderId, error)) - return { false, error }; - - std::string layerId; - { - std::lock_guard lock(mRuntimeLayerMutex); - if (!mRuntimeLayerModel.AddLayer(mShaderCatalog, shaderId, layerId, error)) - return { false, error }; - } - - Log("runtime-shader", "Layer added: " + layerId + " shader=" + shaderId); - StartLayerShaderBuild(layerId, shaderId); - return { true, std::string() }; - } - - ControlActionResult HandleRemoveLayer(const std::string& body) - { - std::string layerId; - std::string error; - if (!ExtractStringField(body, "layerId", layerId, error)) - return { false, error }; - - { - std::lock_guard lock(mRuntimeLayerMutex); - if (!mRuntimeLayerModel.RemoveLayer(layerId, error)) - return { false, error }; - } - - Log("runtime-shader", "Layer removed: " + layerId); - StopLayerShaderBuild(layerId); - PublishRuntimeRenderLayers(); - return { true, std::string() }; - } - - void PublishRuntimeRenderLayers() - { - std::vector renderLayers; - { - std::lock_guard lock(mRuntimeLayerMutex); - renderLayers = mRuntimeLayerModel.Snapshot().renderLayers; - } - mRenderThread.SubmitRuntimeRenderLayers(renderLayers); - } - - static bool ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error) - { - JsonValue root; - std::string parseError; - if (!ParseJson(body.empty() ? "{}" : body, root, parseError) || !root.isObject()) - { - error = parseError.empty() ? "Request body must be a JSON object." : parseError; - return false; - } - - const JsonValue* field = root.find(fieldName); - if (!field || !field->isString() || field->asString().empty()) - { - error = std::string("Request field '") + fieldName + "' must be a non-empty string."; - return false; - } - - value = field->asString(); - error.clear(); - return true; - } - - RuntimeLayerModelSnapshot CopyRuntimeLayerSnapshot(const CadenceTelemetrySnapshot& telemetry) const - { - std::lock_guard lock(mRuntimeLayerMutex); - RuntimeLayerModelSnapshot snapshot = mRuntimeLayerModel.Snapshot(); - if (telemetry.shaderBuildFailures > 0) - { - snapshot.compileSucceeded = false; - snapshot.compileMessage = "Runtime shader GL commit failed; see logs for details."; - } - return snapshot; - } - RenderThread& mRenderThread; SystemFrameExchange& mFrameExchange; AppConfig mConfig; @@ -458,11 +233,7 @@ private: TelemetryHealthMonitor mTelemetryHealth; CadenceTelemetry mHttpTelemetry; HttpControlServer mHttpServer; - SupportedShaderCatalog mShaderCatalog; - mutable std::mutex mRuntimeLayerMutex; - RuntimeLayerModel mRuntimeLayerModel; - std::mutex mShaderBuildMutex; - std::map> mShaderBuilds; + RuntimeLayerController mRuntimeLayers; bool mStarted = false; bool mVideoOutputEnabled = false; std::string mVideoOutputStatus = "DeckLink output not started."; diff --git a/apps/RenderCadenceCompositor/app/RuntimeLayerController.cpp b/apps/RenderCadenceCompositor/app/RuntimeLayerController.cpp new file mode 100644 index 0000000..532d529 --- /dev/null +++ b/apps/RenderCadenceCompositor/app/RuntimeLayerController.cpp @@ -0,0 +1,242 @@ +#include "RuntimeLayerController.h" + +#include "AppConfigProvider.h" +#include "RuntimeJson.h" +#include "../logging/Logger.h" + +#include + +namespace RenderCadenceCompositor +{ +RuntimeLayerController::RuntimeLayerController(RenderLayerPublisher publisher) : + mPublisher(std::move(publisher)) +{ +} + +RuntimeLayerController::~RuntimeLayerController() +{ + Stop(); +} + +void RuntimeLayerController::SetPublisher(RenderLayerPublisher publisher) +{ + mPublisher = std::move(publisher); +} + +void RuntimeLayerController::Initialize(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames, std::string& runtimeShaderId) +{ + LoadSupportedShaderCatalog(shaderLibrary, maxTemporalHistoryFrames); + InitializeLayerModel(runtimeShaderId); +} + +void RuntimeLayerController::StartStartupBuild(const std::string& runtimeShaderId) +{ + if (runtimeShaderId.empty()) + { + Log("runtime-shader", "Runtime shader build disabled."); + return; + } + + Log("runtime-shader", "Starting background Slang build for shader '" + runtimeShaderId + "'."); + const std::string layerId = FirstRuntimeLayerId(); + if (!layerId.empty()) + StartLayerShaderBuild(layerId, runtimeShaderId); +} + +void RuntimeLayerController::Stop() +{ + StopAllRuntimeShaderBuilds(); +} + +ControlActionResult RuntimeLayerController::HandleAddLayer(const std::string& body) +{ + std::string shaderId; + std::string error; + if (!ExtractStringField(body, "shaderId", shaderId, error)) + return { false, error }; + + std::string layerId; + { + std::lock_guard lock(mRuntimeLayerMutex); + if (!mRuntimeLayerModel.AddLayer(mShaderCatalog, shaderId, layerId, error)) + return { false, error }; + } + + Log("runtime-shader", "Layer added: " + layerId + " shader=" + shaderId); + StartLayerShaderBuild(layerId, shaderId); + return { true, std::string() }; +} + +ControlActionResult RuntimeLayerController::HandleRemoveLayer(const std::string& body) +{ + std::string layerId; + std::string error; + if (!ExtractStringField(body, "layerId", layerId, error)) + return { false, error }; + + { + std::lock_guard lock(mRuntimeLayerMutex); + if (!mRuntimeLayerModel.RemoveLayer(layerId, error)) + return { false, error }; + } + + Log("runtime-shader", "Layer removed: " + layerId); + StopLayerShaderBuild(layerId); + PublishRuntimeRenderLayers(); + return { true, std::string() }; +} + +RuntimeLayerModelSnapshot RuntimeLayerController::Snapshot(const CadenceTelemetrySnapshot& telemetry) const +{ + std::lock_guard lock(mRuntimeLayerMutex); + RuntimeLayerModelSnapshot snapshot = mRuntimeLayerModel.Snapshot(); + if (telemetry.shaderBuildFailures > 0) + { + snapshot.compileSucceeded = false; + snapshot.compileMessage = "Runtime shader GL commit failed; see logs for details."; + } + return snapshot; +} + +void RuntimeLayerController::LoadSupportedShaderCatalog(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames) +{ + const std::filesystem::path shaderRoot = FindRepoPath(shaderLibrary); + std::string error; + if (!mShaderCatalog.Load(shaderRoot, maxTemporalHistoryFrames, error)) + { + LogWarning("runtime-shader", "Supported shader catalog is empty: " + error); + return; + } + + Log("runtime-shader", "Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) + " shader(s)."); +} + +void RuntimeLayerController::InitializeLayerModel(std::string& runtimeShaderId) +{ + std::lock_guard lock(mRuntimeLayerMutex); + std::string error; + if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, runtimeShaderId, error)) + { + LogWarning("runtime-shader", error + " Runtime shader build disabled."); + runtimeShaderId.clear(); + mRuntimeLayerModel.Clear(); + } +} + +void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId) +{ + { + std::lock_guard lock(mRuntimeLayerMutex); + std::string error; + mRuntimeLayerModel.MarkBuildStarted(layerId, "Runtime Slang build started for shader '" + shaderId + "'.", error); + } + + auto bridge = std::make_unique(); + RuntimeShaderBridge* bridgePtr = bridge.get(); + { + std::lock_guard lock(mShaderBuildMutex); + auto existingIt = mShaderBuilds.find(layerId); + if (existingIt != mShaderBuilds.end()) + existingIt->second->Stop(); + mShaderBuilds[layerId] = std::move(bridge); + } + + bridgePtr->Start( + layerId, + shaderId, + [this](const RuntimeShaderArtifact& artifact) { + if (MarkRuntimeBuildReady(artifact)) + PublishRuntimeRenderLayers(); + }, + [this, layerId](const std::string& message) { + MarkRuntimeBuildFailedForLayer(layerId, message); + LogError("runtime-shader", "Runtime Slang build failed: " + message); + }); +} + +void RuntimeLayerController::StopLayerShaderBuild(const std::string& layerId) +{ + std::unique_ptr bridge; + { + std::lock_guard lock(mShaderBuildMutex); + auto bridgeIt = mShaderBuilds.find(layerId); + if (bridgeIt == mShaderBuilds.end()) + return; + bridge = std::move(bridgeIt->second); + mShaderBuilds.erase(bridgeIt); + } + bridge->Stop(); +} + +void RuntimeLayerController::StopAllRuntimeShaderBuilds() +{ + std::map> builds; + { + std::lock_guard lock(mShaderBuildMutex); + builds.swap(mShaderBuilds); + } + for (auto& entry : builds) + entry.second->Stop(); +} + +void RuntimeLayerController::PublishRuntimeRenderLayers() +{ + if (!mPublisher) + return; + + std::vector renderLayers; + { + std::lock_guard lock(mRuntimeLayerMutex); + renderLayers = mRuntimeLayerModel.Snapshot().renderLayers; + } + mPublisher(renderLayers); +} + +bool RuntimeLayerController::MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact) +{ + std::lock_guard lock(mRuntimeLayerMutex); + std::string error; + if (!mRuntimeLayerModel.MarkBuildReady(artifact, error)) + { + LogWarning("runtime-shader", error); + return false; + } + return true; +} + +void RuntimeLayerController::MarkRuntimeBuildFailedForLayer(const std::string& layerId, const std::string& message) +{ + std::lock_guard lock(mRuntimeLayerMutex); + std::string error; + if (!mRuntimeLayerModel.MarkBuildFailed(layerId, message, error)) + LogWarning("runtime-shader", error); +} + +std::string RuntimeLayerController::FirstRuntimeLayerId() const +{ + std::lock_guard lock(mRuntimeLayerMutex); + return mRuntimeLayerModel.FirstLayerId(); +} + +bool RuntimeLayerController::ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error) +{ + JsonValue root; + std::string parseError; + if (!ParseJson(body.empty() ? "{}" : body, root, parseError) || !root.isObject()) + { + error = parseError.empty() ? "Request body must be a JSON object." : parseError; + return false; + } + + const JsonValue* field = root.find(fieldName); + if (!field || !field->isString() || field->asString().empty()) + { + error = std::string("Request field '") + fieldName + "' must be a non-empty string."; + return false; + } + + value = field->asString(); + error.clear(); + return true; +} +} diff --git a/apps/RenderCadenceCompositor/app/RuntimeLayerController.h b/apps/RenderCadenceCompositor/app/RuntimeLayerController.h new file mode 100644 index 0000000..4b4cc25 --- /dev/null +++ b/apps/RenderCadenceCompositor/app/RuntimeLayerController.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../control/ControlActionResult.h" +#include "../runtime/RuntimeLayerModel.h" +#include "../runtime/RuntimeShaderBridge.h" +#include "../runtime/SupportedShaderCatalog.h" +#include "../telemetry/CadenceTelemetry.h" + +#include +#include +#include +#include +#include +#include + +namespace RenderCadenceCompositor +{ +class RuntimeLayerController +{ +public: + using RenderLayerPublisher = std::function&)>; + + explicit RuntimeLayerController(RenderLayerPublisher publisher = RenderLayerPublisher()); + RuntimeLayerController(const RuntimeLayerController&) = delete; + RuntimeLayerController& operator=(const RuntimeLayerController&) = delete; + ~RuntimeLayerController(); + + void SetPublisher(RenderLayerPublisher publisher); + void Initialize(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames, std::string& runtimeShaderId); + void StartStartupBuild(const std::string& runtimeShaderId); + void Stop(); + + ControlActionResult HandleAddLayer(const std::string& body); + ControlActionResult HandleRemoveLayer(const std::string& body); + + RuntimeLayerModelSnapshot Snapshot(const CadenceTelemetrySnapshot& telemetry) const; + const SupportedShaderCatalog& ShaderCatalog() const { return mShaderCatalog; } + +private: + void LoadSupportedShaderCatalog(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames); + void InitializeLayerModel(std::string& runtimeShaderId); + void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId); + void StopLayerShaderBuild(const std::string& layerId); + void StopAllRuntimeShaderBuilds(); + void PublishRuntimeRenderLayers(); + bool MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact); + void MarkRuntimeBuildFailedForLayer(const std::string& layerId, const std::string& message); + + std::string FirstRuntimeLayerId() const; + static bool ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error); + + RenderLayerPublisher mPublisher; + SupportedShaderCatalog mShaderCatalog; + mutable std::mutex mRuntimeLayerMutex; + RuntimeLayerModel mRuntimeLayerModel; + std::mutex mShaderBuildMutex; + std::map> mShaderBuilds; +}; +} diff --git a/apps/RenderCadenceCompositor/control/ControlActionResult.h b/apps/RenderCadenceCompositor/control/ControlActionResult.h new file mode 100644 index 0000000..3595342 --- /dev/null +++ b/apps/RenderCadenceCompositor/control/ControlActionResult.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace RenderCadenceCompositor +{ +struct ControlActionResult +{ + bool ok = false; + std::string error; +}; +} diff --git a/apps/RenderCadenceCompositor/control/HttpControlServer.h b/apps/RenderCadenceCompositor/control/HttpControlServer.h index 93b2e7f..31f15b8 100644 --- a/apps/RenderCadenceCompositor/control/HttpControlServer.h +++ b/apps/RenderCadenceCompositor/control/HttpControlServer.h @@ -1,5 +1,7 @@ #pragma once +#include "ControlActionResult.h" + #include #include @@ -19,12 +21,6 @@ struct HttpControlServerConfig std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10); }; -struct ControlActionResult -{ - bool ok = false; - std::string error; -}; - struct HttpControlServerCallbacks { std::function getStateJson;