Skip to content

Commit 2f6839f

Browse files
PredictedQueueDel fix attempt (space-wizards#6688)
1 parent c65b9db commit 2f6839f

5 files changed

Lines changed: 353 additions & 10 deletions

File tree

RELEASE-NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ END TEMPLATE-->
4343

4444
### Bugfixes
4545

46-
*None yet*
46+
* Fix PredictedQueueDel not rolling back properly on the client.
4747

4848
### Other
4949

Robust.Client/GameObjects/ClientEntityManager.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public sealed partial class ClientEntityManager : EntityManager, IClientEntityMa
3131

3232
private readonly Queue<EntityUid> _queuedPredictedDeletions = new();
3333
private readonly HashSet<EntityUid> _queuedPredictedDeletionsSet = new();
34+
private readonly HashSet<EntityUid> _predictedDetachedEntities = new();
3435
private Histogram? _tickUpdateHistogram;
3536
private Histogram.Child? _entityNetHistogram;
3637

@@ -61,6 +62,9 @@ public override void FlushEntities()
6162
// Server doesn't network deletions on client shutdown so we need to
6263
// manually clear these out or risk stale data getting used.
6364
PendingNetEntityStates.Clear();
65+
_queuedPredictedDeletions.Clear();
66+
_queuedPredictedDeletionsSet.Clear();
67+
_predictedDetachedEntities.Clear();
6468
using var _ = _gameTiming.StartStateApplicationArea();
6569
base.FlushEntities();
6670
}
@@ -97,6 +101,14 @@ public override void QueueDeleteEntity(EntityUid? uid)
97101
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}");
98102
}
99103

104+
public override void DeleteEntity(EntityUid? uid)
105+
{
106+
if (uid != null)
107+
ClearPredictedDeletion(uid.Value);
108+
109+
base.DeleteEntity(uid);
110+
}
111+
100112
/// <inheritdoc />
101113
public override void Dirty(EntityUid uid, IComponent component, MetaDataComponent? meta = null)
102114
{
@@ -237,6 +249,9 @@ internal override void ProcessQueueudDeletions()
237249
base.ProcessQueueudDeletions();
238250
while (_queuedPredictedDeletions.TryDequeue(out var uid))
239251
{
252+
if (!_queuedPredictedDeletionsSet.Remove(uid))
253+
continue;
254+
240255
if (!MetaQuery.TryGetComponentInternal(uid, out var meta))
241256
continue;
242257

@@ -246,19 +261,14 @@ internal override void ProcessQueueudDeletions()
246261
var xform = TransformQuery.GetComponentInternal(uid);
247262
if (meta.NetEntity.IsClientSide())
248263
{
264+
ClearPredictedDeletion(uid);
249265
DeleteEntity(uid, meta, xform);
250266
}
251267
else
252268
{
253-
_xforms.DetachEntity(uid, xform, meta, null);
254-
// base call bypasses IGameTiming.InPrediction check
255-
// This is pretty janky and there should be a way for the client to dirty an entity outside of prediction
256-
// TODO PREDICTION Is actually needed after the current predicted deletion fix?
257-
base.Dirty(uid, xform, meta);
269+
PredictedDetachNetworkedEntity(uid, xform, meta);
258270
}
259271
}
260-
261-
_queuedPredictedDeletionsSet.Clear();
262272
}
263273

264274
/// <inheritdoc />
@@ -354,16 +364,42 @@ public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformC
354364

355365
if (ent.Comp1.NetEntity.IsClientSide())
356366
{
367+
ClearPredictedDeletion(ent.Owner);
357368
DeleteEntity(ent, ent.Comp1, ent.Comp2);
358369
}
359370
else
360371
{
361-
_xforms.DetachEntity(ent, ent.Comp2);
372+
PredictedDetachNetworkedEntity(ent.Owner, ent.Comp2, ent.Comp1);
362373
}
363374
}
364375

376+
internal bool IsPredictedDetached(EntityUid uid)
377+
{
378+
return _predictedDetachedEntities.Contains(uid);
379+
}
380+
381+
internal void ClearPredictedDeletion(EntityUid uid)
382+
{
383+
_predictedDetachedEntities.Remove(uid);
384+
_queuedPredictedDeletionsSet.Remove(uid);
385+
}
386+
387+
private void PredictedDetachNetworkedEntity(EntityUid uid, TransformComponent xform, MetaDataComponent meta)
388+
{
389+
if (!_predictedDetachedEntities.Add(uid))
390+
return;
391+
392+
// base call bypasses IGameTiming.InPrediction check. Predicted queue deletes are processed after prediction,
393+
// but reset still needs to see the detached entity as dirty and restore it from the last server state.
394+
base.Dirty(uid, xform, meta);
395+
_xforms.DetachEntity(uid, xform, meta, null);
396+
meta.Flags |= MetaDataFlags.Detached;
397+
}
398+
365399
public override bool IsQueuedForDeletion(EntityUid uid)
366-
=> QueuedDeletionsSet.Contains(uid) || _queuedPredictedDeletions.Contains(uid);
400+
=> QueuedDeletionsSet.Contains(uid)
401+
|| _queuedPredictedDeletionsSet.Contains(uid)
402+
|| _predictedDetachedEntities.Contains(uid);
367403

368404
/// <inheritdoc />
369405
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?> ent)

Robust.Client/GameStates/ClientGameStateManager.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,15 +602,20 @@ public void ResetPredictedEntities()
602602
!_processor.TryGetLastServerStates(meta.NetEntity, out var last))
603603
{
604604
// Entity was probably deleted on the server so do nothing.
605+
_entities.ClearPredictedDeletion(entity);
605606
continue;
606607
}
607608

608609
countReset += 1;
610+
var predictedDetached = _entities.IsPredictedDetached(entity);
609611

610612
try
611613
{
612614
_resettingPredictedEntities = true;
613615

616+
if (predictedDetached)
617+
meta.Flags &= ~MetaDataFlags.Detached;
618+
614619
foreach (var (netId, comp) in meta.NetComponents)
615620
{
616621
if (!comp.NetSyncEnabled)
@@ -634,6 +639,7 @@ public void ResetPredictedEntities()
634639
}
635640
}
636641

642+
// If the component wasn't modified, or if we have no state to roll back to.
637643
if (comp.LastModifiedTick <= _timing.LastRealTick ||
638644
!last.TryGetValue(netId, out var compState))
639645
{
@@ -693,6 +699,7 @@ public void ResetPredictedEntities()
693699

694700
DebugTools.Assert(meta.EntityLastModifiedTick > _timing.LastRealTick);
695701
meta.EntityLastModifiedTick = _timing.LastRealTick;
702+
_entities.ClearPredictedDeletion(entity);
696703
}
697704

698705
_entities.System<PhysicsSystem>().ResetContacts();

Robust.Client/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[assembly: InternalsVisibleTo("Robust.Benchmarks")]
88
[assembly: InternalsVisibleTo("Robust.Client.Tests")]
99
[assembly: InternalsVisibleTo("Robust.Client.IntegrationTests")]
10+
[assembly: InternalsVisibleTo("Robust.Shared.IntegrationTests")]
1011

1112
#if NET5_0_OR_GREATER
1213
[module: SkipLocalsInit]

0 commit comments

Comments
 (0)