Skip to content

Commit

Permalink
Fix replay playback bugs (#5604)
Browse files Browse the repository at this point in the history
* Sort replay entities

* Fix nameof(TState)

* I forgot to generate implicit states

* release notes
  • Loading branch information
ElectroJr authored Jan 16, 2025
1 parent a314c5f commit 6a3f88b
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 14 deletions.
2 changes: 1 addition & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ END TEMPLATE-->

### Bugfixes

*None yet*
* Fixed a state handling bug in replays, which was causing exceptions to be thrown when applying delta states.

### Other

Expand Down
4 changes: 2 additions & 2 deletions Robust.Client/GameStates/ClientGameStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ public void ApplyGameState()

using (_prof.Group("MergeImplicitData"))
{
MergeImplicitData(createdEntities);
GenerateImplicitStates(createdEntities);
}

if (_lastProcessedInput < curState.LastProcessedInput)
Expand Down Expand Up @@ -671,7 +671,7 @@ public void ResetPredictedEntities()
/// initial server state for any newly created entity. It does this by simply using the standard <see
/// cref="IEntityManager.GetComponentState"/>.
/// </remarks>
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
public void GenerateImplicitStates(IEnumerable<NetEntity> createdEntities)
{
var bus = _entityManager.EventBus;

Expand Down
6 changes: 6 additions & 0 deletions Robust.Client/GameStates/IClientGameStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public interface IClientGameStateManager
/// </summary>
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);

/// <summary>
/// Generates implicit component states for newly created entities.
/// This should always be called after running <see cref="ApplyGameState(GameState, GameState)"/>.
/// </summary>
void GenerateImplicitStates(IEnumerable<NetEntity> states);

/// <summary>
/// Resets any entities that have changed while predicting future ticks.
/// </summary>
Expand Down
28 changes: 26 additions & 2 deletions Robust.Client/Replays/Loading/ReplayLoadManager.Start.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,21 @@ public async Task StartReplayAsync(ReplayData data, LoadReplayCallback callback)
_gameState.ClearDetachQueue();
_gameState.ApplyGameState(checkpoint.State, next);

// Sort entities to ensure that we initialize parents before children
var sorted = new List<EntityUid>(entities.Count);
var added = new HashSet<EntityUid>(entities.Count);
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
foreach (var uid in entities)
{
AddSorted(uid, sorted, added, xformQuery);
}
DebugTools.AssertEqual(sorted.Count, entities.Count);
DebugTools.AssertEqual(added.Count, entities.Count);
await callback(i, total, LoadingState.Initializing, false);

i = 0;
var query = _entMan.GetEntityQuery<MetaDataComponent>();
foreach (var uid in entities)
foreach (var uid in sorted)
{
_entMan.InitializeEntity(uid, query.GetComponent(uid));
if (i++ % 50 == 0)
Expand All @@ -109,7 +121,7 @@ public async Task StartReplayAsync(ReplayData data, LoadReplayCallback callback)

i = 0;
await callback(0, total, LoadingState.Starting, true);
foreach (var uid in entities)
foreach (var uid in sorted)
{
_entMan.StartEntity(uid);
if (i++ % 50 == 0)
Expand All @@ -132,4 +144,16 @@ public async Task StartReplayAsync(ReplayData data, LoadReplayCallback callback)
_replayPlayback.StartReplay(data);
_timing.Paused = false;
}

private void AddSorted(EntityUid uid, List<EntityUid> sortedList, HashSet<EntityUid> added, EntityQuery<TransformComponent> query)
{
if (!added.Add(uid))
return;

var parent = query.Comp(uid).ParentUid;
if (parent != EntityUid.Invalid)
AddSorted(parent, sortedList, added, query);

sortedList.Add(uid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ private void TickUpdateOverride(FrameEventArgs args)
_gameState.UpdateFullRep(state, cloneDelta: true);
var next = Replay.NextState;
BeforeApplyState?.Invoke((state, next));
_gameState.ApplyGameState(state, next);
var created = _gameState.ApplyGameState(state, next);
_gameState.GenerateImplicitStates(created);
DebugTools.Assert(Replay.LastApplied >= state.FromSequence);
DebugTools.Assert(Replay.LastApplied + 1 <= state.ToSequence);
Replay.LastApplied = state.ToSequence;
Expand Down
16 changes: 8 additions & 8 deletions Robust.Shared/GameObjects/ComponentState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ public interface IComponentDeltaState<TState> : IComponentDeltaState where TStat

void IComponentDeltaState.ApplyToFullState(IComponentState fullState)
{
if (fullState is TState state)
ApplyToFullState(state);
else
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
if (fullState is not TState state)
throw new Exception($"Unexpected type. Expected {typeof(TState).Name} but got {fullState.GetType().Name}");

ApplyToFullState(state);
}

IComponentState IComponentDeltaState.CreateNewFullState(IComponentState fullState)
{
if (fullState is TState state)
return CreateNewFullState(state);
else
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
if (fullState is not TState state)
throw new Exception($"Unexpected type. Expected {typeof(TState).Name} but got {fullState.GetType().Name}");

return CreateNewFullState(state);
}
}

0 comments on commit 6a3f88b

Please sign in to comment.