Files
video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp
Aiden 5cbdbd6813
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m48s
CI / Windows Release Package (push) Has been cancelled
Pass 3
2026-05-11 02:06:17 +10:00

587 lines
16 KiB
C++

#include "RuntimeStore.h"
#include "RuntimeStatePresenter.h"
#include <cctype>
#include <fstream>
#include <mutex>
#include <random>
#include <sstream>
#include <windows.h>
namespace
{
std::string ToLowerCopy(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
return text;
}
double GenerateStartupRandom()
{
std::random_device randomDevice;
std::uniform_real_distribution<double> distribution(0.0, 1.0);
return distribution(randomDevice);
}
}
RuntimeStore::RuntimeStore() :
mRenderSnapshotBuilder(*this),
mHealthTelemetry(),
mReloadRequested(false),
mCompileSucceeded(false),
mStartupRandom(GenerateStartupRandom()),
mServerPort(8080),
mAutoReloadEnabled(true),
mStartTime(std::chrono::steady_clock::now()),
mLastScanTime((std::chrono::steady_clock::time_point::min)())
{
}
HealthTelemetry& RuntimeStore::GetHealthTelemetry()
{
return mHealthTelemetry;
}
const HealthTelemetry& RuntimeStore::GetHealthTelemetry() const
{
return mHealthTelemetry;
}
RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder()
{
return mRenderSnapshotBuilder;
}
const RenderSnapshotBuilder& RuntimeStore::GetRenderSnapshotBuilder() const
{
return mRenderSnapshotBuilder;
}
bool RuntimeStore::InitializeStore(std::string& error)
{
try
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mConfigStore.Initialize(error))
return false;
if (!LoadPersistentState(error))
return false;
if (!ScanShaderPackages(error))
return false;
mLayerStack.NormalizeLayerIds();
mLayerStack.EnsureDefaultsForAllLayers(mShaderCatalog);
mLayerStack.EnsureDefaultLayer(mShaderCatalog);
mServerPort = mConfigStore.GetConfig().serverPort;
mAutoReloadEnabled = mConfigStore.GetConfig().autoReload;
mReloadRequested = true;
mCompileMessage = "Waiting for shader compile.";
return true;
}
catch (const std::exception& exception)
{
error = std::string("RuntimeStore::InitializeStore exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RuntimeStore::InitializeStore threw a non-standard exception.";
return false;
}
}
std::string RuntimeStore::BuildPersistentStateJson() const
{
return RuntimeStatePresenter::BuildRuntimeStateJson(*this);
}
bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
{
try
{
std::lock_guard<std::mutex> lock(mMutex);
registryChanged = false;
reloadRequested = false;
if (!mAutoReloadEnabled)
{
reloadRequested = mReloadRequested;
return true;
}
const auto now = std::chrono::steady_clock::now();
if (mLastScanTime != (std::chrono::steady_clock::time_point::min)() &&
std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastScanTime).count() < 250)
{
reloadRequested = mReloadRequested;
return true;
}
mLastScanTime = now;
std::string scanError;
const ShaderPackageCatalog::Snapshot previousCatalog = mShaderCatalog.CaptureSnapshot();
if (!ScanShaderPackages(scanError))
{
error = scanError;
return false;
}
registryChanged = mShaderCatalog.HasCatalogChangedSince(previousCatalog);
mLayerStack.EnsureDefaultsForAllLayers(mShaderCatalog);
for (RuntimeStore::LayerPersistentState& layer : mLayerStack.Layers())
{
const ShaderPackage* active = mShaderCatalog.FindPackage(layer.shaderId);
if (!active)
continue;
if (mShaderCatalog.HasPackageChangedSince(previousCatalog, layer.shaderId))
mReloadRequested = true;
}
reloadRequested = mReloadRequested;
if (registryChanged || reloadRequested)
MarkRenderStateDirtyLocked();
return true;
}
catch (const std::exception& exception)
{
error = std::string("RuntimeStore::PollStoredFileChanges exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RuntimeStore::PollStoredFileChanges threw a non-standard exception.";
return false;
}
}
bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mLayerStack.CreateLayer(mShaderCatalog, shaderId, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mLayerStack.DeleteLayer(layerId, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
bool shouldMove = false;
if (!mLayerStack.ResolveLayerMove(layerId, direction, shouldMove, error))
return false;
if (!shouldMove)
return true;
if (!mLayerStack.MoveLayer(layerId, direction, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
bool shouldMove = false;
if (!mLayerStack.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
return false;
if (!shouldMove)
return true;
if (!mLayerStack.MoveLayerToIndex(layerId, targetIndex, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mLayerStack.SetLayerBypassState(layerId, bypassed, error))
return false;
mReloadRequested = true;
MarkParameterStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mLayerStack.SetLayerShaderSelection(mShaderCatalog, layerId, shaderId, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mLayerStack.SetParameterValue(layerId, parameterId, value, error))
return false;
MarkParameterStateDirtyLocked();
return !persistState || SavePersistentState(error);
}
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mLayerStack.ResetLayerParameterValues(mShaderCatalog, layerId, error))
return false;
MarkParameterStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
if (safeStem.empty())
{
error = "Preset name must include at least one letter or number.";
return false;
}
JsonValue root = JsonValue::MakeObject();
root = mLayerStack.BuildStackPresetValue(mShaderCatalog, presetName);
return WriteTextFile(mConfigStore.GetPresetRoot() / (safeStem + ".json"), SerializeJson(root, true), error);
}
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
if (safeStem.empty())
{
error = "Preset name must include at least one letter or number.";
return false;
}
const std::filesystem::path presetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json");
std::string presetText = ReadTextFile(presetPath, error);
if (presetText.empty())
return false;
JsonValue root;
if (!ParseJson(presetText, root, error))
return false;
if (!mLayerStack.LoadStackPresetValue(mShaderCatalog, root, error))
return false;
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::HasStoredLayer(const std::string& layerId) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mLayerStack.HasLayer(layerId);
}
bool RuntimeStore::HasStoredShader(const std::string& shaderId) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mShaderCatalog.HasPackage(shaderId);
}
bool RuntimeStore::TryGetStoredParameterById(const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mLayerStack.TryGetParameterById(mShaderCatalog, layerId, parameterId, snapshot, error);
}
bool RuntimeStore::TryGetStoredParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mLayerStack.TryGetParameterByControlKey(mShaderCatalog, layerKey, parameterKey, snapshot, error);
}
bool RuntimeStore::ResolveStoredLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mLayerStack.ResolveLayerMove(layerId, direction, shouldMove, error);
}
bool RuntimeStore::ResolveStoredLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
return mLayerStack.ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error);
}
bool RuntimeStore::IsValidStackPresetName(const std::string& presetName) const
{
return !LayerStackStore::MakeSafePresetFileStem(presetName).empty();
}
double RuntimeStore::GetRuntimeElapsedSeconds() const
{
return std::chrono::duration_cast<std::chrono::duration<double>>(
std::chrono::steady_clock::now() - mStartTime).count();
}
const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const
{
return mConfigStore.GetRepoRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const
{
return mConfigStore.GetUiRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const
{
return mConfigStore.GetDocsRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const
{
return mConfigStore.GetRuntimeRoot();
}
unsigned short RuntimeStore::GetConfiguredControlServerPort() const
{
return mServerPort;
}
unsigned short RuntimeStore::GetConfiguredOscPort() const
{
return mConfigStore.GetConfig().oscPort;
}
const std::string& RuntimeStore::GetConfiguredOscBindAddress() const
{
return mConfigStore.GetConfig().oscBindAddress;
}
double RuntimeStore::GetConfiguredOscSmoothing() const
{
return mConfigStore.GetConfig().oscSmoothing;
}
unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const
{
return mConfigStore.GetConfig().maxTemporalHistoryFrames;
}
unsigned RuntimeStore::GetConfiguredPreviewFps() const
{
return mConfigStore.GetConfig().previewFps;
}
bool RuntimeStore::IsExternalKeyingConfigured() const
{
return mConfigStore.GetConfig().enableExternalKeying;
}
const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const
{
return mConfigStore.GetConfig().inputVideoFormat;
}
const std::string& RuntimeStore::GetConfiguredInputFrameRate() const
{
return mConfigStore.GetConfig().inputFrameRate;
}
const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const
{
return mConfigStore.GetConfig().outputVideoFormat;
}
const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
{
return mConfigStore.GetConfig().outputFrameRate;
}
void RuntimeStore::SetBoundControlServerPort(unsigned short port)
{
std::lock_guard<std::mutex> lock(mMutex);
mServerPort = port;
mConfigStore.SetBoundControlServerPort(port);
}
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
{
std::lock_guard<std::mutex> lock(mMutex);
mCompileSucceeded = succeeded;
mCompileMessage = message;
}
void RuntimeStore::ClearReloadRequest()
{
std::lock_guard<std::mutex> lock(mMutex);
mReloadRequested = false;
}
bool RuntimeStore::LoadPersistentState(std::string& error)
{
if (!std::filesystem::exists(mConfigStore.GetRuntimeStatePath()))
return true;
std::string stateText = ReadTextFile(mConfigStore.GetRuntimeStatePath(), error);
if (stateText.empty())
return false;
JsonValue root;
if (!ParseJson(stateText, root, error))
return false;
return mLayerStack.LoadPersistentStateValue(root);
}
bool RuntimeStore::SavePersistentState(std::string& error) const
{
return WriteTextFile(mConfigStore.GetRuntimeStatePath(), SerializeJson(mLayerStack.BuildPersistentStateValue(mShaderCatalog), true), error);
}
bool RuntimeStore::ScanShaderPackages(std::string& error)
{
if (!mShaderCatalog.Scan(mConfigStore.GetShaderRoot(), mConfigStore.GetConfig().maxTemporalHistoryFrames, error))
return false;
mLayerStack.RemoveLayersWithMissingPackages(mShaderCatalog);
MarkRenderStateDirtyLocked();
return true;
}
std::string RuntimeStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const
{
std::ifstream input(path, std::ios::binary);
if (!input)
{
error = "Could not open file: " + path.string();
return std::string();
}
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
bool RuntimeStore::WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const
{
std::error_code fsError;
std::filesystem::create_directories(path.parent_path(), fsError);
const std::filesystem::path temporaryPath = path.string() + ".tmp";
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
if (!output)
{
error = "Could not write file: " + temporaryPath.string();
return false;
}
output << contents;
output.close();
if (!output.good())
{
error = "Could not finish writing file: " + temporaryPath.string();
return false;
}
if (!MoveFileExA(temporaryPath.string().c_str(), path.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
{
const DWORD lastError = GetLastError();
std::filesystem::remove(temporaryPath, fsError);
error = "Could not replace file: " + path.string() + " (Win32 error " + std::to_string(lastError) + ")";
return false;
}
return true;
}
std::vector<std::string> RuntimeStore::GetStackPresetNamesLocked() const
{
std::vector<std::string> presetNames;
std::error_code fsError;
if (!std::filesystem::exists(mConfigStore.GetPresetRoot(), fsError))
return presetNames;
for (const auto& entry : std::filesystem::directory_iterator(mConfigStore.GetPresetRoot(), fsError))
{
if (!entry.is_regular_file())
continue;
if (ToLowerCopy(entry.path().extension().string()) != ".json")
continue;
presetNames.push_back(entry.path().stem().string());
}
std::sort(presetNames.begin(), presetNames.end());
return presetNames;
}
bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const
{
std::lock_guard<std::mutex> lock(mMutex);
const RuntimeStore::LayerPersistentState* layer = mLayerStack.FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
if (!mShaderCatalog.CopyPackage(layer->shaderId, shaderPackage))
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
return true;
}
void RuntimeStore::GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const
{
std::lock_guard<std::mutex> lock(mMutex);
repoRoot = mConfigStore.GetRepoRoot();
wrapperPath = mConfigStore.GetWrapperPath();
generatedGlslPath = mConfigStore.GetGeneratedGlslPath();
patchedGlslPath = mConfigStore.GetPatchedGlslPath();
maxTemporalHistoryFrames = mConfigStore.GetConfig().maxTemporalHistoryFrames;
}
void RuntimeStore::MarkRenderStateDirtyLocked()
{
mRenderSnapshotBuilder.MarkRenderStateDirty();
}
void RuntimeStore::MarkParameterStateDirtyLocked()
{
mRenderSnapshotBuilder.MarkParameterStateDirty();
}