step 2
This commit is contained in:
@@ -126,6 +126,8 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
|
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
|
||||||
"${APP_DIR}/runtime/live/RuntimeLiveState.h"
|
"${APP_DIR}/runtime/live/RuntimeLiveState.h"
|
||||||
"${APP_DIR}/runtime/persistence/PersistenceRequest.h"
|
"${APP_DIR}/runtime/persistence/PersistenceRequest.h"
|
||||||
|
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
|
||||||
|
"${APP_DIR}/runtime/persistence/PersistenceWriter.h"
|
||||||
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
|
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
|
||||||
"${APP_DIR}/runtime/presentation/RuntimeStateJson.h"
|
"${APP_DIR}/runtime/presentation/RuntimeStateJson.h"
|
||||||
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
|
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
|
||||||
@@ -347,6 +349,7 @@ add_executable(RuntimeSubsystemTests
|
|||||||
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
|
"${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp"
|
||||||
"${APP_DIR}/runtime/live/CommittedLiveState.cpp"
|
"${APP_DIR}/runtime/live/CommittedLiveState.cpp"
|
||||||
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp"
|
"${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp"
|
||||||
|
"${APP_DIR}/runtime/persistence/PersistenceWriter.cpp"
|
||||||
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
|
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
|
||||||
"${APP_DIR}/runtime/store/RuntimeConfigStore.cpp"
|
"${APP_DIR}/runtime/store/RuntimeConfigStore.cpp"
|
||||||
"${APP_DIR}/runtime/store/RuntimeStore.cpp"
|
"${APP_DIR}/runtime/store/RuntimeStore.cpp"
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include "PersistenceWriter.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
bool PersistenceWriter::WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const
|
||||||
|
{
|
||||||
|
if (snapshot.targetPath.empty())
|
||||||
|
{
|
||||||
|
error = "Persistence snapshot target path is empty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code fsError;
|
||||||
|
std::filesystem::create_directories(snapshot.targetPath.parent_path(), fsError);
|
||||||
|
|
||||||
|
const std::filesystem::path temporaryPath = snapshot.targetPath.string() + ".tmp";
|
||||||
|
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!output)
|
||||||
|
{
|
||||||
|
error = "Could not write file: " + temporaryPath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output << snapshot.contents;
|
||||||
|
output.close();
|
||||||
|
if (!output.good())
|
||||||
|
{
|
||||||
|
error = "Could not finish writing file: " + temporaryPath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MoveFileExA(temporaryPath.string().c_str(), snapshot.targetPath.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
|
||||||
|
{
|
||||||
|
const DWORD lastError = GetLastError();
|
||||||
|
std::filesystem::remove(temporaryPath, fsError);
|
||||||
|
error = "Could not replace file: " + snapshot.targetPath.string() + " (Win32 error " + std::to_string(lastError) + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PersistenceRequest.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class PersistenceWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool WriteSnapshot(const PersistenceSnapshot& snapshot, std::string& error) const;
|
||||||
|
};
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -99,6 +98,23 @@ std::string RuntimeStore::BuildPersistentStateJson() const
|
|||||||
return RuntimeStatePresenter::BuildRuntimeStateJson(*this);
|
return RuntimeStatePresenter::BuildRuntimeStateJson(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
return BuildRuntimeStatePersistenceSnapshotLocked(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const
|
||||||
|
{
|
||||||
|
PersistenceSnapshot snapshot;
|
||||||
|
snapshot.targetKind = PersistenceTargetKind::RuntimeState;
|
||||||
|
snapshot.targetPath = mConfigStore.GetRuntimeStatePath();
|
||||||
|
snapshot.contents = SerializeJson(mCommittedLiveState.BuildPersistentStateValue(mShaderCatalog), true);
|
||||||
|
snapshot.reason = request.reason;
|
||||||
|
snapshot.generation = request.sequence;
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
|
bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -270,10 +286,7 @@ bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::s
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonValue root = JsonValue::MakeObject();
|
return mPersistenceWriter.WriteSnapshot(BuildStackPresetPersistenceSnapshot(presetName), error);
|
||||||
root = mCommittedLiveState.BuildStackPresetValue(mShaderCatalog, presetName);
|
|
||||||
|
|
||||||
return WriteTextFile(mConfigStore.GetPresetRoot() / (safeStem + ".json"), SerializeJson(root, true), error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
|
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
|
||||||
@@ -465,7 +478,20 @@ bool RuntimeStore::LoadPersistentState(std::string& error)
|
|||||||
|
|
||||||
bool RuntimeStore::SavePersistentState(std::string& error) const
|
bool RuntimeStore::SavePersistentState(std::string& error) const
|
||||||
{
|
{
|
||||||
return WriteTextFile(mConfigStore.GetRuntimeStatePath(), SerializeJson(mCommittedLiveState.BuildPersistentStateValue(mShaderCatalog), true), error);
|
return mPersistenceWriter.WriteSnapshot(BuildRuntimeStatePersistenceSnapshotLocked(PersistenceRequest::RuntimeStateRequest("SavePersistentState")), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistenceSnapshot RuntimeStore::BuildStackPresetPersistenceSnapshot(const std::string& presetName) const
|
||||||
|
{
|
||||||
|
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);
|
||||||
|
|
||||||
|
PersistenceSnapshot snapshot;
|
||||||
|
snapshot.targetKind = PersistenceTargetKind::StackPreset;
|
||||||
|
snapshot.targetPath = mConfigStore.GetPresetRoot() / (safeStem + ".json");
|
||||||
|
snapshot.contents = SerializeJson(mCommittedLiveState.BuildStackPresetValue(mShaderCatalog, presetName), true);
|
||||||
|
snapshot.reason = "SaveStackPreset";
|
||||||
|
snapshot.generation = 0;
|
||||||
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeStore::ScanShaderPackages(std::string& error)
|
bool RuntimeStore::ScanShaderPackages(std::string& error)
|
||||||
@@ -493,38 +519,6 @@ std::string RuntimeStore::ReadTextFile(const std::filesystem::path& path, std::s
|
|||||||
return buffer.str();
|
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> RuntimeStore::GetStackPresetNamesLocked() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> presetNames;
|
std::vector<std::string> presetNames;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "HealthTelemetry.h"
|
#include "HealthTelemetry.h"
|
||||||
#include "CommittedLiveState.h"
|
#include "CommittedLiveState.h"
|
||||||
#include "LayerStackStore.h"
|
#include "LayerStackStore.h"
|
||||||
|
#include "PersistenceWriter.h"
|
||||||
#include "RenderSnapshotBuilder.h"
|
#include "RenderSnapshotBuilder.h"
|
||||||
#include "RuntimeConfigStore.h"
|
#include "RuntimeConfigStore.h"
|
||||||
#include "RuntimeJson.h"
|
#include "RuntimeJson.h"
|
||||||
@@ -30,6 +31,7 @@ public:
|
|||||||
|
|
||||||
bool InitializeStore(std::string& error);
|
bool InitializeStore(std::string& error);
|
||||||
std::string BuildPersistentStateJson() const;
|
std::string BuildPersistentStateJson() const;
|
||||||
|
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const;
|
||||||
bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
|
bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
|
||||||
|
|
||||||
bool CreateStoredLayer(const std::string& shaderId, std::string& error);
|
bool CreateStoredLayer(const std::string& shaderId, std::string& error);
|
||||||
@@ -82,14 +84,16 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool LoadPersistentState(std::string& error);
|
bool LoadPersistentState(std::string& error);
|
||||||
bool SavePersistentState(std::string& error) const;
|
bool SavePersistentState(std::string& error) const;
|
||||||
|
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const;
|
||||||
|
PersistenceSnapshot BuildStackPresetPersistenceSnapshot(const std::string& presetName) const;
|
||||||
bool ScanShaderPackages(std::string& error);
|
bool ScanShaderPackages(std::string& error);
|
||||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
|
||||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||||
void MarkRenderStateDirtyLocked();
|
void MarkRenderStateDirtyLocked();
|
||||||
void MarkParameterStateDirtyLocked();
|
void MarkParameterStateDirtyLocked();
|
||||||
|
|
||||||
RenderSnapshotBuilder mRenderSnapshotBuilder;
|
RenderSnapshotBuilder mRenderSnapshotBuilder;
|
||||||
|
PersistenceWriter mPersistenceWriter;
|
||||||
RuntimeConfigStore mConfigStore;
|
RuntimeConfigStore mConfigStore;
|
||||||
ShaderPackageCatalog mShaderCatalog;
|
ShaderPackageCatalog mShaderCatalog;
|
||||||
CommittedLiveState mCommittedLiveState;
|
CommittedLiveState mCommittedLiveState;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Phases 1-5 separate durable state, coordination policy, render-facing snapshots,
|
|||||||
## Status
|
## Status
|
||||||
|
|
||||||
- Phase 6 design package: proposed.
|
- Phase 6 design package: proposed.
|
||||||
- Phase 6 implementation: Step 1 complete.
|
- Phase 6 implementation: Step 2 complete.
|
||||||
- Current alignment: `RuntimeStore` owns durable serialization, config, package metadata, preset IO, and persistence requests; `CommittedLiveState` owns the current committed/session layer state; and `RuntimeCoordinator` publishes typed persistence requests for persisted mutations. The remaining issue is that actual disk writes are still synchronous store work rather than queued, debounced, atomic background writes.
|
- Current alignment: `RuntimeStore` owns durable serialization, config, package metadata, preset IO, and persistence requests; `CommittedLiveState` owns the current committed/session layer state; and `RuntimeCoordinator` publishes typed persistence requests for persisted mutations. The remaining issue is that actual disk writes are still synchronous store work rather than queued, debounced, atomic background writes.
|
||||||
|
|
||||||
Current persistence footholds:
|
Current persistence footholds:
|
||||||
@@ -201,9 +201,16 @@ Move file-write mechanics behind a helper while keeping serialization ownership
|
|||||||
|
|
||||||
Initial target:
|
Initial target:
|
||||||
|
|
||||||
- `RuntimeStore` can build serialized runtime-state snapshots
|
- [x] `RuntimeStore` can build serialized runtime-state snapshots
|
||||||
- `PersistenceWriter` writes the snapshot
|
- [x] `PersistenceWriter` writes the snapshot
|
||||||
- existing synchronous save path can call through the writer/helper during transition
|
- [x] existing synchronous save path can call through the writer/helper during transition
|
||||||
|
|
||||||
|
Current implementation:
|
||||||
|
|
||||||
|
- `RuntimeStore::BuildRuntimeStatePersistenceSnapshot(...)` captures serialized runtime-state content and target path.
|
||||||
|
- `PersistenceWriter::WriteSnapshot(...)` owns the temp-file and replace write mechanics.
|
||||||
|
- `RuntimeStore::SavePersistentState(...)` still behaves synchronously, but now writes through `PersistenceWriter`.
|
||||||
|
- Stack preset saves also use `PersistenceWriter` synchronously; preset async policy remains a later decision.
|
||||||
|
|
||||||
### Step 3. Add Debounced Background Worker
|
### Step 3. Add Debounced Background Worker
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,13 @@ void TestRuntimeCoordinatorPersistenceEvents()
|
|||||||
Expect(store.InitializeStore(error), "runtime store initializes in isolated fixture");
|
Expect(store.InitializeStore(error), "runtime store initializes in isolated fixture");
|
||||||
Expect(error.empty(), "runtime store initialization has no error");
|
Expect(error.empty(), "runtime store initialization has no error");
|
||||||
|
|
||||||
|
const PersistenceRequest snapshotRequest = PersistenceRequest::RuntimeStateRequest("unit-test");
|
||||||
|
const PersistenceSnapshot snapshot = store.BuildRuntimeStatePersistenceSnapshot(snapshotRequest);
|
||||||
|
Expect(snapshot.targetKind == PersistenceTargetKind::RuntimeState, "runtime store builds a runtime-state persistence snapshot");
|
||||||
|
Expect(snapshot.reason == "unit-test", "runtime-state persistence snapshot preserves request reason");
|
||||||
|
Expect(snapshot.targetPath.filename().string() == "runtime_state.json", "runtime-state persistence snapshot targets the runtime state file");
|
||||||
|
Expect(snapshot.contents.find("\"layers\"") != std::string::npos, "runtime-state persistence snapshot contains serialized layer state");
|
||||||
|
|
||||||
RuntimeEventDispatcher dispatcher(64);
|
RuntimeEventDispatcher dispatcher(64);
|
||||||
std::vector<RuntimeEvent> seenEvents;
|
std::vector<RuntimeEvent> seenEvents;
|
||||||
dispatcher.SubscribeAll([&seenEvents](const RuntimeEvent& event) {
|
dispatcher.SubscribeAll([&seenEvents](const RuntimeEvent& event) {
|
||||||
|
|||||||
Reference in New Issue
Block a user