OSC stubs
This commit is contained in:
@@ -270,7 +270,7 @@ Use those docs to inspect the `/api/state`, layer control, and reload endpoints.
|
||||
|
||||
The control UI has a **Reload shaders** button. It rescans `shaders/`, re-reads manifests, refreshes shader availability/errors, updates active layer parameter definitions from changed manifests, and queues recompilation for every catalog-valid layer in the active stack. Missing shader packages are marked failed, and the previous working render stack remains active where possible until replacement builds commit successfully.
|
||||
|
||||
Each parameter row still exposes the intended OSC route in the UI, but OSC ingress is not wired in the current native host.
|
||||
Each parameter row still exposes the intended OSC route in the UI. The native host has an OSC service stub that reports the configured bind/port in state, but it does not open a UDP listener or dispatch OSC messages yet.
|
||||
|
||||
The control UI currently still shows preset and screenshot controls from the intended route surface. Those endpoints return an unimplemented action result in the native host until their backend paths are wired.
|
||||
|
||||
|
||||
@@ -25,20 +25,20 @@
|
||||
"oscBindAddress": {
|
||||
"type": "string",
|
||||
"default": "0.0.0.0",
|
||||
"description": "OSC bind address reserved for the control surface. The current native host exposes this in state but does not start the OSC listener yet."
|
||||
"description": "OSC bind address reserved for the control surface. The current native host reports this through an OSC status stub but does not start the UDP listener yet."
|
||||
},
|
||||
"oscPort": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"minimum": 0,
|
||||
"maximum": 65535,
|
||||
"default": 9000,
|
||||
"description": "OSC UDP port reserved for the control surface."
|
||||
"description": "OSC UDP port reserved for the control surface. Use 0 to mark the OSC stub disabled."
|
||||
},
|
||||
"oscSmoothing": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"default": 0.18,
|
||||
"description": "Reserved OSC smoothing amount exposed in runtime state."
|
||||
"description": "Reserved OSC smoothing amount reported through the OSC status stub."
|
||||
},
|
||||
"input": {
|
||||
"$ref": "#/$defs/input"
|
||||
|
||||
@@ -34,7 +34,7 @@ Primary source areas:
|
||||
- `src/runtime/shader`: background Slang build bridge and prepared shader artifact types
|
||||
- `src/runtime/state`: runtime JSON helpers, parameter normalization, and debounced runtime-state persistence
|
||||
- `src/runtime/text`: MSDF/MTSDF font atlas build and CPU-side prepared text texture composition
|
||||
- `src/control`: command parsing, HTTP/WebSocket transport helpers, OpenAPI state JSON
|
||||
- `src/control`: command parsing, HTTP/WebSocket transport helpers, OSC status stub, OpenAPI state JSON
|
||||
- `src/app/RenderCadenceHttpRoutes.*`: this app's current HTTP endpoint map
|
||||
- `src/preview`: optional non-consuming preview window
|
||||
- `src/telemetry` and `src/logging`: runtime observation and logging
|
||||
@@ -170,6 +170,8 @@ Unsupported routes return an action response with `ok: false`.
|
||||
|
||||
Forks can reuse the HTTP/WebSocket shell without keeping these endpoints by installing a different route callback.
|
||||
|
||||
`OscControlServer` is currently a lifecycle/status stub. It consumes startup OSC config, exposes configured/disabled/not-listening state through `/api/state`, and leaves UDP socket receive, OSC decode, and runtime dispatch unimplemented until that ingress boundary is built deliberately.
|
||||
|
||||
## Tests
|
||||
|
||||
Native tests cover the main non-GL contracts:
|
||||
|
||||
@@ -21,7 +21,7 @@ These parts are the useful base for the fork:
|
||||
- `src/render/readback`: BGRA8/UYVY8 PBO readback and completed-frame publication.
|
||||
- `src/platform`: hidden GL window/context support.
|
||||
- `src/app`: startup, config, video backend factory, runtime layer orchestration, preview, telemetry, and HTTP server hookup.
|
||||
- `src/control/http`, `src/telemetry`, `src/logging`, and `ui`: useful if the new repo still wants a local control surface.
|
||||
- `src/control/http`, `src/control/osc`, `src/telemetry`, `src/logging`, and `ui`: useful if the new repo still wants a local control surface. `control/osc` is currently a status/lifecycle stub, not a UDP listener.
|
||||
- `src/app/RenderCadenceHttpRoutes.*`: useful only if the new repo keeps this app's current `/api/...` control surface.
|
||||
|
||||
## Replace Or Rework
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# OSC Control
|
||||
|
||||
This is the intended OSC control contract, but OSC ingress is not wired in the current `RenderCadenceCompositor` native host yet. The config fields and UI copy buttons are present for compatibility; use the REST layer parameter endpoints or the control UI for live parameter changes today.
|
||||
This is the intended OSC control contract, but OSC ingress is not wired in the current `RenderCadenceCompositor` native host yet. The native host has an OSC service stub that consumes config and reports status in `/api/state`; it does not open a UDP listener or dispatch messages yet. Use the REST layer parameter endpoints or the control UI for live parameter changes today.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -14,7 +14,7 @@ Set the UDP port in `config/runtime-host.json`:
|
||||
}
|
||||
```
|
||||
|
||||
When OSC ingress is implemented, `oscPort: 0` should disable the OSC listener, `oscBindAddress: "127.0.0.1"` should keep OSC local to the host, and `oscBindAddress: "0.0.0.0"` should listen on all IPv4 interfaces. `oscSmoothing` is reserved for a per-frame easing amount on numeric OSC controls.
|
||||
Today, `oscPort: 0` marks the OSC stub disabled and any nonzero port marks it configured but not listening. When OSC ingress is implemented, `oscBindAddress: "127.0.0.1"` should keep OSC local to the host, and `oscBindAddress: "0.0.0.0"` should listen on all IPv4 interfaces. `oscSmoothing` is reserved for a per-frame easing amount on numeric OSC controls.
|
||||
|
||||
## Address Pattern
|
||||
|
||||
|
||||
@@ -730,6 +730,8 @@ components:
|
||||
type: number
|
||||
previewFps:
|
||||
type: number
|
||||
osc:
|
||||
$ref: "#/components/schemas/AppOscStatus"
|
||||
input:
|
||||
type: object
|
||||
properties:
|
||||
@@ -771,6 +773,24 @@ components:
|
||||
alphaRequired:
|
||||
type: boolean
|
||||
description: General output alpha request. When true, automatic output pixel-format selection uses an alpha-carrying system-frame format.
|
||||
AppOscStatus:
|
||||
type: object
|
||||
properties:
|
||||
configured:
|
||||
type: boolean
|
||||
description: True when OSC has a nonzero configured port.
|
||||
listening:
|
||||
type: boolean
|
||||
description: False in the current native host because UDP OSC ingress is only stubbed.
|
||||
bindAddress:
|
||||
type: string
|
||||
port:
|
||||
type: number
|
||||
smoothing:
|
||||
type: number
|
||||
statusMessage:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
RuntimeStatus:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -199,9 +199,9 @@ Currently consumed fields:
|
||||
|
||||
- `serverPort`
|
||||
- `shaderLibrary`
|
||||
- `oscBindAddress` (reported for compatibility; OSC ingress is not wired yet)
|
||||
- `oscPort` (reported for compatibility; OSC ingress is not wired yet)
|
||||
- `oscSmoothing` (reported for compatibility; OSC ingress is not wired yet)
|
||||
- `oscBindAddress` (reported through the OSC service stub; UDP ingress is not wired yet)
|
||||
- `oscPort` (use `0` to mark the OSC stub disabled; UDP ingress is not wired yet)
|
||||
- `oscSmoothing` (reported through the OSC service stub; smoothing is reserved for future OSC ingress)
|
||||
- `input.backend`
|
||||
- `input.device`
|
||||
- `input.resolution`
|
||||
@@ -263,6 +263,8 @@ Current endpoints:
|
||||
|
||||
The HTTP server runs on its own thread. `control/http/HttpControlServer` owns socket lifetime, HTTP parsing, static UI/docs helpers, and WebSocket transport. The Render Cadence endpoint map lives in `app/RenderCadenceHttpRoutes`, which samples/copies telemetry through callbacks and translates POST bodies into runtime control commands. A fork can keep the HTTP/WebSocket shell and install a different route callback without inheriting this app's `/api/...` surface. Command execution is app-owned, so future OSC ingress can create the same commands without depending on HTTP route code. Control commands may update the display layer model, request debounced runtime-state persistence, start background shader builds, or publish an already-built render-layer snapshot, but they do not call render work or DeckLink scheduling directly.
|
||||
|
||||
`control/osc/OscControlServer` is currently a lifecycle/status stub. It consumes the startup OSC config and reports whether OSC is configured or disabled, but it deliberately does not open a UDP socket or dispatch parameter changes yet.
|
||||
|
||||
## Optional DeckLink Output
|
||||
|
||||
DeckLink output is an optional edge service in this app.
|
||||
@@ -464,6 +466,7 @@ This app keeps the same core behavior but splits it into modules that can grow:
|
||||
- `runtime/text/`: font atlas build and prepared text texture composition
|
||||
- `control/`: control action results and runtime-state JSON presentation
|
||||
- `control/http/`: local HTTP transport, static UI/OpenAPI serving helpers, and WebSocket updates
|
||||
- `control/osc/`: OSC service lifecycle/status stub; no UDP listener or dispatch yet
|
||||
- `app/RenderCadenceHttpRoutes`: this app's `/api/...` endpoint map behind the reusable HTTP server route callback
|
||||
- `json/`: compact JSON serialization helpers
|
||||
- `video/`: DeckLink output wrapper and scheduling thread
|
||||
|
||||
@@ -51,6 +51,17 @@ void ApplyPort(const JsonValue& root, const char* key, unsigned short& target)
|
||||
target = static_cast<unsigned short>(port);
|
||||
}
|
||||
|
||||
void ApplyOptionalPort(const JsonValue& root, const char* key, unsigned short& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (!value || !value->isNumber())
|
||||
return;
|
||||
|
||||
const double port = value->asNumber();
|
||||
if (port >= 0.0 && port <= 65535.0)
|
||||
target = static_cast<unsigned short>(port);
|
||||
}
|
||||
|
||||
JsonValue NumberValue(double value)
|
||||
{
|
||||
return JsonValue(value);
|
||||
@@ -136,7 +147,7 @@ bool ApplyAppConfigJson(const JsonValue& root, AppConfig& config, std::string* e
|
||||
ApplyString(root, "shaderLibrary", config.shaderLibrary);
|
||||
ApplyPort(root, "serverPort", config.http.preferredPort);
|
||||
ApplyString(root, "oscBindAddress", config.oscBindAddress);
|
||||
ApplyPort(root, "oscPort", config.oscPort);
|
||||
ApplyOptionalPort(root, "oscPort", config.oscPort);
|
||||
ApplyDouble(root, "oscSmoothing", config.oscSmoothing);
|
||||
ApplyInputConfig(root, config);
|
||||
ApplyOutputConfig(root, config);
|
||||
|
||||
@@ -81,6 +81,17 @@ void ApplyPort(const JsonValue& root, const char* key, unsigned short& target)
|
||||
target = static_cast<unsigned short>(port);
|
||||
}
|
||||
|
||||
void ApplyOptionalPort(const JsonValue& root, const char* key, unsigned short& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (!value || !value->isNumber())
|
||||
return;
|
||||
|
||||
const double port = value->asNumber();
|
||||
if (port >= 0.0 && port <= 65535.0)
|
||||
target = static_cast<unsigned short>(port);
|
||||
}
|
||||
|
||||
void ApplyInputConfig(const JsonValue& root, AppConfig& config)
|
||||
{
|
||||
const JsonValue* input = Find(root, "input");
|
||||
@@ -159,7 +170,7 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
|
||||
ApplyString(root, "shaderLibrary", mConfig.shaderLibrary);
|
||||
ApplyPort(root, "serverPort", mConfig.http.preferredPort);
|
||||
ApplyString(root, "oscBindAddress", mConfig.oscBindAddress);
|
||||
ApplyPort(root, "oscPort", mConfig.oscPort);
|
||||
ApplyOptionalPort(root, "oscPort", mConfig.oscPort);
|
||||
ApplyDouble(root, "oscSmoothing", mConfig.oscSmoothing);
|
||||
ApplyInputConfig(root, mConfig);
|
||||
ApplyOutputConfig(root, mConfig);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "RuntimeLayerController.h"
|
||||
#include "../logging/Logger.h"
|
||||
#include "../control/RuntimeStateJson.h"
|
||||
#include "../control/osc/OscControlServer.h"
|
||||
#include "../json/JsonWriter.h"
|
||||
#include "../preview/PreviewWindowThread.h"
|
||||
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||
@@ -115,6 +116,7 @@ public:
|
||||
StartPreviewWindow();
|
||||
StartOptionalVideoOutput();
|
||||
mTelemetryHealth.Start(mFrameExchange, *mOutput, mOutputThread, mRenderThread);
|
||||
StartOscServer();
|
||||
StartHttpServer();
|
||||
Log("app", "RenderCadenceCompositor started.");
|
||||
mStarted = true;
|
||||
@@ -124,6 +126,7 @@ public:
|
||||
void Stop()
|
||||
{
|
||||
mHttpServer.Stop();
|
||||
mOscServer.Stop();
|
||||
mTelemetryHealth.Stop();
|
||||
mPreviewWindow.Stop();
|
||||
mOutputThread.Stop();
|
||||
@@ -325,10 +328,29 @@ private:
|
||||
mVideoOutputEnabled,
|
||||
mVideoOutputStatus,
|
||||
mRuntimeLayers.ShaderCatalog(),
|
||||
layerSnapshot
|
||||
layerSnapshot,
|
||||
&mOscServer.State()
|
||||
});
|
||||
}
|
||||
|
||||
void StartOscServer()
|
||||
{
|
||||
OscControlServerConfig oscConfig;
|
||||
oscConfig.bindAddress = mConfig.oscBindAddress;
|
||||
oscConfig.port = mConfig.oscPort;
|
||||
oscConfig.smoothing = mConfig.oscSmoothing;
|
||||
|
||||
std::string error;
|
||||
if (!mOscServer.Start(oscConfig, error))
|
||||
{
|
||||
LogWarning("osc", "OSC control stub did not start: " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mOscServer.State().statusMessage.empty())
|
||||
Log("osc", mOscServer.State().statusMessage);
|
||||
}
|
||||
|
||||
std::string BuildConfigJson() const
|
||||
{
|
||||
AppConfig diskConfig = mConfig;
|
||||
@@ -442,6 +464,7 @@ private:
|
||||
TelemetryHealthMonitor mTelemetryHealth;
|
||||
CadenceTelemetry mHttpTelemetry;
|
||||
HttpControlServer mHttpServer;
|
||||
OscControlServer mOscServer;
|
||||
PreviewWindowThread mPreviewWindow;
|
||||
RuntimeLayerController mRuntimeLayers;
|
||||
std::function<VideoInputEdgeMetrics()> mVideoInputMetricsProvider;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../app/AppConfig.h"
|
||||
#include "../app/AppConfigProvider.h"
|
||||
#include "../control/osc/OscControlServer.h"
|
||||
#include "../json/JsonWriter.h"
|
||||
#include "RuntimeLayerModel.h"
|
||||
#include "SupportedShaderCatalog.h"
|
||||
@@ -22,6 +23,7 @@ struct RuntimeStateJsonInput
|
||||
std::string videoOutputStatus;
|
||||
const SupportedShaderCatalog& shaderCatalog;
|
||||
const RuntimeLayerModelSnapshot& runtimeLayers;
|
||||
const OscControlServerState* osc = nullptr;
|
||||
};
|
||||
|
||||
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
@@ -279,6 +281,27 @@ inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& inp
|
||||
writer.EndArray();
|
||||
}
|
||||
|
||||
inline void WriteOscJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||
{
|
||||
const bool configured = input.osc ? input.osc->configured : input.config.oscPort != 0;
|
||||
const bool listening = input.osc ? input.osc->listening : false;
|
||||
const std::string bindAddress = input.osc ? input.osc->bindAddress : input.config.oscBindAddress;
|
||||
const unsigned short port = input.osc ? input.osc->port : input.config.oscPort;
|
||||
const double smoothing = input.osc ? input.osc->smoothing : input.config.oscSmoothing;
|
||||
const std::string status = input.osc
|
||||
? input.osc->statusMessage
|
||||
: (configured ? "OSC ingress is not implemented yet." : "OSC ingress disabled by config.");
|
||||
|
||||
writer.BeginObject();
|
||||
writer.KeyBool("configured", configured);
|
||||
writer.KeyBool("listening", listening);
|
||||
writer.KeyString("bindAddress", bindAddress);
|
||||
writer.KeyUInt("port", port);
|
||||
writer.KeyDouble("smoothing", smoothing);
|
||||
writer.KeyString("statusMessage", status);
|
||||
writer.EndObject();
|
||||
}
|
||||
|
||||
inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
{
|
||||
JsonWriter writer;
|
||||
@@ -293,6 +316,8 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
writer.KeyBool("autoReload", input.config.autoReload);
|
||||
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
|
||||
writer.KeyDouble("previewFps", input.config.previewFps);
|
||||
writer.Key("osc");
|
||||
WriteOscJson(writer, input);
|
||||
writer.Key("input");
|
||||
writer.BeginObject();
|
||||
writer.KeyString("backend", input.config.input.backend);
|
||||
|
||||
40
src/control/osc/OscControlServer.cpp
Normal file
40
src/control/osc/OscControlServer.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "OscControlServer.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
bool OscControlServer::Start(OscControlServerConfig config, std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
Stop();
|
||||
|
||||
mConfig = std::move(config);
|
||||
mState.bindAddress = mConfig.bindAddress;
|
||||
mState.port = mConfig.port;
|
||||
mState.smoothing = mConfig.smoothing;
|
||||
|
||||
if (mConfig.port == 0)
|
||||
{
|
||||
mState.configured = false;
|
||||
mState.listening = false;
|
||||
mState.statusMessage = "OSC ingress disabled by config.";
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostringstream status;
|
||||
status << "OSC ingress stub configured at " << mConfig.bindAddress << ":" << mConfig.port
|
||||
<< "; UDP listener is not implemented yet.";
|
||||
mState.configured = true;
|
||||
mState.listening = false;
|
||||
mState.statusMessage = status.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OscControlServer::Stop()
|
||||
{
|
||||
mConfig = OscControlServerConfig();
|
||||
mState = OscControlServerState();
|
||||
}
|
||||
}
|
||||
38
src/control/osc/OscControlServer.h
Normal file
38
src/control/osc/OscControlServer.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
struct OscControlServerConfig
|
||||
{
|
||||
std::string bindAddress = "127.0.0.1";
|
||||
unsigned short port = 9000;
|
||||
double smoothing = 0.0;
|
||||
};
|
||||
|
||||
struct OscControlServerState
|
||||
{
|
||||
bool configured = false;
|
||||
bool listening = false;
|
||||
std::string bindAddress;
|
||||
unsigned short port = 0;
|
||||
double smoothing = 0.0;
|
||||
std::string statusMessage;
|
||||
};
|
||||
|
||||
class OscControlServer
|
||||
{
|
||||
public:
|
||||
bool Start(OscControlServerConfig config, std::string& error);
|
||||
void Stop();
|
||||
|
||||
bool IsConfigured() const { return mState.configured; }
|
||||
bool IsListening() const { return mState.listening; }
|
||||
const OscControlServerState& State() const { return mState; }
|
||||
|
||||
private:
|
||||
OscControlServerConfig mConfig;
|
||||
OscControlServerState mState;
|
||||
};
|
||||
}
|
||||
@@ -98,6 +98,11 @@ add_video_shader_test(RenderCadenceCompositorHttpControlServerTests
|
||||
)
|
||||
target_link_libraries(RenderCadenceCompositorHttpControlServerTests PRIVATE Ws2_32)
|
||||
|
||||
add_video_shader_test(OscControlServerTests
|
||||
"${SRC_DIR}/control/osc/OscControlServer.cpp"
|
||||
"${TEST_DIR}/OscControlServerTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RenderCadenceCompositorAppConfigProviderTests
|
||||
"${SRC_DIR}/app/AppConfig.cpp"
|
||||
"${SRC_DIR}/app/AppRestart.cpp"
|
||||
|
||||
81
tests/OscControlServerTests.cpp
Normal file
81
tests/OscControlServerTests.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "osc/OscControlServer.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
int gFailures = 0;
|
||||
|
||||
void Expect(bool condition, const std::string& message)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
++gFailures;
|
||||
std::cerr << "FAILED: " << message << "\n";
|
||||
}
|
||||
|
||||
void ExpectEquals(const std::string& actual, const std::string& expected, const std::string& message)
|
||||
{
|
||||
if (actual == expected)
|
||||
return;
|
||||
|
||||
++gFailures;
|
||||
std::cerr << "FAILED: " << message << "\n"
|
||||
<< "expected: " << expected << "\n"
|
||||
<< "actual: " << actual << "\n";
|
||||
}
|
||||
|
||||
void TestDisabledWhenPortIsZero()
|
||||
{
|
||||
using namespace RenderCadenceCompositor;
|
||||
|
||||
OscControlServer server;
|
||||
OscControlServerConfig config;
|
||||
config.bindAddress = "127.0.0.1";
|
||||
config.port = 0;
|
||||
config.smoothing = 0.25;
|
||||
|
||||
std::string error;
|
||||
Expect(server.Start(config, error), "disabled OSC stub starts successfully");
|
||||
Expect(error.empty(), "disabled OSC stub does not report an error");
|
||||
Expect(!server.IsConfigured(), "port zero leaves OSC unconfigured");
|
||||
Expect(!server.IsListening(), "port zero does not listen");
|
||||
ExpectEquals(server.State().statusMessage, "OSC ingress disabled by config.", "disabled status is explicit");
|
||||
}
|
||||
|
||||
void TestConfiguredStubDoesNotListenYet()
|
||||
{
|
||||
using namespace RenderCadenceCompositor;
|
||||
|
||||
OscControlServer server;
|
||||
OscControlServerConfig config;
|
||||
config.bindAddress = "0.0.0.0";
|
||||
config.port = 9000;
|
||||
config.smoothing = 0.18;
|
||||
|
||||
std::string error;
|
||||
Expect(server.Start(config, error), "configured OSC stub starts successfully");
|
||||
Expect(error.empty(), "configured OSC stub does not report an error");
|
||||
Expect(server.IsConfigured(), "nonzero port marks OSC as configured");
|
||||
Expect(!server.IsListening(), "stub does not claim to listen before UDP ingress exists");
|
||||
ExpectEquals(server.State().bindAddress, "0.0.0.0", "bind address is retained");
|
||||
Expect(server.State().statusMessage.find("UDP listener is not implemented yet") != std::string::npos, "status reports stub state");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
TestDisabledWhenPortIsZero();
|
||||
TestConfiguredStubDoesNotListenYet();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
std::cerr << gFailures << " OscControlServer test failure(s).\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "OscControlServer tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -115,6 +115,27 @@ void TestCommandLineOverrides()
|
||||
Expect(config.http.preferredPort == 8282, "port CLI override applies");
|
||||
}
|
||||
|
||||
void TestOscPortZeroIsAllowed()
|
||||
{
|
||||
using namespace RenderCadenceCompositor;
|
||||
|
||||
const std::filesystem::path path = std::filesystem::temp_directory_path() / "render-cadence-compositor-config-osc-disabled-test.json";
|
||||
std::ofstream output(path, std::ios::binary);
|
||||
output << "{ \"oscPort\": 0 }\n";
|
||||
output.close();
|
||||
|
||||
std::string error;
|
||||
AppConfigProvider provider;
|
||||
Expect(provider.Load(path, error), "provider accepts oscPort zero");
|
||||
Expect(provider.Config().oscPort == 0, "provider loads oscPort zero");
|
||||
|
||||
AppConfig parsed;
|
||||
Expect(ParseAppConfigJson("{\"oscPort\":0}", parsed, error), "config JSON parser accepts oscPort zero");
|
||||
Expect(parsed.oscPort == 0, "config JSON parser loads oscPort zero");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
void TestPreviewDefaultsAreOptIn()
|
||||
{
|
||||
using namespace RenderCadenceCompositor;
|
||||
@@ -216,6 +237,7 @@ int main()
|
||||
{
|
||||
TestLoadsRuntimeHostConfig();
|
||||
TestCommandLineOverrides();
|
||||
TestOscPortZeroIsAllowed();
|
||||
TestPreviewDefaultsAreOptIn();
|
||||
TestConfigJsonRoundTrip();
|
||||
TestOutputAlphaNormalizesLegacyKeying();
|
||||
|
||||
@@ -119,6 +119,7 @@ int main()
|
||||
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
|
||||
ExpectContains(json, "\"input\":{\"backend\":\"decklink\",\"device\":\"default\",\"resolution\":\"1080p\",\"frameRate\":\"59.94\"}", "state JSON should expose nested input config");
|
||||
ExpectContains(json, "\"output\":{\"backend\":\"decklink\",\"device\":\"default\",\"resolution\":\"1080p\",\"frameRate\":\"59.94\",\"pixelFormat\":\"auto\",\"systemFramePixelFormat\":\"8-bit BGRA\",\"keying\"", "state JSON should expose nested output config");
|
||||
ExpectContains(json, "\"osc\":{\"configured\":true,\"listening\":false", "state JSON should expose OSC stub status");
|
||||
ExpectContains(json, "\"videoOutput\":{\"enabled\":true,\"backend\":\"decklink\"", "state JSON should expose neutral video output status");
|
||||
ExpectContains(json, "\"scheduleFailures\":2", "state JSON should expose neutral video output schedule failures");
|
||||
ExpectContains(json, "\"backendMetrics\":{\"bufferedAvailable\":true,\"buffered\":4", "state JSON should expose backend-specific video output metrics");
|
||||
|
||||
@@ -410,7 +410,7 @@ export function ConfigEditor({ onClose }) {
|
||||
<h4>OSC</h4>
|
||||
<div className="config-fields">
|
||||
<TextField config={draft} label="Bind address" path="oscBindAddress" setConfig={setDraft} />
|
||||
<NumberField config={draft} label="Port" min={1} path="oscPort" setConfig={setDraft} />
|
||||
<NumberField config={draft} label="Port" min={0} path="oscPort" setConfig={setDraft} />
|
||||
<NumberField config={draft} label="Smoothing" min={0} path="oscSmoothing" setConfig={setDraft} step={0.01} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user