fix(portal): synchronize destination presentation state
This commit is contained in:
parent
4b1bceefbb
commit
e95f55f25b
42 changed files with 2815 additions and 288 deletions
|
|
@ -67,6 +67,7 @@ public sealed class GpuWorldState
|
|||
}
|
||||
|
||||
private readonly Dictionary<uint, LoadedLandblock> _loaded = new();
|
||||
private readonly Dictionary<uint, LandblockStreamTier> _tierByLandblock = new();
|
||||
private readonly Dictionary<uint, (Vector3 Min, Vector3 Max)> _aabbs = new();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -75,6 +76,12 @@ public sealed class GpuWorldState
|
|||
/// Drained into <see cref="_loaded"/> in <see cref="AddLandblock"/>.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, List<WorldEntity>> _pendingByLandblock = new();
|
||||
// Far-to-near promotion can complete before the base far landblock is
|
||||
// published. Preserve its independently-prepared EnvCell geometry ids
|
||||
// alongside the parked entity layer so the later AddLandblock transaction
|
||||
// cannot open the render gate before those shells upload.
|
||||
private readonly Dictionary<uint, HashSet<ulong>> _pendingRenderIdsByLandblock = new();
|
||||
private readonly HashSet<uint> _pendingNearTierLandblocks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Entities that must survive landblock unloads (e.g. the player character).
|
||||
|
|
@ -96,6 +103,14 @@ public sealed class GpuWorldState
|
|||
public event Action<uint, bool>? LiveProjectionVisibilityChanged;
|
||||
|
||||
public bool IsLoaded(uint landblockId) => _loaded.ContainsKey(landblockId);
|
||||
public bool IsNearTier(uint landblockId) =>
|
||||
_tierByLandblock.TryGetValue(landblockId, out var tier)
|
||||
&& tier == LandblockStreamTier.Near;
|
||||
public bool IsNearTierOrPending(uint landblockId) =>
|
||||
IsNearTier(landblockId) || _pendingNearTierLandblocks.Contains(landblockId);
|
||||
public bool IsRenderReady(uint landblockId) =>
|
||||
_loaded.ContainsKey(landblockId)
|
||||
&& (_wbSpawnAdapter?.IsLandblockRenderReady(landblockId) ?? true);
|
||||
public bool IsLiveEntityVisible(uint serverGuid) =>
|
||||
serverGuid != 0 && _visibleLiveGuids.Contains(serverGuid);
|
||||
|
||||
|
|
@ -204,8 +219,17 @@ public sealed class GpuWorldState
|
|||
public int PersistentGuidCount => _persistentGuids.Count;
|
||||
public int PendingVisibilityTransitionCount => _visibilityTransitions.Count;
|
||||
|
||||
public void AddLandblock(LoadedLandblock landblock)
|
||||
public void AddLandblock(
|
||||
LoadedLandblock landblock,
|
||||
IEnumerable<ulong>? additionalRenderIds = null,
|
||||
LandblockStreamTier tier = LandblockStreamTier.Near)
|
||||
{
|
||||
// A stale Far completion must never replace a newer Near entity layer.
|
||||
// StreamingController normally filters it before render-side apply;
|
||||
// keep the state boundary independently monotonic as well.
|
||||
if (tier == LandblockStreamTier.Far && IsNearTier(landblock.LandblockId))
|
||||
return;
|
||||
|
||||
// If pending live entities have been waiting for this landblock,
|
||||
// merge them into the LoadedLandblock record before storing. The
|
||||
// record's Entities field is IReadOnlyList; we replace the whole
|
||||
|
|
@ -222,9 +246,29 @@ public sealed class GpuWorldState
|
|||
_pendingByLandblock.Remove(landblock.LandblockId);
|
||||
}
|
||||
|
||||
HashSet<ulong>? mergedRenderIds = additionalRenderIds is null
|
||||
? null
|
||||
: new HashSet<ulong>(additionalRenderIds);
|
||||
if (_pendingRenderIdsByLandblock.Remove(landblock.LandblockId, out var pendingRenderIds))
|
||||
{
|
||||
mergedRenderIds ??= new HashSet<ulong>();
|
||||
mergedRenderIds.UnionWith(pendingRenderIds);
|
||||
}
|
||||
|
||||
bool pendingNear = _pendingNearTierLandblocks.Remove(landblock.LandblockId);
|
||||
if (pendingNear
|
||||
|| (_tierByLandblock.TryGetValue(landblock.LandblockId, out var currentTier)
|
||||
&& currentTier == LandblockStreamTier.Near))
|
||||
{
|
||||
tier = LandblockStreamTier.Near;
|
||||
}
|
||||
|
||||
_loaded[landblock.LandblockId] = landblock;
|
||||
_tierByLandblock[landblock.LandblockId] = tier;
|
||||
if (_wbSpawnAdapter is not null)
|
||||
_wbSpawnAdapter.OnLandblockLoaded(_loaded[landblock.LandblockId]);
|
||||
_wbSpawnAdapter.OnLandblockLoaded(
|
||||
_loaded[landblock.LandblockId],
|
||||
mergedRenderIds);
|
||||
|
||||
// C.1.5b: fire DefaultScript for dat-hydrated entities (ServerGuid==0).
|
||||
// LiveEntityRuntime owns activation for live objects. This static-only
|
||||
|
|
@ -397,10 +441,13 @@ public sealed class GpuWorldState
|
|||
}
|
||||
|
||||
_pendingByLandblock.Remove(canonical);
|
||||
_pendingRenderIdsByLandblock.Remove(canonical);
|
||||
_pendingNearTierLandblocks.Remove(canonical);
|
||||
if (retainedLive.Count > 0)
|
||||
_pendingByLandblock[canonical] = retainedLive;
|
||||
_aabbs.Remove(canonical);
|
||||
|
||||
_tierByLandblock.Remove(canonical);
|
||||
if (_loaded.Remove(canonical))
|
||||
RebuildFlatView();
|
||||
}
|
||||
|
|
@ -612,6 +659,18 @@ public sealed class GpuWorldState
|
|||
// protects against future callers that mirror live projection placement's
|
||||
// cell-resolved-id pattern.
|
||||
uint canonical = (landblockId & 0xFFFF0000u) | 0xFFFFu;
|
||||
// A promotion may have parked its Near layer before the Far base load
|
||||
// exists. Demotion must retire that pending tier just as completely as
|
||||
// an installed tier, while preserving live server projections.
|
||||
_pendingNearTierLandblocks.Remove(canonical);
|
||||
_pendingRenderIdsByLandblock.Remove(canonical);
|
||||
if (_pendingByLandblock.TryGetValue(canonical, out var pending))
|
||||
{
|
||||
pending.RemoveAll(entity => entity.ServerGuid == 0);
|
||||
if (pending.Count == 0)
|
||||
_pendingByLandblock.Remove(canonical);
|
||||
}
|
||||
|
||||
if (!_loaded.TryGetValue(canonical, out var lb)) return;
|
||||
if (_wbSpawnAdapter is not null)
|
||||
_wbSpawnAdapter.OnLandblockUnloaded(canonical);
|
||||
|
|
@ -642,12 +701,7 @@ public sealed class GpuWorldState
|
|||
lb.LandblockId,
|
||||
lb.Heightmap,
|
||||
retainedLive);
|
||||
if (_pendingByLandblock.TryGetValue(canonical, out var pending))
|
||||
{
|
||||
pending.RemoveAll(entity => entity.ServerGuid == 0);
|
||||
if (pending.Count == 0)
|
||||
_pendingByLandblock.Remove(canonical);
|
||||
}
|
||||
_tierByLandblock[canonical] = LandblockStreamTier.Far;
|
||||
RebuildFlatView();
|
||||
}
|
||||
|
||||
|
|
@ -664,7 +718,10 @@ public sealed class GpuWorldState
|
|||
/// callers may pass cell-resolved ids and they will key correctly.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void AddEntitiesToExistingLandblock(uint landblockId, IReadOnlyList<WorldEntity> entities)
|
||||
public bool AddEntitiesToExistingLandblock(
|
||||
uint landblockId,
|
||||
IReadOnlyList<WorldEntity> entities,
|
||||
IEnumerable<ulong>? additionalRenderIds = null)
|
||||
{
|
||||
// A.5 T14 follow-up: canonicalize for symmetry with live projection placement.
|
||||
uint canonical = (landblockId & 0xFFFF0000u) | 0xFFFFu;
|
||||
|
|
@ -677,14 +734,25 @@ public sealed class GpuWorldState
|
|||
_pendingByLandblock[canonical] = bucket;
|
||||
}
|
||||
bucket.AddRange(entities);
|
||||
return;
|
||||
_pendingNearTierLandblocks.Add(canonical);
|
||||
if (additionalRenderIds is not null)
|
||||
{
|
||||
if (!_pendingRenderIdsByLandblock.TryGetValue(canonical, out var renderIds))
|
||||
{
|
||||
renderIds = new HashSet<ulong>();
|
||||
_pendingRenderIdsByLandblock[canonical] = renderIds;
|
||||
}
|
||||
renderIds.UnionWith(additionalRenderIds);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var merged = new List<WorldEntity>(lb.Entities.Count + entities.Count);
|
||||
merged.AddRange(lb.Entities);
|
||||
merged.AddRange(entities);
|
||||
_loaded[canonical] = new LoadedLandblock(lb.LandblockId, lb.Heightmap, merged);
|
||||
_tierByLandblock[canonical] = LandblockStreamTier.Near;
|
||||
if (_wbSpawnAdapter is not null)
|
||||
_wbSpawnAdapter.OnLandblockLoaded(_loaded[canonical]);
|
||||
_wbSpawnAdapter.OnLandblockLoaded(_loaded[canonical], additionalRenderIds);
|
||||
|
||||
// C.1.5b: fire DefaultScript for each promoted dat-hydrated entity.
|
||||
// All entities arriving via this path are atlas-tier by construction
|
||||
|
|
@ -697,6 +765,7 @@ public sealed class GpuWorldState
|
|||
}
|
||||
|
||||
RebuildFlatView();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RebuildFlatView()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue