Docs
This commit is contained in:
15
README.md
15
README.md
@@ -143,7 +143,20 @@ The local REST control API is documented as an OpenAPI/Swagger spec:
|
|||||||
docs/openapi.yaml
|
docs/openapi.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Load that file in Swagger UI, Redoc, or another OpenAPI viewer to inspect the `/api/state`, layer control, stack preset, and reload endpoints. Live state updates are also sent over the `/ws` WebSocket.
|
When the control server is running, the same spec is also served at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:<serverPort>/docs/openapi.yaml
|
||||||
|
http://127.0.0.1:<serverPort>/openapi.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
A Swagger UI page is available at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:<serverPort>/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
Use those docs to inspect the `/api/state`, layer control, stack preset, and reload endpoints. Live state updates are also sent over the `/ws` WebSocket.
|
||||||
|
|
||||||
## Shader Packages
|
## Shader Packages
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ std::string GuessContentType(const std::filesystem::path& assetPath)
|
|||||||
return "text/css";
|
return "text/css";
|
||||||
if (extension == ".json")
|
if (extension == ".json")
|
||||||
return "application/json";
|
return "application/json";
|
||||||
|
if (extension == ".yaml" || extension == ".yml")
|
||||||
|
return "application/yaml";
|
||||||
if (extension == ".svg")
|
if (extension == ".svg")
|
||||||
return "image/svg+xml";
|
return "image/svg+xml";
|
||||||
if (extension == ".png")
|
if (extension == ".png")
|
||||||
@@ -78,9 +80,10 @@ ControlServer::~ControlServer()
|
|||||||
Stop();
|
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;
|
mUiRoot = uiRoot;
|
||||||
|
mDocsRoot = docsRoot;
|
||||||
mCallbacks = callbacks;
|
mCallbacks = callbacks;
|
||||||
|
|
||||||
if (!InitializeWinsock(error))
|
if (!InitializeWinsock(error))
|
||||||
@@ -246,6 +249,12 @@ ControlServer::HttpResponse ControlServer::ServeGetRequest(const HttpRequest& re
|
|||||||
if (request.path == "/api/state")
|
if (request.path == "/api/state")
|
||||||
return { "200 OK", "application/json", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}" };
|
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)
|
if (request.path.size() > 1)
|
||||||
{
|
{
|
||||||
const HttpResponse assetResponse = ServeUiAsset(request.path.substr(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 };
|
: 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)
|
ControlServer::HttpResponse ControlServer::HandleApiPost(const HttpRequest& request)
|
||||||
{
|
{
|
||||||
JsonValue root;
|
JsonValue root;
|
||||||
@@ -449,12 +487,16 @@ std::string ControlServer::LoadUiAsset(const std::string& relativePath, std::str
|
|||||||
return std::string();
|
return std::string();
|
||||||
|
|
||||||
const std::filesystem::path assetPath = mUiRoot / sanitizedPath;
|
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)
|
if (!input)
|
||||||
return std::string();
|
return std::string();
|
||||||
|
|
||||||
contentType = GuessContentType(assetPath);
|
|
||||||
|
|
||||||
std::ostringstream buffer;
|
std::ostringstream buffer;
|
||||||
buffer << input.rdbuf();
|
buffer << input.rdbuf();
|
||||||
return buffer.str();
|
return buffer.str();
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public:
|
|||||||
ControlServer();
|
ControlServer();
|
||||||
~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 Stop();
|
||||||
void BroadcastState();
|
void BroadcastState();
|
||||||
|
|
||||||
@@ -76,11 +76,14 @@ private:
|
|||||||
HttpResponse RouteHttpRequest(const HttpRequest& request);
|
HttpResponse RouteHttpRequest(const HttpRequest& request);
|
||||||
HttpResponse ServeGetRequest(const HttpRequest& request) const;
|
HttpResponse ServeGetRequest(const HttpRequest& request) const;
|
||||||
HttpResponse ServeUiAsset(const std::string& relativePath) const;
|
HttpResponse ServeUiAsset(const std::string& relativePath) const;
|
||||||
|
HttpResponse ServeOpenApiSpec() const;
|
||||||
|
HttpResponse ServeSwaggerDocs() const;
|
||||||
HttpResponse HandleApiPost(const HttpRequest& request);
|
HttpResponse HandleApiPost(const HttpRequest& request);
|
||||||
bool InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError);
|
bool InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError);
|
||||||
bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
|
bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
|
||||||
void BroadcastStateLocked();
|
void BroadcastStateLocked();
|
||||||
std::string LoadUiAsset(const std::string& relativePath, std::string& contentType) const;
|
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;
|
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 Base64Encode(const unsigned char* data, DWORD dataLength);
|
||||||
static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
|
static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
|
||||||
@@ -89,6 +92,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::filesystem::path mUiRoot;
|
std::filesystem::path mUiRoot;
|
||||||
|
std::filesystem::path mDocsRoot;
|
||||||
Callbacks mCallbacks;
|
Callbacks mCallbacks;
|
||||||
UniqueSocket mListenSocket;
|
UniqueSocket mListenSocket;
|
||||||
unsigned short mPort;
|
unsigned short mPort;
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ bool OpenGLComposite::InitOpenGLState()
|
|||||||
return true;
|
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);
|
MessageBoxA(NULL, runtimeError.c_str(), "Local control server failed to start", MB_OK);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1388,6 +1388,7 @@ bool RuntimeHost::ResolvePaths(std::string& error)
|
|||||||
|
|
||||||
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
|
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
|
||||||
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
|
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
|
||||||
|
mDocsRoot = mRepoRoot / "docs";
|
||||||
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
|
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
|
||||||
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
|
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
|
||||||
mRuntimeRoot = mRepoRoot / "runtime";
|
mRuntimeRoot = mRepoRoot / "runtime";
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public:
|
|||||||
|
|
||||||
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
|
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
|
||||||
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
||||||
|
const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; }
|
||||||
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
||||||
unsigned short GetServerPort() const { return mServerPort; }
|
unsigned short GetServerPort() const { return mServerPort; }
|
||||||
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||||
@@ -118,6 +119,7 @@ private:
|
|||||||
PersistentState mPersistentState;
|
PersistentState mPersistentState;
|
||||||
std::filesystem::path mRepoRoot;
|
std::filesystem::path mRepoRoot;
|
||||||
std::filesystem::path mUiRoot;
|
std::filesystem::path mUiRoot;
|
||||||
|
std::filesystem::path mDocsRoot;
|
||||||
std::filesystem::path mShaderRoot;
|
std::filesystem::path mShaderRoot;
|
||||||
std::filesystem::path mRuntimeRoot;
|
std::filesystem::path mRuntimeRoot;
|
||||||
std::filesystem::path mPresetRoot;
|
std::filesystem::path mPresetRoot;
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ Tracked files:
|
|||||||
|
|
||||||
- `templates/`: source templates used to generate shader runtime code.
|
- `templates/`: source templates used to generate shader runtime code.
|
||||||
|
|
||||||
|
Packaged documentation:
|
||||||
|
|
||||||
|
- `../docs/openapi.yaml`: OpenAPI/Swagger spec for the local control API.
|
||||||
|
- `http://127.0.0.1:<serverPort>/docs`: Swagger UI page served by the native control server.
|
||||||
|
|
||||||
Generated files:
|
Generated files:
|
||||||
|
|
||||||
- `shader_cache/active_shader_wrapper.slang`: generated Slang wrapper for the active shader/layer.
|
- `shader_cache/active_shader_wrapper.slang`: generated Slang wrapper for the active shader/layer.
|
||||||
|
|||||||
Reference in New Issue
Block a user