Skip to content

Commit 0fa904b

Browse files
committed
fix resync flow & skip duplicated inputs
1 parent bfd122c commit 0fa904b

File tree

6 files changed

+50
-99
lines changed

6 files changed

+50
-99
lines changed

src/DCLPulse/Messaging/PlayerStateInputHandler.cs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,58 @@ public class PlayerStateInputHandler(
1414
ParcelEncoder parcelEncoder)
1515
: RuntimePacketHandlerBase<PlayerStateInputHandler>(logger), IMessageHandler
1616
{
17+
private const float TOLERANCE = 0.001f;
18+
1719
public void Handle(Dictionary<PeerIndex, PeerState> peers, PeerIndex from, ClientMessage message)
1820
{
1921
if (SkipFromUnauthorizedPeer(peers, from, message, out _))
2022
return;
2123

2224
PlayerStateInput input = message.Input;
25+
PlayerState state = input.State;
26+
27+
// Skip publishing if the state hasn't changed. Without this, seq increments on every
28+
// input message even when the peer is idle, causing observers to receive deltas with
29+
// non-contiguous sequences (gaps from suppressed no-diff deltas) and triggering more resync loops.
30+
if (snapshotBoard.TryRead(from, out PeerSnapshot current) && IsSameState(current, state))
31+
return;
2332

24-
Vector3 globalPeerPosition = parcelEncoder.DecodeToGlobalPosition(input.State.ParcelIndex, input.State.Position);
33+
Vector3 globalPeerPosition = parcelEncoder.DecodeToGlobalPosition(state.ParcelIndex, state.Position);
2534

2635
var snapshot = new PeerSnapshot(
2736
snapshotBoard.LastSeq(from) + 1,
2837
timeProvider.MonotonicTime,
29-
input.State.ParcelIndex,
30-
input.State.Position,
38+
state.ParcelIndex,
39+
state.Position,
3140
globalPeerPosition,
32-
input.State.Velocity,
33-
input.State.RotationY,
34-
input.State.MovementBlend,
35-
input.State.SlideBlend,
36-
input.State.GetHeadYaw(),
37-
input.State.GetHeadPitch(),
38-
(PlayerAnimationFlags)input.State.StateFlags,
39-
input.State.GlideState);
41+
state.Velocity,
42+
state.RotationY,
43+
state.MovementBlend,
44+
state.SlideBlend,
45+
state.GetHeadYaw(),
46+
state.GetHeadPitch(),
47+
(PlayerAnimationFlags)state.StateFlags,
48+
state.GlideState);
4049

4150
snapshotBoard.Publish(from, in snapshot);
4251
spatialGrid.Set(from, snapshot.GlobalPosition);
4352
}
53+
54+
private static bool IsSameState(in PeerSnapshot current, PlayerState incoming) =>
55+
current.Parcel == incoming.ParcelIndex
56+
&& current.LocalPosition == (Vector3)incoming.Position
57+
&& current.Velocity == (Vector3)incoming.Velocity
58+
&& FloatEquals(current.RotationY, incoming.RotationY)
59+
&& FloatEquals(current.MovementBlend, incoming.MovementBlend)
60+
&& FloatEquals(current.SlideBlend, incoming.SlideBlend)
61+
&& FloatEquals(current.HeadYaw, incoming.GetHeadYaw())
62+
&& FloatEquals(current.HeadPitch, incoming.GetHeadPitch())
63+
&& current.AnimationFlags == (PlayerAnimationFlags)incoming.StateFlags
64+
&& current.GlideState == incoming.GlideState;
65+
66+
private static bool FloatEquals(in float a, in float b) =>
67+
Math.Abs(a - b) < TOLERANCE;
68+
69+
private static bool FloatEquals(float? a, float? b) =>
70+
(!a.HasValue && !b.HasValue) || (a.HasValue && b.HasValue && Math.Abs(a.Value - b.Value) < TOLERANCE);
4471
}

src/DCLPulse/Peers/Simulation/PeerSimulation.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,18 +217,15 @@ private void ProcessVisibleSubjects(
217217
}
218218
else
219219
{
220-
if (resyncRequests != null && resyncRequests.Remove(entry.Subject, out uint lastKnownSeq))
220+
if (resyncRequests != null && resyncRequests.Remove(entry.Subject, out _))
221221
{
222-
// Try a targeted delta from the client's baseline; fall back to full state
223-
// if the baseline is evicted, the seq hasn't advanced, or all fields are within epsilon.
224-
if (!snapshotBoard.TryRead(entry.Subject, lastKnownSeq, out PeerSnapshot knownSnapshot)
225-
|| !SendDelta(knownSnapshot, ITransport.PacketMode.RELIABLE))
222+
// Always respond with STATE_FULL, never a delta. The client uses the
223+
// message type (full vs delta) to resolve the pending resync — a delta
224+
// is indistinguishable from a normal update and won't clear the resync state.
225+
messagePipe.Send(new OutgoingMessage(observerId, new ServerMessage
226226
{
227-
messagePipe.Send(new OutgoingMessage(observerId, new ServerMessage
228-
{
229-
PlayerStateFull = CreateFullState(entry.Subject, subjectSnapshot),
230-
}, ITransport.PacketMode.RELIABLE));
231-
}
227+
PlayerStateFull = CreateFullState(entry.Subject, subjectSnapshot),
228+
}, ITransport.PacketMode.RELIABLE));
232229
}
233230
else
234231
SendDelta(view.LastSentSnapshot, ITransport.PacketMode.UNRELIABLE_SEQUENCED);
@@ -273,6 +270,8 @@ bool SendDelta(PeerSnapshot baseline, ITransport.PacketMode packetMode)
273270
PlayerStateDelta = delta,
274271
}, packetMode));
275272

273+
logger.LogInformation("Sending delta player state {Sequence} to {Observer}", delta.NewSeq, observerId);
274+
276275
return true;
277276
}
278277
}

src/DCLPulseTests/DrainPeerLifeCycleEventsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public void SetUp()
3737
new IdentityBoard(100),
3838
new PeerOptions(),
3939
Substitute.For<ILogger<PeersManager>>(),
40+
Substitute.For<ILogger<PeerSimulation>>(),
4041
timeProvider,
4142
new Dictionary<ClientMessage.MessageOneofCase, IMessageHandler>(),
4243
Substitute.For<ITransport>(),

src/DCLPulseTests/PeerSimulationTests.Resync.cs

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -33,59 +33,6 @@ public void Resync_SendsFullStateWhenKnownSeqEvictedFromRing()
3333
Assert.That(msg.Message.PlayerStateFull.Sequence, Is.EqualTo(RING_CAPACITY + 5));
3434
}
3535

36-
[Test]
37-
public void Resync_SendsTargetedDeltaWhenKnownSeqStillInRing()
38-
{
39-
SetVisibleSubjects((subject, PeerViewSimulationTier.TIER_0));
40-
simulation.SimulateTick(peers, tickCounter: 0);
41-
DrainAllMessages(); // PlayerJoined
42-
43-
// Advance a few seqs — all still within ring capacity
44-
PublishSnapshot(subject, seq: 2);
45-
PublishSnapshot(subject, seq: 3, position: new Vector3(5f, 0f, 0f));
46-
47-
// Observer's known_seq is 1, which is still in the ring
48-
AddResyncRequest(observer, subject, knownSeq: 1);
49-
50-
simulation.SimulateTick(peers, tickCounter: 1);
51-
52-
OutgoingMessage msg = DrainSingleMessage();
53-
Assert.That(msg.To, Is.EqualTo(observer));
54-
Assert.That(msg.PacketMode, Is.EqualTo(ITransport.PacketMode.RELIABLE));
55-
Assert.That(msg.Message.MessageCase, Is.EqualTo(ServerMessage.MessageOneofCase.PlayerStateDelta));
56-
Assert.That(msg.Message.PlayerStateDelta.NewSeq, Is.EqualTo(3u));
57-
Assert.That(msg.Message.PlayerStateDelta.PositionXQuantized, Is.EqualTo(5f));
58-
}
59-
60-
[Test]
61-
public void Resync_TargetedDeltaDiffsFromKnownSeqNotLastSent()
62-
{
63-
SetVisibleSubjects((subject, PeerViewSimulationTier.TIER_0));
64-
simulation.SimulateTick(peers, tickCounter: 0);
65-
DrainAllMessages(); // PlayerJoined at seq 1
66-
67-
// Server sends a normal delta at seq 2 (position X=10)
68-
PublishSnapshot(subject, seq: 2, position: new Vector3(10f, 0f, 0f));
69-
simulation.SimulateTick(peers, tickCounter: 1);
70-
DrainAllMessages(); // delta seq 2, view baseline is now seq 2
71-
72-
// Subject moves again to seq 3 (position X=10 unchanged, Y=5 changed)
73-
PublishSnapshot(subject, seq: 3, position: new Vector3(10f, 5f, 0f));
74-
75-
// Client says it's stuck at seq 1 (never got seq 2)
76-
AddResyncRequest(observer, subject, knownSeq: 1);
77-
78-
simulation.SimulateTick(peers, tickCounter: 2);
79-
80-
OutgoingMessage msg = DrainSingleMessage();
81-
Assert.That(msg.Message.MessageCase, Is.EqualTo(ServerMessage.MessageOneofCase.PlayerStateDelta));
82-
Assert.That(msg.Message.PlayerStateDelta.NewSeq, Is.EqualTo(3u));
83-
84-
// Diff is from seq 1 (Zero) to seq 3 — both X and Y changed
85-
Assert.That(msg.Message.PlayerStateDelta.PositionXQuantized, Is.EqualTo(10f));
86-
Assert.That(msg.Message.PlayerStateDelta.PositionYQuantized, Is.EqualTo(5f));
87-
}
88-
8936
[Test]
9037
public void Resync_ViewBaselineAdvancesToCurrentAfterResync()
9138
{
@@ -149,29 +96,6 @@ public void Resync_LeftoversForInvisibleSubjectsClearedAfterTick()
14996
Assert.That(peers[observer].ResyncRequests, Is.Empty);
15097
}
15198

152-
[Test]
153-
public void Resync_OnlyLatestKnownSeqKeptPerSubject()
154-
{
155-
SetVisibleSubjects((subject, PeerViewSimulationTier.TIER_0));
156-
simulation.SimulateTick(peers, tickCounter: 0);
157-
DrainAllMessages();
158-
159-
PublishSnapshot(subject, seq: 2, position: new Vector3(1f, 0f, 0f));
160-
PublishSnapshot(subject, seq: 3, position: new Vector3(2f, 0f, 0f));
161-
162-
// Client sends two resyncs — only the second should matter
163-
AddResyncRequest(observer, subject, knownSeq: 1);
164-
AddResyncRequest(observer, subject, knownSeq: 2);
165-
166-
simulation.SimulateTick(peers, tickCounter: 1);
167-
168-
OutgoingMessage msg = DrainSingleMessage();
169-
Assert.That(msg.Message.MessageCase, Is.EqualTo(ServerMessage.MessageOneofCase.PlayerStateDelta));
170-
171-
// Diff is from seq 2 (X=1) to seq 3 (X=2), not from seq 1
172-
Assert.That(msg.Message.PlayerStateDelta.PositionXQuantized, Is.EqualTo(2f));
173-
}
174-
17599
[Test]
176100
public void Resync_NullResyncRequestsDoesNotCrash()
177101
{

src/DCLPulseTests/PeerSimulationTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,12 @@ public void SetUp()
7070

7171
timeProvider = Substitute.For<ITimeProvider>();
7272
timeProvider.MonotonicTime.Returns(0u);
73-
7473
profileBoard = new ProfileBoard(MAX_PEERS);
7574

7675
simulation = new PeerSimulation(
7776
areaOfInterest, snapshotBoard, spatialGrid, identityBoard, messagePipe,
7877
SimulationSteps, timeProvider, Substitute.For<ITransport>(),
79-
profileBoard);
78+
profileBoard, Substitute.For<ILogger<PeerSimulation>>());
8079

8180
peers = new Dictionary<PeerIndex, PeerState>
8281
{

src/DCLPulseTests/WorkerAsyncTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public void SetUp()
3636
new IdentityBoard(100),
3737
new PeerOptions(),
3838
Substitute.For<ILogger<PeersManager>>(),
39+
Substitute.For<ILogger<PeerSimulation>>(),
3940
timeProvider,
4041
new Dictionary<ClientMessage.MessageOneofCase, IMessageHandler>(),
4142
Substitute.For<ITransport>(),

0 commit comments

Comments
 (0)