Docs
Some checks failed
CI / Native Windows Build And Tests (push) Failing after 7s
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-03 12:11:53 +10:00
parent eeb8f294bf
commit cccb7a3aa3
7 changed files with 74 additions and 7 deletions

View File

@@ -54,6 +54,8 @@ std::string GuessContentType(const std::filesystem::path& assetPath)
return "text/css";
if (extension == ".json")
return "application/json";
if (extension == ".yaml" || extension == ".yml")
return "application/yaml";
if (extension == ".svg")
return "image/svg+xml";
if (extension == ".png")
@@ -78,9 +80,10 @@ ControlServer::~ControlServer()
Stop();
}
bool ControlServer::Start(const std::filesystem::path& uiRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error)
bool ControlServer::Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error)
{
mUiRoot = uiRoot;
mDocsRoot = docsRoot;
mCallbacks = callbacks;
if (!InitializeWinsock(error))
@@ -246,6 +249,12 @@ ControlServer::HttpResponse ControlServer::ServeGetRequest(const HttpRequest& re
if (request.path == "/api/state")
return { "200 OK", "application/json", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}" };
if (request.path == "/openapi.yaml" || request.path == "/docs/openapi.yaml")
return ServeOpenApiSpec();
if (request.path == "/docs" || request.path == "/docs/")
return ServeSwaggerDocs();
if (request.path.size() > 1)
{
const HttpResponse assetResponse = ServeUiAsset(request.path.substr(1));
@@ -265,6 +274,35 @@ ControlServer::HttpResponse ControlServer::ServeUiAsset(const std::string& relat
: HttpResponse{ "200 OK", contentType, body };
}
ControlServer::HttpResponse ControlServer::ServeOpenApiSpec() const
{
const std::filesystem::path specPath = mDocsRoot / "openapi.yaml";
const std::string body = LoadTextFile(specPath);
return body.empty()
? HttpResponse{ "404 Not Found", "text/plain", "OpenAPI spec not found" }
: HttpResponse{ "200 OK", GuessContentType(specPath), body };
}
ControlServer::HttpResponse ControlServer::ServeSwaggerDocs() const
{
std::ostringstream html;
html << "<!doctype html>\n"
<< "<html lang=\"en\">\n"
<< "<head>\n"
<< " <meta charset=\"utf-8\">\n"
<< " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
<< " <title>Video Shader Toys API Docs</title>\n"
<< " <link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\">\n"
<< "</head>\n"
<< "<body>\n"
<< " <div id=\"swagger-ui\"></div>\n"
<< " <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n"
<< " <script>SwaggerUIBundle({url:'/docs/openapi.yaml',dom_id:'#swagger-ui'});</script>\n"
<< "</body>\n"
<< "</html>\n";
return { "200 OK", "text/html", html.str() };
}
ControlServer::HttpResponse ControlServer::HandleApiPost(const HttpRequest& request)
{
JsonValue root;
@@ -449,12 +487,16 @@ std::string ControlServer::LoadUiAsset(const std::string& relativePath, std::str
return std::string();
const std::filesystem::path assetPath = mUiRoot / sanitizedPath;
std::ifstream input(assetPath, std::ios::binary);
contentType = GuessContentType(assetPath);
return LoadTextFile(assetPath);
}
std::string ControlServer::LoadTextFile(const std::filesystem::path& path) const
{
std::ifstream input(path, std::ios::binary);
if (!input)
return std::string();
contentType = GuessContentType(assetPath);
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();

View File

@@ -37,7 +37,7 @@ public:
ControlServer();
~ControlServer();
bool Start(const std::filesystem::path& uiRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
void Stop();
void BroadcastState();
@@ -76,11 +76,14 @@ private:
HttpResponse RouteHttpRequest(const HttpRequest& request);
HttpResponse ServeGetRequest(const HttpRequest& request) const;
HttpResponse ServeUiAsset(const std::string& relativePath) const;
HttpResponse ServeOpenApiSpec() const;
HttpResponse ServeSwaggerDocs() const;
HttpResponse HandleApiPost(const HttpRequest& request);
bool InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError);
bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
void BroadcastStateLocked();
std::string LoadUiAsset(const std::string& relativePath, std::string& contentType) const;
std::string LoadTextFile(const std::filesystem::path& path) const;
std::string BuildJsonResponse(bool success, const std::string& error = std::string()) const;
static std::string Base64Encode(const unsigned char* data, DWORD dataLength);
static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
@@ -89,6 +92,7 @@ private:
private:
std::filesystem::path mUiRoot;
std::filesystem::path mDocsRoot;
Callbacks mCallbacks;
UniqueSocket mListenSocket;
unsigned short mPort;

View File

@@ -836,7 +836,7 @@ bool OpenGLComposite::InitOpenGLState()
return true;
};
if (!mControlServer->Start(mRuntimeHost->GetUiRoot(), mRuntimeHost->GetServerPort(), callbacks, runtimeError))
if (!mControlServer->Start(mRuntimeHost->GetUiRoot(), mRuntimeHost->GetDocsRoot(), mRuntimeHost->GetServerPort(), callbacks, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Local control server failed to start", MB_OK);
return false;

View File

@@ -1388,6 +1388,7 @@ bool RuntimeHost::ResolvePaths(std::string& error)
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
mDocsRoot = mRepoRoot / "docs";
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
mRuntimeRoot = mRepoRoot / "runtime";

View File

@@ -45,6 +45,7 @@ public:
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; }
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
unsigned short GetServerPort() const { return mServerPort; }
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
@@ -118,6 +119,7 @@ private:
PersistentState mPersistentState;
std::filesystem::path mRepoRoot;
std::filesystem::path mUiRoot;
std::filesystem::path mDocsRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mPresetRoot;