Public state reset function. (#3539)

This commit is contained in:
Leon Friedrich
2022-11-28 14:12:07 +13:00
committed by GitHub
parent f856ac0efa
commit e6f2d36d50
2 changed files with 60 additions and 17 deletions

View File

@@ -265,6 +265,8 @@ namespace Robust.Client.GameStates
{
_processor.LastFullStateRequested = null;
_timing.LastProcessedTick = curState.ToSequence;
DebugTools.Assert(curState.FromSequence == GameTick.Zero);
PartialStateReset(curState, true, true);
}
else
_timing.LastProcessedTick += 1;
@@ -632,9 +634,6 @@ namespace Robust.Client.GameStates
var toCreate = new Dictionary<EntityUid, EntityState>();
var enteringPvs = 0;
if (curState.FromSequence == GameTick.Zero)
FullReset(curState, metas, xforms, xformSys);
var curSpan = curState.EntityStates.Span;
foreach (var es in curSpan)
{
@@ -776,39 +775,71 @@ namespace Robust.Client.GameStates
return (toCreate.Keys, detached);
}
private void FullReset(GameState curState, EntityQuery<MetaDataComponent> metas, EntityQuery<TransformComponent> xforms, SharedTransformSystem xformSys)
/// <inheritdoc />
public void PartialStateReset(GameState state, bool resetAllEnts, bool deleteClientSideEnts)
{
// This is somewhat of a hack to do a proper reset for exception tolerance. I need to do this properly at
// some point. This is basically a hotfix to hide the side effects of #3413. Basically, this gets run if the
// client encounters an error while applying game states and requests a full server state.
using var _ = _timing.StartStateApplicationArea();
_sawmill.Info("Performing full entity reset.");
if (state.FromSequence != GameTick.Zero)
{
_sawmill.Error("Attempted to reset to a state with incomplete data");
return;
}
// TODO properly reset maps
// TODO properly reset player-states (e.g., attached enttiy and current eye).
_sawmill.Info($"Resetting all entity states to tick {state.ToSequence}.");
// Linq bad, but this should be rare (when first connecting, and when encountering PVS errors, which ideally would just never happen).
var ents = curState.EntityStates.Value?.Select(x => x.Uid)?.ToHashSet() ?? new HashSet<EntityUid>();
foreach (var ent in _entities.GetEntities().ToArray())
// Construct hashset for set.Contains() checks.
var entityStates = state.EntityStates.Span;
var stateEnts = new HashSet<EntityUid>(entityStates.Length);
foreach (var entState in entityStates)
{
stateEnts.Add(entState.Uid);
}
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
var currentEnts = _entities.GetEntities();
var toDelete = new List<EntityUid>(Math.Max(64, currentEnts.Count() - stateEnts.Count()));
foreach (var ent in currentEnts)
{
if (ent.IsClientSide())
continue;
if (ents.Contains(ent) && metas.TryGetComponent(ent, out var meta))
{
meta.LastStateApplied = GameTick.Zero;
if (deleteClientSideEnts)
toDelete.Add(ent);
continue;
}
if (stateEnts.Contains(ent) && metas.TryGetComponent(ent, out var meta))
{
if (resetAllEnts || meta.LastStateApplied > state.ToSequence)
meta.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
continue;
}
if (!xforms.TryGetComponent(ent, out var xform))
continue;
// this entity is going to get deleted, but maybe some if its children won't be, so lets detach them to null.
xformSys.DetachParentToNull(xform, xforms, metas);
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
xformSys.DetachParentToNull(xforms.GetComponent(child.Value), xforms, metas, xform);
if (!deleteClientSideEnts && child.Value.IsClientSide())
{
// Even though we aren't meant to be deleting client-side entities here, this client-side entity is getting detached to null space, so we will delete it anyways).
_sawmill.Warning($"Deleting client-side entity ({_entities.ToPrettyString(child.Value)}) as it was parented to a server side entity that is getting deleted ({_entities.ToPrettyString(ent)})");
toDelete.Add(child.Value);
}
}
toDelete.Add(ent);
}
foreach (var ent in toDelete)
{
_entities.DeleteEntity(ent);
}
}

View File

@@ -96,5 +96,17 @@ namespace Robust.Client.GameStates
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
void UpdateFullRep(GameState state);
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state by deleting entities and
/// marking ensuring component states will get applied properly. <see cref="ApplyGameState(GameState,
/// GameState?)"/> Still needs to be called separately after this is run.
/// </summary>
/// <param name="state">The state to reset to.</param>
/// <param name="resetAllEnts">Whether to apply component states to all entities, or only those that have been
/// modified some time after the state's to-sequence</param>
/// <param name="deleteClientSideEnts">Whether to delete all client-side entities (which are never part of the
/// networked game state).</param>
void PartialStateReset(GameState state, bool resetAllEnts, bool deleteClientSideEnts);
}
}