Fix client-side replay exception (#5068)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Leon Friedrich
2024-04-27 14:30:00 +12:00
committed by GitHub
parent 16bab1bc03
commit 2a102f048f
4 changed files with 30 additions and 16 deletions

View File

@@ -88,7 +88,6 @@ public sealed partial class ReplayLoadManager
if (initMessages != null)
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
var entSpan = state0.EntityStates.Value;
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
@@ -98,6 +97,8 @@ public sealed partial class ReplayLoadManager
entStates.Add(entState.NetEntity, modifiedState);
}
ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates);
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
var playerSpan = state0.PlayerStates.Value;
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
@@ -144,7 +145,7 @@ public sealed partial class ReplayLoadManager
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
ProcessQueue(curState.ToSequence, detachQueue, detached);
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
UpdateDeletions(curState.EntityDeletions, entStates, detached);
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
@@ -176,14 +177,28 @@ public sealed partial class ReplayLoadManager
private void ProcessQueue(
GameTick curTick,
Dictionary<GameTick, List<NetEntity>> detachQueue,
HashSet<NetEntity> detached)
HashSet<NetEntity> detached,
Dictionary<NetEntity, EntityState> entStates)
{
foreach (var (tick, ents) in detachQueue)
{
if (tick > curTick)
continue;
detachQueue.Remove(tick);
detached.UnionWith(ents);
foreach (var e in ents)
{
if (entStates.ContainsKey(e))
detached.Add(e);
else
{
// AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss
// I.e., entity was created on tick n, then leaves PVS range on the tick n+1
// If the n-th tick gets dropped, the client only ever receives the pvs-leave message.
// In that case we should just ignore it.
_sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?");
}
}
}
}

View File

@@ -79,13 +79,14 @@ internal sealed partial class ReplayPlaybackManager
if (checkpoint.DetachedStates == null)
return;
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ;
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length);
foreach (var es in checkpoint.DetachedStates)
{
var uid = _entMan.GetEntity(es.NetEntity);
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta))
{
DebugTools.Assert(!meta.EntityDeleted);
continue;
}
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
@@ -93,18 +94,16 @@ internal sealed partial class ReplayPlaybackManager
if (metaState == null)
throw new MissingMetadataException(es.NetEntity);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
meta = metas.GetComponent(uid);
uid = _entMan.CreateEntity(metaState.PrototypeId, out meta);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(meta.NetEntity);
_entMan.SetNetEntity(uid.Value, es.NetEntity, meta);
_entMan.SetNetEntity(uid, es.NetEntity, meta);
_entMan.InitializeEntity(uid, meta);
_entMan.StartEntity(uid);
_entMan.InitializeEntity(uid.Value, meta);
_entMan.StartEntity(uid.Value);
meta.LastStateApplied = checkpoint.Tick;
}
}

View File

@@ -86,7 +86,7 @@ namespace Robust.Server.GameObjects
StartEntity(entity);
}
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return base.CreateEntity(prototypeName, out metadata, context);

View File

@@ -776,7 +776,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
internal virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return AllocEntity(out metadata);