acdream/src/AcDream.App/Physics/LiveEntityInboundAuthorityGate.cs

210 lines
6.8 KiB
C#

using System.Numerics;
using AcDream.App.World;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
namespace AcDream.App.Physics;
internal readonly record struct AcceptedMotionNetworkUpdate(
WorldSession.EntityMotionUpdate Update,
LiveEntityRecord Record,
AcceptedPhysicsTimestamps Timestamps,
ulong MovementAuthorityVersion,
ulong VelocityAuthorityVersion);
internal readonly record struct AcceptedVectorNetworkUpdate(
VectorUpdate.Parsed Update,
LiveEntityRecord Record,
ulong VectorAuthorityVersion,
ulong VelocityAuthorityVersion);
internal readonly record struct AcceptedStateNetworkUpdate(
SetState.Parsed Update,
LiveEntityRecord Record,
ulong StateAuthorityVersion);
internal readonly record struct AcceptedPositionNetworkUpdate(
WorldSession.EntityPositionUpdate Update,
RuntimeEntityRecord Canonical,
WorldSession.EntitySpawn Spawn,
AcceptedPhysicsTimestamps Timestamps,
PositionTimestampDisposition TimestampDisposition,
ulong PositionAuthorityVersion,
ulong VelocityAuthorityVersion);
/// <summary>
/// Owns the one retail timestamp/authority admission edge for live inbound
/// Movement, Vector, State, and Position packets. The accepted context pins
/// the exact record and channel versions before any re-entrant App callback
/// can run; channel presenters must revalidate those captures after callbacks.
/// </summary>
internal sealed class LiveEntityInboundAuthorityGate
{
private readonly LiveEntityRuntime _liveEntities;
private readonly Action<uint, AcceptedPhysicsTimestamps> _publishTimestamps;
public LiveEntityInboundAuthorityGate(
LiveEntityRuntime liveEntities,
Action<uint, AcceptedPhysicsTimestamps> publishTimestamps)
{
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_publishTimestamps = publishTimestamps
?? throw new ArgumentNullException(nameof(publishTimestamps));
}
internal uint? LastLivePlayerLandblockId { get; private set; }
internal void ResetSessionState() => LastLivePlayerLandblockId = null;
internal bool TryAcceptMotion(
WorldSession.EntityMotionUpdate update,
bool retainPayload,
out AcceptedMotionNetworkUpdate accepted,
out bool timestampAccepted)
{
accepted = default;
timestampAccepted = _liveEntities.TryApplyMotion(
update,
retainPayload,
out _,
out AcceptedPhysicsTimestamps timestamps);
if (!timestampAccepted)
return false;
LiveEntityRecord? record = null;
ulong movementAuthorityVersion = 0;
ulong velocityAuthorityVersion = 0;
if (retainPayload)
{
if (!_liveEntities.TryGetRecord(update.Guid, out record))
return false;
movementAuthorityVersion = record.MovementAuthorityVersion;
velocityAuthorityVersion = record.VelocityAuthorityVersion;
}
_publishTimestamps(update.Guid, timestamps);
if (!retainPayload)
return false;
if (!_liveEntities.IsCurrentMovementAuthority(
record!,
movementAuthorityVersion)
|| !_liveEntities.IsCurrentVelocityAuthority(
record!,
velocityAuthorityVersion))
{
return false;
}
accepted = new AcceptedMotionNetworkUpdate(
update,
record!,
timestamps,
movementAuthorityVersion,
velocityAuthorityVersion);
return true;
}
internal bool TryAcceptVector(
VectorUpdate.Parsed update,
bool payloadIsValid,
out AcceptedVectorNetworkUpdate accepted)
{
accepted = default;
if (!payloadIsValid
|| !_liveEntities.TryApplyVector(update, out _)
|| !_liveEntities.TryGetRecord(update.Guid, out LiveEntityRecord record))
{
return false;
}
accepted = new AcceptedVectorNetworkUpdate(
update,
record,
record.VectorAuthorityVersion,
record.VelocityAuthorityVersion);
return true;
}
internal bool TryAcceptState(
SetState.Parsed update,
out AcceptedStateNetworkUpdate accepted)
{
accepted = default;
if (!_liveEntities.TryApplyState(update, out _, out _)
|| !_liveEntities.TryGetRecord(update.Guid, out LiveEntityRecord record))
{
return false;
}
accepted = new AcceptedStateNetworkUpdate(
update,
record,
record.StateAuthorityVersion);
return true;
}
internal bool TryAcceptPosition(
WorldSession.EntityPositionUpdate update,
uint localPlayerGuid,
Quaternion? forcePositionRotation,
Vector3? currentLocalVelocity,
bool payloadIsValid,
out AcceptedPositionNetworkUpdate accepted)
{
accepted = default;
if (!payloadIsValid)
return false;
bool isLocalPlayer = update.Guid == localPlayerGuid;
bool known = _liveEntities.TryApplyPosition(
update,
isLocalPlayer,
isLocalPlayer ? forcePositionRotation : null,
isLocalPlayer ? currentLocalVelocity : null,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn spawn,
out AcceptedPhysicsTimestamps timestamps);
if (!known)
{
if (isLocalPlayer)
LastLivePlayerLandblockId = update.Position.LandblockId;
return false;
}
RuntimeEntityRecord? canonical = null;
ulong positionAuthorityVersion = 0;
ulong velocityAuthorityVersion = 0;
if (disposition is not PositionTimestampDisposition.Rejected)
{
if (!_liveEntities.TryGetCanonical(update.Guid, out canonical))
return false;
positionAuthorityVersion = canonical.PositionAuthorityVersion;
velocityAuthorityVersion = canonical.VelocityAuthorityVersion;
}
_publishTimestamps(update.Guid, timestamps);
if (disposition is PositionTimestampDisposition.Rejected)
return false;
if (!_liveEntities.IsCurrentPositionAuthority(
canonical!,
positionAuthorityVersion))
{
return false;
}
accepted = new AcceptedPositionNetworkUpdate(
update,
canonical!,
spawn,
timestamps,
disposition,
positionAuthorityVersion,
velocityAuthorityVersion);
return true;
}
internal void ObserveAcceptedLocalPosition(uint landblockId) =>
LastLivePlayerLandblockId = landblockId;
}