refactor(world): extract live entity network updates
Move Position, Vector, State, Movement, and equal-generation CreateObject routing out of GameWindow while preserving per-channel authority, ForcePosition acknowledgement, and motion-runtime ownership. Add adversarial authority and exact-wire coverage so reentrant updates and GUID reuse cannot publish stale state.
This commit is contained in:
parent
c203e4799a
commit
aa90c64666
19 changed files with 3701 additions and 2241 deletions
209
src/AcDream.App/Physics/LiveEntityInboundAuthorityGate.cs
Normal file
209
src/AcDream.App/Physics/LiveEntityInboundAuthorityGate.cs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
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,
|
||||
LiveEntityRecord Record,
|
||||
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;
|
||||
}
|
||||
|
||||
LiveEntityRecord? record = null;
|
||||
ulong positionAuthorityVersion = 0;
|
||||
ulong velocityAuthorityVersion = 0;
|
||||
if (disposition is not PositionTimestampDisposition.Rejected)
|
||||
{
|
||||
if (!_liveEntities.TryGetRecord(update.Guid, out record))
|
||||
return false;
|
||||
positionAuthorityVersion = record.PositionAuthorityVersion;
|
||||
velocityAuthorityVersion = record.VelocityAuthorityVersion;
|
||||
}
|
||||
|
||||
_publishTimestamps(update.Guid, timestamps);
|
||||
if (disposition is PositionTimestampDisposition.Rejected)
|
||||
return false;
|
||||
if (!_liveEntities.IsCurrentPositionAuthority(
|
||||
record!,
|
||||
positionAuthorityVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
accepted = new AcceptedPositionNetworkUpdate(
|
||||
update,
|
||||
record!,
|
||||
spawn,
|
||||
timestamps,
|
||||
disposition,
|
||||
positionAuthorityVersion,
|
||||
velocityAuthorityVersion);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void ObserveAcceptedLocalPosition(uint landblockId) =>
|
||||
LastLivePlayerLandblockId = landblockId;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue