2026-06-25 18:09:33 +01:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using GameCult.Aetheria.State.Verse;
|
|
|
|
|
|
|
|
|
|
public sealed class AetheriaUnityObservedFrameApplier
|
|
|
|
|
{
|
|
|
|
|
private readonly Func<AetheriaDaemonObserver> _resolveObserver;
|
|
|
|
|
private readonly Func<int, GalaxyZone> _resolveObservedZone;
|
|
|
|
|
private readonly Func<Zone> _getZone;
|
|
|
|
|
private readonly Action<Zone> _setZone;
|
2026-06-28 03:34:08 +01:00
|
|
|
private readonly AetheriaUnityObservedEntityIndex _entityIndex;
|
2026-06-28 14:37:28 +01:00
|
|
|
private readonly AetheriaUnityObservedEntityRestorer _entityRestorer;
|
|
|
|
|
private readonly AetheriaUnityObservedZoneContextFactory _zoneContextFactory;
|
2026-06-25 18:09:33 +01:00
|
|
|
private readonly Func<ZoneRenderer> _resolveZoneRenderer;
|
|
|
|
|
private readonly Func<Entity> _getCurrentEntity;
|
|
|
|
|
private readonly Action<Entity> _restoreCurrentEntityBinding;
|
|
|
|
|
private readonly Action<string> _logWarning;
|
|
|
|
|
|
|
|
|
|
private long _lastAppliedZoneRenderFrameId = -1;
|
|
|
|
|
private string _lastAppliedZoneRenderRunId = "";
|
|
|
|
|
private int _lastAppliedZoneRenderZoneIndex = -1;
|
|
|
|
|
private AetheriaRuntimeZoneRenderDocument _lastZoneRender;
|
|
|
|
|
|
|
|
|
|
public AetheriaUnityObservedFrameApplier(
|
|
|
|
|
Func<AetheriaDaemonObserver> resolveObserver,
|
|
|
|
|
Func<int, GalaxyZone> resolveObservedZone,
|
|
|
|
|
Func<Zone> getZone,
|
|
|
|
|
Action<Zone> setZone,
|
2026-06-28 03:34:08 +01:00
|
|
|
AetheriaUnityObservedEntityIndex entityIndex,
|
2026-06-28 14:37:28 +01:00
|
|
|
AetheriaUnityObservedEntityRestorer entityRestorer,
|
|
|
|
|
AetheriaUnityObservedZoneContextFactory zoneContextFactory,
|
2026-06-25 18:09:33 +01:00
|
|
|
Func<ZoneRenderer> resolveZoneRenderer,
|
|
|
|
|
Func<Entity> getCurrentEntity,
|
|
|
|
|
Action<Entity> restoreCurrentEntityBinding,
|
|
|
|
|
Action<string> logWarning)
|
|
|
|
|
{
|
|
|
|
|
_resolveObserver = resolveObserver ?? (() => null);
|
|
|
|
|
_resolveObservedZone = resolveObservedZone ?? (_ => null);
|
|
|
|
|
_getZone = getZone ?? (() => null);
|
|
|
|
|
_setZone = setZone ?? (_ => { });
|
2026-06-28 03:34:08 +01:00
|
|
|
_entityIndex = entityIndex ?? throw new ArgumentNullException(nameof(entityIndex));
|
2026-06-28 14:37:28 +01:00
|
|
|
_entityRestorer = entityRestorer ?? throw new ArgumentNullException(nameof(entityRestorer));
|
|
|
|
|
_zoneContextFactory = zoneContextFactory ?? throw new ArgumentNullException(nameof(zoneContextFactory));
|
2026-06-25 18:09:33 +01:00
|
|
|
_resolveZoneRenderer = resolveZoneRenderer ?? (() => null);
|
|
|
|
|
_getCurrentEntity = getCurrentEntity ?? (() => null);
|
|
|
|
|
_restoreCurrentEntityBinding = restoreCurrentEntityBinding ?? (_ => { });
|
|
|
|
|
_logWarning = logWarning ?? (_ => { });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool ApplyLatestZoneRender()
|
|
|
|
|
{
|
|
|
|
|
var observer = _resolveObserver();
|
|
|
|
|
if (observer == null || !observer.HasAuthoritativeState)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 07:34:11 +01:00
|
|
|
var render = observer.LastObservedState?.ZoneRender;
|
2026-06-25 18:09:33 +01:00
|
|
|
if (render == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TryRestoreEntityGraphFromZoneRender(render);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryRestoreEntityGraphFromZoneRender(AetheriaRuntimeZoneRenderDocument render)
|
|
|
|
|
{
|
|
|
|
|
if (render == null || render.ZoneIndex < 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(render.RunId))
|
|
|
|
|
{
|
|
|
|
|
_logWarning("Aetheria zone-render feed does not identify a run id.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var runId = render.RunId;
|
|
|
|
|
var currentEntityKey = string.IsNullOrWhiteSpace(render.CurrentEntityKey) ? "" : render.CurrentEntityKey;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(currentEntityKey))
|
|
|
|
|
{
|
|
|
|
|
_logWarning($"Aetheria zone-render feed for run {runId} does not identify a current entity.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 11:31:48 +01:00
|
|
|
var entitySnapshots = AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, render.ZoneIndex, render.EntitySnapshots)
|
2026-06-28 11:26:11 +01:00
|
|
|
.OrderBy(entity => entity.EntityIndex)
|
2026-06-25 18:09:33 +01:00
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
if (entitySnapshots.Length == 0)
|
|
|
|
|
{
|
2026-06-28 03:34:08 +01:00
|
|
|
_logWarning($"Aetheria zone-render feed has no entity snapshots for zone {render.ZoneIndex}.");
|
2026-06-25 18:09:33 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var targetZone = _resolveObservedZone(render.ZoneIndex);
|
|
|
|
|
if (targetZone == null)
|
|
|
|
|
{
|
|
|
|
|
_logWarning($"Aetheria zone-render feed references missing zone index {render.ZoneIndex}.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (render.FrameId == _lastAppliedZoneRenderFrameId &&
|
|
|
|
|
string.Equals(render.RunId, _lastAppliedZoneRenderRunId, StringComparison.Ordinal) &&
|
|
|
|
|
render.ZoneIndex == _lastAppliedZoneRenderZoneIndex)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var zoneRenderer = _resolveZoneRenderer();
|
2026-06-28 14:37:28 +01:00
|
|
|
if (_entityRestorer.TryApplyInPlace(
|
2026-06-25 18:09:33 +01:00
|
|
|
_lastAppliedZoneRenderRunId,
|
|
|
|
|
_lastAppliedZoneRenderZoneIndex,
|
|
|
|
|
runId,
|
|
|
|
|
render.ZoneIndex,
|
|
|
|
|
entitySnapshots,
|
|
|
|
|
currentEntityKey,
|
|
|
|
|
_getCurrentEntity(),
|
|
|
|
|
out var reboundCurrentEntity))
|
|
|
|
|
{
|
|
|
|
|
if (reboundCurrentEntity != null)
|
|
|
|
|
_restoreCurrentEntityBinding(reboundCurrentEntity);
|
|
|
|
|
zoneRenderer?.ApplyZoneRender(render);
|
|
|
|
|
zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render);
|
|
|
|
|
_lastAppliedZoneRenderFrameId = render.FrameId;
|
2026-06-28 07:34:11 +01:00
|
|
|
_lastAppliedZoneRenderRunId = runId;
|
|
|
|
|
_lastAppliedZoneRenderZoneIndex = render.ZoneIndex;
|
2026-06-25 18:09:33 +01:00
|
|
|
_lastZoneRender = render;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_getZone()?.GalaxyZone != targetZone)
|
|
|
|
|
{
|
2026-06-28 14:37:28 +01:00
|
|
|
var resolvedZone = _zoneContextFactory.ResolveContext(targetZone, render);
|
2026-06-25 18:09:33 +01:00
|
|
|
if (resolvedZone == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
_setZone(resolvedZone);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 14:37:28 +01:00
|
|
|
_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone());
|
2026-06-28 03:34:08 +01:00
|
|
|
zoneRenderer?.LoadDaemonZoneView(_entityIndex.EntitiesByDaemonIndex, render);
|
|
|
|
|
if (_entityIndex.TryResolveEntityByRecordKey(currentEntityKey, out var currentEntity))
|
2026-06-25 18:09:33 +01:00
|
|
|
_restoreCurrentEntityBinding(currentEntity);
|
|
|
|
|
zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render);
|
|
|
|
|
_lastAppliedZoneRenderFrameId = render.FrameId;
|
|
|
|
|
_lastAppliedZoneRenderRunId = runId;
|
|
|
|
|
_lastAppliedZoneRenderZoneIndex = render.ZoneIndex;
|
|
|
|
|
_lastZoneRender = render;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|