feat(app): project canonical runtime placements
This commit is contained in:
parent
ef43667872
commit
74c9b155bd
5 changed files with 1395 additions and 0 deletions
|
|
@ -5,6 +5,7 @@ using AcDream.Core.Physics;
|
|||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Physics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
||||
|
|
@ -386,6 +387,15 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
private readonly Dictionary<RuntimeEntityKey, ILiveEntityAnimationRuntime> _spatialAnimations = new();
|
||||
private readonly List<RuntimeEntityRecord> _spatialRootCanonicalScratch = new();
|
||||
private readonly List<RuntimeEntityRecord> _spatialRemoteCanonicalScratch = new();
|
||||
// Runtime placement receipts are presentation observations of an already
|
||||
// committed canonical SetPosition transaction. GpuWorldState still emits
|
||||
// its ordinary visibility callback while that observation rebuckets or
|
||||
// withdraws a sidecar, so pin the exact incarnation while the callback is
|
||||
// in flight. A depth (rather than a bool) preserves nested/re-entrant
|
||||
// projection mutations without ever turning graphical visibility into a
|
||||
// Runtime physics/workset mutation.
|
||||
private readonly Dictionary<RuntimeEntityKey, int>
|
||||
_presentationOnlySpatialMutationDepth = new();
|
||||
private bool _isClearing;
|
||||
private bool _sessionClearPendingFinalization;
|
||||
private bool _isRegisteringResources;
|
||||
|
|
@ -446,6 +456,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_projections.VisibleRecords;
|
||||
internal IReadOnlyCollection<RuntimeEntityRecord> CanonicalRecords =>
|
||||
_directory.ActiveRecords;
|
||||
internal ulong SessionLifetimeVersion => _directory.SessionLifetimeVersion;
|
||||
internal RuntimePhysicsState Physics => _physics;
|
||||
public IReadOnlyDictionary<uint, WorldSession.EntitySpawn> Snapshots => _directory.Snapshots;
|
||||
internal int AnimationRuntimeCount
|
||||
|
|
@ -863,6 +874,269 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies one canonical Runtime placement receipt to the graphical
|
||||
/// sidecar only. Runtime has already committed identity, position,
|
||||
/// collision residence, object-clock state, and simulation worksets.
|
||||
/// </summary>
|
||||
internal bool TryApplyRuntimePlacementProjection(
|
||||
in RuntimePlacementProjectionSnapshot projection)
|
||||
{
|
||||
if (projection.Kind is RuntimePlacementProjectionKind.Discard)
|
||||
{
|
||||
// Discard cancels only an unacknowledged observation. If its Place
|
||||
// was already projected, retail keeps that last committed frame
|
||||
// visible until a later canonical Place or Withdraw supersedes it.
|
||||
return true;
|
||||
}
|
||||
|
||||
RuntimePlacementProjectionToken token = projection.Token;
|
||||
if (!TryGetRuntimePlacementProjectionRecord(
|
||||
token,
|
||||
requirePlacementVersions:
|
||||
projection.Kind is RuntimePlacementProjectionKind.Place,
|
||||
out LiveEntityRecord? record)
|
||||
|| record.WorldEntity is not { } entity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (projection.Kind is RuntimePlacementProjectionKind.Place
|
||||
&& !_spatial.IsLoaded(
|
||||
(token.ExactCellId & 0xFFFF0000u) | 0xFFFFu))
|
||||
{
|
||||
// A canonical SetPosition receipt is not permission to create a
|
||||
// pending graphical bucket. Keep the FIFO head unacknowledged
|
||||
// until the destination backend exists, otherwise GpuWorldState's
|
||||
// later pending-drain edge escapes this exact receipt transaction.
|
||||
return false;
|
||||
}
|
||||
|
||||
return projection.Kind switch
|
||||
{
|
||||
RuntimePlacementProjectionKind.Place =>
|
||||
TryApplyRuntimePlacementPlace(in projection, record, entity),
|
||||
RuntimePlacementProjectionKind.Withdraw =>
|
||||
TryApplyRuntimePlacementWithdrawal(token, record, entity),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private bool TryApplyRuntimePlacementPlace(
|
||||
in RuntimePlacementProjectionSnapshot projection,
|
||||
LiveEntityRecord record,
|
||||
WorldEntity entity)
|
||||
{
|
||||
RuntimePlacementProjectionToken token = projection.Token;
|
||||
RuntimeEntityKey key = token.Entity;
|
||||
ulong projectionOperation = ++record.ProjectionMutationVersion;
|
||||
|
||||
entity.SetPosition(projection.WorldPosition);
|
||||
entity.Rotation = projection.Orientation;
|
||||
entity.ParentCellId = token.ExactCellId;
|
||||
entity.EffectCellId = token.ExactCellId;
|
||||
record.IsSpatiallyProjected = true;
|
||||
|
||||
Exception? spatialNotificationFailure = null;
|
||||
uint priorRebucketingGuid = _rebucketingGuid;
|
||||
_rebucketingGuid = record.ServerGuid;
|
||||
BeginPresentationOnlySpatialMutation(key);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_spatial.RebucketLiveEntity(key, entity, token.ExactCellId);
|
||||
}
|
||||
catch (AggregateException error)
|
||||
{
|
||||
spatialNotificationFailure = error;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndPresentationOnlySpatialMutation(key);
|
||||
_rebucketingGuid = priorRebucketingGuid;
|
||||
}
|
||||
|
||||
if (!IsCurrentProjectionOperation(
|
||||
record.ServerGuid,
|
||||
record,
|
||||
projectionOperation)
|
||||
|| !TryGetRuntimePlacementProjectionRecord(
|
||||
token,
|
||||
requirePlacementVersions: true,
|
||||
out LiveEntityRecord? current)
|
||||
|| !ReferenceEquals(current, record)
|
||||
|| !ReferenceEquals(current.WorldEntity, entity))
|
||||
{
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
record.ServerGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visible = _spatial.IsLiveEntityProjectionResident(key);
|
||||
record.IsSpatiallyVisible = visible;
|
||||
RefreshSpatialPresentationIndexes(record);
|
||||
RefreshPresentation(record);
|
||||
|
||||
if (!IsCurrentProjectionOperation(
|
||||
record.ServerGuid,
|
||||
record,
|
||||
projectionOperation))
|
||||
{
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
record.ServerGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
record.ServerGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryApplyRuntimePlacementWithdrawal(
|
||||
in RuntimePlacementProjectionToken token,
|
||||
LiveEntityRecord record,
|
||||
WorldEntity entity)
|
||||
{
|
||||
RuntimeEntityKey key = token.Entity;
|
||||
ulong projectionOperation = ++record.ProjectionMutationVersion;
|
||||
record.IsSpatiallyProjected = false;
|
||||
|
||||
Exception? spatialNotificationFailure = null;
|
||||
uint priorRebucketingGuid = _rebucketingGuid;
|
||||
_rebucketingGuid = record.ServerGuid;
|
||||
BeginPresentationOnlySpatialMutation(key);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_spatial.RemoveLiveEntityProjection(entity);
|
||||
}
|
||||
catch (AggregateException error)
|
||||
{
|
||||
spatialNotificationFailure = error;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndPresentationOnlySpatialMutation(key);
|
||||
_rebucketingGuid = priorRebucketingGuid;
|
||||
}
|
||||
|
||||
if (!IsCurrentProjectionOperation(
|
||||
record.ServerGuid,
|
||||
record,
|
||||
projectionOperation)
|
||||
|| !TryGetRuntimePlacementProjectionRecord(
|
||||
token,
|
||||
requirePlacementVersions: false,
|
||||
out LiveEntityRecord? current)
|
||||
|| !ReferenceEquals(current, record)
|
||||
|| !ReferenceEquals(current.WorldEntity, entity))
|
||||
{
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
record.ServerGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
record.IsSpatiallyVisible = false;
|
||||
RefreshSpatialPresentationIndexes(record);
|
||||
RefreshPresentation(record);
|
||||
|
||||
if (!IsCurrentProjectionOperation(
|
||||
record.ServerGuid,
|
||||
record,
|
||||
projectionOperation))
|
||||
{
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
record.ServerGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return false;
|
||||
}
|
||||
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
record.ServerGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetRuntimePlacementProjectionRecord(
|
||||
in RuntimePlacementProjectionToken token,
|
||||
bool requirePlacementVersions,
|
||||
out LiveEntityRecord record)
|
||||
{
|
||||
if (!token.IsValid
|
||||
|| token.SessionLifetimeVersion != _directory.SessionLifetimeVersion
|
||||
|| !_projections.TryGet(token.Entity, out record!)
|
||||
|| !_directory.IsCurrent(record.Canonical)
|
||||
|| record.Canonical.Key != token.Entity
|
||||
|| RequireProjectionKey(record) != token.Entity
|
||||
|| !IsValidPortalPlacementAuthority(token))
|
||||
{
|
||||
record = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requirePlacementVersions
|
||||
&& (record.Canonical.PositionAuthorityVersion
|
||||
!= token.PositionAuthorityVersion
|
||||
|| record.Canonical.SpatialAuthorityVersion
|
||||
!= token.SpatialAuthorityVersion
|
||||
|| record.Canonical.PlacementCommitVersion
|
||||
!= token.PlacementCommitVersion
|
||||
|| record.Canonical.FullCellId != token.ExactCellId))
|
||||
{
|
||||
record = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidPortalPlacementAuthority(
|
||||
in RuntimePlacementProjectionToken token)
|
||||
{
|
||||
RuntimePortalPlacementAuthority portal = token.Portal;
|
||||
if (!portal.Present)
|
||||
return portal.IsEmpty;
|
||||
|
||||
return portal.IsValid
|
||||
&& portal.Projection.DestinationCell == token.ExactCellId;
|
||||
}
|
||||
|
||||
private void BeginPresentationOnlySpatialMutation(RuntimeEntityKey key)
|
||||
{
|
||||
_presentationOnlySpatialMutationDepth.TryGetValue(key, out int depth);
|
||||
_presentationOnlySpatialMutationDepth[key] = checked(depth + 1);
|
||||
}
|
||||
|
||||
private void EndPresentationOnlySpatialMutation(RuntimeEntityKey key)
|
||||
{
|
||||
if (!_presentationOnlySpatialMutationDepth.TryGetValue(
|
||||
key,
|
||||
out int depth)
|
||||
|| depth <= 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Presentation-only spatial mutation depth was not balanced.");
|
||||
}
|
||||
|
||||
if (depth == 1)
|
||||
_presentationOnlySpatialMutationDepth.Remove(key);
|
||||
else
|
||||
_presentationOnlySpatialMutationDepth[key] = depth - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes only the render-bucket reference. The logical record and every
|
||||
/// create-time resource remain alive for later projection/rebucketing.
|
||||
|
|
@ -2588,6 +2862,33 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
|
||||
_physics.AcknowledgeSpatialProjection(record.Canonical, spatial);
|
||||
|
||||
RefreshSpatialPresentationIndexes(record, current, spatial, key);
|
||||
}
|
||||
|
||||
private void RefreshSpatialPresentationIndexes(LiveEntityRecord record)
|
||||
{
|
||||
bool current = IsCurrentRecord(record);
|
||||
bool spatial = current && HasSpatialRuntimeProjection(record);
|
||||
if (record.ProjectionKey is not { } key)
|
||||
{
|
||||
if (record.WorldEntity is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Materialized live entity 0x{record.ServerGuid:X8}/{record.Generation} has no exact projection key.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshSpatialPresentationIndexes(record, current, spatial, key);
|
||||
}
|
||||
|
||||
private void RefreshSpatialPresentationIndexes(
|
||||
LiveEntityRecord record,
|
||||
bool current,
|
||||
bool spatial,
|
||||
RuntimeEntityKey key)
|
||||
{
|
||||
|
||||
if (record.WorldEntity is not null)
|
||||
{
|
||||
if (spatial && record.AnimationRuntime is { } animation)
|
||||
|
|
@ -2641,6 +2942,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
bool wasVisible = record.IsSpatiallyVisible;
|
||||
if (RequireProjectionKey(record) != key)
|
||||
return;
|
||||
if (_presentationOnlySpatialMutationDepth.ContainsKey(key))
|
||||
{
|
||||
record.IsSpatiallyVisible = visible;
|
||||
RefreshSpatialPresentationIndexes(record);
|
||||
RefreshPresentation(record);
|
||||
return;
|
||||
}
|
||||
bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical);
|
||||
record.IsSpatiallyVisible = visible;
|
||||
bool isOrdinaryRoot = record.ProjectionKind is LiveEntityProjectionKind.World
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue