Use CultLib for live reload state
This commit is contained in:
parent
1a0dc36148
commit
a597fab63e
10 changed files with 447 additions and 23 deletions
37
README.md
37
README.md
|
|
@ -35,8 +35,9 @@ Everything else earns its way back in.
|
|||
|
||||
## Run
|
||||
|
||||
Aquarium currently expects the sibling `CultMath` repo at `E:\Projects\CultMath`
|
||||
or the equivalent `..\..\..\CultMath` path from the engine project.
|
||||
Aquarium currently expects the sibling `CultMath` and `CultLib` repos at
|
||||
`E:\Projects\CultMath` and `E:\Projects\CultLib`, or the equivalent
|
||||
`..\..\..\CultMath` / `..\..\..\CultLib` paths from the engine project.
|
||||
|
||||
```powershell
|
||||
dotnet build Aquarium.Engine.slnx
|
||||
|
|
@ -50,10 +51,28 @@ runner instead of `dotnet run`:
|
|||
.\scripts\dev-reload.ps1
|
||||
```
|
||||
|
||||
It builds to a fresh disposable slot under `artifacts\dev-reload`, starts that
|
||||
slot, records the PID, and replaces only the previous process that the script
|
||||
itself launched. This keeps MSBuild away from the locked normal
|
||||
`bin\Debug` output while a live window is still open.
|
||||
It builds to a fresh disposable slot under `artifacts\dev-reload`, records the
|
||||
slot/PID in PowerShell CLIXML, and launches the runtime with a MessagePack
|
||||
CultCache store at `artifacts\dev-reload\cultcache\aquarium-client.msgpack`.
|
||||
The previous script-owned process is stopped only after the replacement build
|
||||
succeeds. This keeps MSBuild away from the locked normal `bin\Debug` output
|
||||
while a live window is still open.
|
||||
|
||||
For automatic rebuild/restart on source changes, use the watcher:
|
||||
|
||||
```powershell
|
||||
.\scripts\dev-watch.ps1
|
||||
```
|
||||
|
||||
The watcher polls source files, waits for writes to settle, builds into a new
|
||||
slot, and only replaces the running Aquarium after the new build succeeds. If a
|
||||
build fails, the previous good process keeps running and that same broken source
|
||||
fingerprint is not retried until files change again.
|
||||
|
||||
Runtime live state is a typed CultCache document, not a loose sidecar file. The
|
||||
first document is `epiphany.aquarium.live_state`, currently banking camera
|
||||
target, yaw, pitch, distance, time, and save generation every few frames so a
|
||||
reload can rehydrate without pretending memory is vibes.
|
||||
|
||||
Stop the script-owned Aquarium process:
|
||||
|
||||
|
|
@ -73,6 +92,12 @@ Headless reload smoke without touching the normal build output:
|
|||
.\scripts\dev-reload.ps1 -Headless
|
||||
```
|
||||
|
||||
Headless watch smoke:
|
||||
|
||||
```powershell
|
||||
.\scripts\dev-watch.ps1 -Headless
|
||||
```
|
||||
|
||||
The first cut opens a Win32 window directly and owns a D3D11 swapchain through
|
||||
Vortice. No Stride runtime, no asset protocol, no borrowed game loop wearing a
|
||||
fake mustache. See `docs/vortice-spine.md`.
|
||||
|
|
|
|||
41
docs/cult-runtime-surface.md
Normal file
41
docs/cult-runtime-surface.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Cult Runtime Surface
|
||||
|
||||
Aquarium uses CultLib directly for runtime identity and reload recovery.
|
||||
|
||||
## State Store
|
||||
|
||||
`AquariumCultStateStore` opens a `SingleFileMessagePackBackingStore` and attaches
|
||||
it to a `CultCache`. The dev scripts pass:
|
||||
|
||||
```powershell
|
||||
--cache E:\Projects\Aquarium-Engine\artifacts\dev-reload\cultcache\aquarium-client.msgpack
|
||||
```
|
||||
|
||||
Direct runs can also use `--cache <path>` or `AQUARIUM_CULTCACHE_PATH`.
|
||||
|
||||
## Live State
|
||||
|
||||
`AquariumLiveState` is a CultCache document:
|
||||
|
||||
- schema name: `epiphany.aquarium.live_state`
|
||||
- schema version: `epiphany.aquarium.live_state.v0`
|
||||
- record key: `aquarium-client/live-state`
|
||||
- name: `aquarium-client`
|
||||
|
||||
It currently stores the camera target, yaw, pitch, distance, runtime time, and a
|
||||
monotonic save generation. `AquariumRuntime` writes it every 0.2 seconds and
|
||||
again during orderly shutdown.
|
||||
|
||||
## CultNet Surface
|
||||
|
||||
The runtime creates a `CultNetHelloMessage` from the local CultCache registry.
|
||||
That gives the engine a concrete CultNet identity and a schema catalog surface
|
||||
before transport work starts:
|
||||
|
||||
- runtime kind: `aquarium-engine`
|
||||
- role: `visual-client`
|
||||
- display name: `Epiphany Aquarium`
|
||||
- supported document types: taken from `CultDocumentRegistry.AllDescriptors`
|
||||
|
||||
Transport is still the next layer. The important thing for reload is already
|
||||
true: live state is a typed Cult document backed by CultLib MessagePack storage.
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
param(
|
||||
[switch]$Headless,
|
||||
[switch]$NoStop,
|
||||
[switch]$BuildOnly
|
||||
[switch]$BuildOnly,
|
||||
[int]$RetainSlots = 12
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
|
@ -11,7 +12,9 @@ $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
|||
$projectPath = Join-Path $repoRoot "src\Aquarium.Engine\Aquarium.Engine.csproj"
|
||||
$devRoot = Join-Path $repoRoot "artifacts\dev-reload"
|
||||
$slotRoot = Join-Path $devRoot "slots"
|
||||
$statePath = Join-Path $devRoot "state.json"
|
||||
$statePath = Join-Path $devRoot "state.clixml"
|
||||
$buildStatePath = Join-Path $devRoot "last-build.clixml"
|
||||
$cultCachePath = Join-Path $devRoot "cultcache\aquarium-client.msgpack"
|
||||
$stdoutLogPath = Join-Path $devRoot "latest.out.log"
|
||||
$stderrLogPath = Join-Path $devRoot "latest.err.log"
|
||||
|
||||
|
|
@ -22,7 +25,7 @@ function Stop-PreviousOwnedProcess {
|
|||
return
|
||||
}
|
||||
|
||||
$state = Get-Content -Raw -Path $statePath | ConvertFrom-Json
|
||||
$state = Import-Clixml -Path $statePath
|
||||
if (-not $state.pid) {
|
||||
return
|
||||
}
|
||||
|
|
@ -50,7 +53,24 @@ function Stop-PreviousOwnedProcess {
|
|||
Stop-Process -Id ([int]$state.pid) -Force
|
||||
}
|
||||
|
||||
Stop-PreviousOwnedProcess
|
||||
function Remove-OldSlots {
|
||||
if ($RetainSlots -lt 1) {
|
||||
return
|
||||
}
|
||||
|
||||
$slots = Get-ChildItem -Path $slotRoot -Directory -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTimeUtc -Descending |
|
||||
Select-Object -Skip $RetainSlots
|
||||
|
||||
foreach ($slot in $slots) {
|
||||
try {
|
||||
Remove-Item -LiteralPath $slot.FullName -Recurse -Force
|
||||
}
|
||||
catch {
|
||||
Write-Host "Could not remove old slot $($slot.FullName): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$slotName = "$timestamp-$([guid]::NewGuid().ToString("N").Substring(0, 8))"
|
||||
|
|
@ -71,16 +91,18 @@ if ($BuildOnly) {
|
|||
@{
|
||||
slot = $slotPath
|
||||
builtAt = (Get-Date).ToString("o")
|
||||
pid = $null
|
||||
stdoutLog = $stdoutLogPath
|
||||
stderrLog = $stderrLogPath
|
||||
} | ConvertTo-Json | Set-Content -Path $statePath
|
||||
} | Export-Clixml -Path $buildStatePath
|
||||
Write-Host "Build-only slot ready."
|
||||
Remove-OldSlots
|
||||
exit 0
|
||||
}
|
||||
|
||||
Stop-PreviousOwnedProcess
|
||||
|
||||
$dllPath = Join-Path $slotPath "Aquarium.Engine.dll"
|
||||
$arguments = @($dllPath)
|
||||
$arguments = @($dllPath, "--cache", $cultCachePath)
|
||||
if ($Headless) {
|
||||
$arguments += "--headless"
|
||||
}
|
||||
|
|
@ -106,10 +128,13 @@ $process = Start-Process @startProcessParameters
|
|||
pid = $process.Id
|
||||
stdoutLog = $stdoutLogPath
|
||||
stderrLog = $stderrLogPath
|
||||
} | ConvertTo-Json | Set-Content -Path $statePath
|
||||
} | Export-Clixml -Path $statePath
|
||||
|
||||
Write-Host "Aquarium running from disposable slot."
|
||||
Write-Host " PID: $($process.Id)"
|
||||
Write-Host " CultCache: $cultCachePath"
|
||||
Write-Host " Stdout: $stdoutLogPath"
|
||||
Write-Host " Stderr: $stderrLogPath"
|
||||
Write-Host "Run this script again to replace only this script-owned process."
|
||||
|
||||
Remove-OldSlots
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ $ErrorActionPreference = "Stop"
|
|||
Set-StrictMode -Version Latest
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$statePath = Join-Path $repoRoot "artifacts\dev-reload\state.json"
|
||||
$statePath = Join-Path $repoRoot "artifacts\dev-reload\state.clixml"
|
||||
|
||||
if (-not (Test-Path $statePath)) {
|
||||
Write-Host "No dev-reload state file exists."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$state = Get-Content -Raw -Path $statePath | ConvertFrom-Json
|
||||
$state = Import-Clixml -Path $statePath
|
||||
if (-not $state.pid) {
|
||||
Write-Host "No script-owned Aquarium PID is recorded."
|
||||
exit 0
|
||||
|
|
|
|||
156
scripts/dev-watch.ps1
Normal file
156
scripts/dev-watch.ps1
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
param(
|
||||
[switch]$Headless,
|
||||
[switch]$NoInitialLaunch,
|
||||
[switch]$Once,
|
||||
[int]$IntervalMilliseconds = 1000,
|
||||
[int]$DebounceMilliseconds = 350,
|
||||
[int]$RetainSlots = 12
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$reloadScript = Join-Path $PSScriptRoot "dev-reload.ps1"
|
||||
$watchLogPath = Join-Path $repoRoot "artifacts\dev-reload\watch.log"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path $watchLogPath) | Out-Null
|
||||
|
||||
function Write-WatchLog([string]$message) {
|
||||
$line = "$(Get-Date -Format o) $message"
|
||||
Write-Host $line
|
||||
Add-Content -Path $watchLogPath -Value $line
|
||||
}
|
||||
|
||||
function Get-WatchedFiles {
|
||||
$roots = @(
|
||||
(Join-Path $repoRoot "src"),
|
||||
(Join-Path $repoRoot "Aquarium.Engine.slnx"),
|
||||
(Join-Path $repoRoot "global.json")
|
||||
)
|
||||
|
||||
foreach ($root in $roots) {
|
||||
if (-not (Test-Path $root)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$item = Get-Item -LiteralPath $root
|
||||
if (-not $item.PSIsContainer) {
|
||||
$item
|
||||
continue
|
||||
}
|
||||
|
||||
Get-ChildItem -LiteralPath $item.FullName -Recurse -File |
|
||||
Where-Object {
|
||||
$_.FullName -notmatch "\\bin\\" -and
|
||||
$_.FullName -notmatch "\\obj\\" -and
|
||||
$_.Extension -in @(".cs", ".csproj", ".hlsl", ".json", ".slnx")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SourceFingerprint {
|
||||
$fingerprintInput = Get-WatchedFiles |
|
||||
Sort-Object FullName |
|
||||
ForEach-Object {
|
||||
"$($_.FullName)|$($_.Length)|$($_.LastWriteTimeUtc.Ticks)"
|
||||
}
|
||||
|
||||
$text = [string]::Join("`n", $fingerprintInput)
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
|
||||
$sha = [System.Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$hashBytes = $sha.ComputeHash($bytes)
|
||||
return -join ($hashBytes | ForEach-Object { $_.ToString("x2") })
|
||||
}
|
||||
finally {
|
||||
$sha.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-StableFingerprint {
|
||||
$first = Get-SourceFingerprint
|
||||
Start-Sleep -Milliseconds $DebounceMilliseconds
|
||||
$second = Get-SourceFingerprint
|
||||
|
||||
while ($first -ne $second) {
|
||||
$first = $second
|
||||
Start-Sleep -Milliseconds $DebounceMilliseconds
|
||||
$second = Get-SourceFingerprint
|
||||
}
|
||||
|
||||
return $second
|
||||
}
|
||||
|
||||
function Invoke-Reload {
|
||||
param([string]$reason)
|
||||
|
||||
Write-WatchLog "Reload requested: $reason"
|
||||
|
||||
$arguments = @(
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", $reloadScript,
|
||||
"-RetainSlots", "$RetainSlots"
|
||||
)
|
||||
|
||||
if ($Headless) {
|
||||
$arguments += "-Headless"
|
||||
}
|
||||
|
||||
$process = Start-Process `
|
||||
-FilePath "powershell" `
|
||||
-ArgumentList $arguments `
|
||||
-WorkingDirectory $repoRoot `
|
||||
-Wait `
|
||||
-PassThru `
|
||||
-NoNewWindow
|
||||
|
||||
if ($process.ExitCode -ne 0) {
|
||||
throw "dev-reload failed with exit code $($process.ExitCode)."
|
||||
}
|
||||
}
|
||||
|
||||
$lastGoodFingerprint = Wait-StableFingerprint
|
||||
$lastFailedFingerprint = $null
|
||||
|
||||
if (-not $NoInitialLaunch) {
|
||||
try {
|
||||
Invoke-Reload "initial launch"
|
||||
$lastGoodFingerprint = Wait-StableFingerprint
|
||||
$lastFailedFingerprint = $null
|
||||
}
|
||||
catch {
|
||||
$lastFailedFingerprint = $lastGoodFingerprint
|
||||
Write-WatchLog "Initial launch failed; keeping any previous good process alive. $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-WatchLog "Watching Aquarium sources. Press Ctrl+C to stop."
|
||||
|
||||
do {
|
||||
Start-Sleep -Milliseconds $IntervalMilliseconds
|
||||
$currentFingerprint = Wait-StableFingerprint
|
||||
|
||||
if ($currentFingerprint -eq $lastGoodFingerprint -or $currentFingerprint -eq $lastFailedFingerprint) {
|
||||
if ($Once) {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-Reload "source change"
|
||||
$lastGoodFingerprint = $currentFingerprint
|
||||
$lastFailedFingerprint = $null
|
||||
}
|
||||
catch {
|
||||
$lastFailedFingerprint = $currentFingerprint
|
||||
Write-WatchLog "Reload failed; previous good process stays alive. $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
if ($Once) {
|
||||
break
|
||||
}
|
||||
} while ($true)
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\CultMath\src\CultMath\CultMath.csproj" />
|
||||
<ProjectReference Include="..\..\..\CultLib\src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj" />
|
||||
<ProjectReference Include="..\..\..\CultLib\src\GameCult.Networking\GameCult.Networking.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public static class AquariumHost
|
|||
{
|
||||
public static int Run(string[] args)
|
||||
{
|
||||
var runtime = new AquariumRuntime(new AquariumRuntimeOptions(ParseHeadless(args)));
|
||||
using var runtime = new AquariumRuntime(new AquariumRuntimeOptions(ParseHeadless(args), ParseCachePath(args)));
|
||||
var input = new InputState();
|
||||
var width = runtime.Options.Headless ? 640 : 1280;
|
||||
var height = runtime.Options.Headless ? 360 : 720;
|
||||
|
|
@ -50,4 +50,18 @@ public static class AquariumHost
|
|||
{
|
||||
return args.Any(arg => string.Equals(arg, "--headless", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static string? ParseCachePath(IReadOnlyCollection<string> args)
|
||||
{
|
||||
var values = args.ToArray();
|
||||
for (var index = 0; index < values.Length - 1; index++)
|
||||
{
|
||||
if (string.Equals(values[index], "--cache", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return values[index + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return Environment.GetEnvironmentVariable("AQUARIUM_CULTCACHE_PATH");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
using System.Numerics;
|
||||
using Aquarium.Engine.Input;
|
||||
using Aquarium.Engine.State;
|
||||
|
||||
namespace Aquarium.Engine;
|
||||
|
||||
public sealed class AquariumRuntime
|
||||
public sealed class AquariumRuntime : IDisposable
|
||||
{
|
||||
private const float StateSaveIntervalSeconds = 0.20f;
|
||||
|
||||
private readonly OrbitCameraRig cameraRig;
|
||||
private readonly AquariumCultStateStore stateStore;
|
||||
|
||||
private GridFrame gridFrame;
|
||||
private float timeSeconds;
|
||||
private float secondsSinceStateSave;
|
||||
|
||||
public AquariumRuntime(AquariumRuntimeOptions options)
|
||||
{
|
||||
Options = options;
|
||||
stateStore = AquariumCultStateStore.Open(options.CultCachePath);
|
||||
var liveState = stateStore.LoadOrCreate();
|
||||
cameraRig = new OrbitCameraRig(
|
||||
target: System.Numerics.Vector2.Zero,
|
||||
yawRadians: Angle.DegreesToRadians(35.0f),
|
||||
pitchRadians: Angle.DegreesToRadians(42.0f),
|
||||
distance: 24.0f);
|
||||
target: new Vector2(liveState.CameraTargetX, liveState.CameraTargetY),
|
||||
yawRadians: liveState.CameraYawRadians,
|
||||
pitchRadians: liveState.CameraPitchRadians,
|
||||
distance: liveState.CameraDistance);
|
||||
timeSeconds = liveState.TimeSeconds;
|
||||
gridFrame = GridFrame.FromCamera(cameraRig);
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +37,8 @@ public sealed class AquariumRuntime
|
|||
{
|
||||
Console.WriteLine("Aquarium Engine booted.");
|
||||
Console.WriteLine($"Grid center: {gridFrame.Center}, radius: {gridFrame.Radius:0.00}");
|
||||
Console.WriteLine($"CultCache: {stateStore.CachePath}");
|
||||
Console.WriteLine($"CultNet hello: {stateStore.Hello.RuntimeKind} / {stateStore.Hello.DisplayName}");
|
||||
Console.WriteLine("Vortice D3D11 is present as the host shell; Aquarium owns the invariants.");
|
||||
}
|
||||
|
||||
|
|
@ -37,9 +47,34 @@ public sealed class AquariumRuntime
|
|||
timeSeconds += Math.Max(deltaSeconds, 0.0f);
|
||||
cameraRig.ApplyInput(input, deltaSeconds);
|
||||
gridFrame = GridFrame.FromCamera(cameraRig);
|
||||
secondsSinceStateSave += Math.Max(deltaSeconds, 0.0f);
|
||||
if (secondsSinceStateSave >= StateSaveIntervalSeconds)
|
||||
{
|
||||
SaveLiveState();
|
||||
secondsSinceStateSave = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SaveLiveState();
|
||||
stateStore.Dispose();
|
||||
}
|
||||
|
||||
private void SaveLiveState()
|
||||
{
|
||||
stateStore.Save(new AquariumLiveState
|
||||
{
|
||||
TimeSeconds = timeSeconds,
|
||||
CameraTargetX = cameraRig.Target.X,
|
||||
CameraTargetY = cameraRig.Target.Y,
|
||||
CameraYawRadians = cameraRig.YawRadians,
|
||||
CameraPitchRadians = cameraRig.PitchRadians,
|
||||
CameraDistance = cameraRig.Distance
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct AquariumRuntimeOptions(bool Headless);
|
||||
public readonly record struct AquariumRuntimeOptions(bool Headless, string? CultCachePath);
|
||||
|
||||
public readonly record struct AquariumFrame(GridFrame Grid, System.Numerics.Vector3 CameraPosition, float TimeSeconds);
|
||||
|
|
|
|||
90
src/Aquarium.Engine/State/AquariumCultStateStore.cs
Normal file
90
src/Aquarium.Engine/State/AquariumCultStateStore.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System.Numerics;
|
||||
using GameCult.Caching;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using GameCult.Networking;
|
||||
|
||||
namespace Aquarium.Engine.State;
|
||||
|
||||
public sealed class AquariumCultStateStore : IDisposable
|
||||
{
|
||||
public const string DefaultRelativePath = "artifacts/dev-reload/cultcache/aquarium-client.msgpack";
|
||||
private static readonly CultRecordKey LiveStateKey = new("aquarium-client/live-state");
|
||||
|
||||
private readonly CultCache cache;
|
||||
private readonly SingleFileMessagePackBackingStore backingStore;
|
||||
|
||||
private AquariumCultStateStore(string path)
|
||||
{
|
||||
CachePath = Path.GetFullPath(path);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(CachePath)!);
|
||||
|
||||
cache = new CultCache();
|
||||
backingStore = new SingleFileMessagePackBackingStore(CachePath);
|
||||
cache.AddBackingStore(backingStore);
|
||||
cache.PullAllBackingStoresAsync().GetAwaiter().GetResult();
|
||||
Hello = CreateHello(cache.Registry);
|
||||
}
|
||||
|
||||
public string CachePath { get; }
|
||||
|
||||
public CultNetHelloMessage Hello { get; }
|
||||
|
||||
public static AquariumCultStateStore Open(string? path)
|
||||
{
|
||||
return new AquariumCultStateStore(string.IsNullOrWhiteSpace(path) ? DefaultRelativePath : path);
|
||||
}
|
||||
|
||||
public AquariumLiveState LoadOrCreate()
|
||||
{
|
||||
var state = cache.Get<AquariumLiveState>(LiveStateKey)
|
||||
?? cache.GetByName<AquariumLiveState>(AquariumLiveState.PrimaryName);
|
||||
|
||||
if (state != null)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
state = new AquariumLiveState();
|
||||
Save(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Save(AquariumLiveState state)
|
||||
{
|
||||
state.Name = AquariumLiveState.PrimaryName;
|
||||
state.SaveGeneration++;
|
||||
cache.AddAsync(state, new CultRecordHandle<AquariumLiveState>(LiveStateKey)).GetAwaiter().GetResult();
|
||||
backingStore.PushAll();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cache.Dispose();
|
||||
}
|
||||
|
||||
private static CultNetHelloMessage CreateHello(CultDocumentRegistry registry)
|
||||
{
|
||||
return new CultNetHelloMessage
|
||||
{
|
||||
RuntimeId = "aquarium-client",
|
||||
RuntimeKind = "aquarium-engine",
|
||||
Role = "visual-client",
|
||||
DisplayName = "Epiphany Aquarium",
|
||||
SupportsSchemaCatalog = true,
|
||||
SupportedDocumentTypes = registry.AllDescriptors
|
||||
.Select(descriptor => descriptor.SchemaName)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray(),
|
||||
SupportedMessageVersions =
|
||||
[
|
||||
CultNetSchemaVersions.Hello,
|
||||
CultNetSchemaVersions.DocumentPutRaw,
|
||||
CultNetSchemaVersions.DocumentDelete,
|
||||
CultNetSchemaVersions.SnapshotRequest,
|
||||
CultNetSchemaVersions.SnapshotResponseRaw,
|
||||
CultNetSchemaVersions.SchemaCatalogRequest,
|
||||
CultNetSchemaVersions.SchemaCatalogResponse
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
36
src/Aquarium.Engine/State/AquariumLiveState.cs
Normal file
36
src/Aquarium.Engine/State/AquariumLiveState.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using GameCult.Caching;
|
||||
using MessagePack;
|
||||
|
||||
namespace Aquarium.Engine.State;
|
||||
|
||||
[CultDocument("epiphany.aquarium.live_state", "epiphany.aquarium.live_state.v0")]
|
||||
[MessagePackObject]
|
||||
public sealed class AquariumLiveState
|
||||
{
|
||||
public const string PrimaryName = "aquarium-client";
|
||||
|
||||
[Key(0)]
|
||||
[CultName]
|
||||
public string Name { get; set; } = PrimaryName;
|
||||
|
||||
[Key(1)]
|
||||
public float TimeSeconds { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public float CameraTargetX { get; set; }
|
||||
|
||||
[Key(3)]
|
||||
public float CameraTargetY { get; set; }
|
||||
|
||||
[Key(4)]
|
||||
public float CameraYawRadians { get; set; } = Angle.DegreesToRadians(35.0f);
|
||||
|
||||
[Key(5)]
|
||||
public float CameraPitchRadians { get; set; } = Angle.DegreesToRadians(42.0f);
|
||||
|
||||
[Key(6)]
|
||||
public float CameraDistance { get; set; } = 24.0f;
|
||||
|
||||
[Key(7)]
|
||||
public long SaveGeneration { get; set; }
|
||||
}
|
||||
Loading…
Reference in a new issue