Skip to content

Commit

Permalink
Allow replay loading to ignore some errors (#4236)
Browse files Browse the repository at this point in the history
  • Loading branch information
ElectroJr authored Aug 6, 2023
1 parent e02166d commit aade062
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 8 deletions.
21 changes: 18 additions & 3 deletions Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,16 @@ TimeSpan GetTime(GameTick tick)
var ticksSinceLastCheckpoint = 0;
var spawnedTracker = 0;
var stateTracker = 0;
var curState = state0;
for (var i = 1; i < states.Count; i++)
{
if (i % 10 == 0)
await callback(i, states.Count, LoadingState.ProcessingFiles, false);

var curState = states[i];
var lastState = curState;
curState = states[i];
DebugTools.Assert(curState.FromSequence <= lastState.ToSequence);

UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
Expand Down Expand Up @@ -222,7 +226,13 @@ private void UpdateMessages(ReplayMessage message,
// forwards. Also, note that files HAVE to be uploaded while generating checkpoints, in case
// someone spawns an entity that relies on uploaded data.
if (!ignoreDuplicates)
throw new NotSupportedException("Overwriting an existing file is not yet supported by replays.");
{
var msg = $"Overwriting an existing file upload! Path: {path}";
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
_sawmill.Error(msg);
else
throw new NotSupportedException(msg);
}

message.Messages.RemoveSwap(i);
break;
Expand Down Expand Up @@ -255,7 +265,12 @@ private void UpdateMessages(ReplayMessage message,
// prototype changes when jumping around in time. This also requires reworking how the initial
// implicit state data is generated, because we can't simply cache it anymore.
// Also, does reloading prototypes in release mode modify existing entities?
throw new NotSupportedException($"Overwriting an existing prototype is not yet supported by replays.");

var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
_sawmill.Error(msg);
else
throw new NotSupportedException(msg);
}
}

Expand Down
16 changes: 15 additions & 1 deletion Robust.Client/Replays/Loading/ReplayLoadManager.Implicit.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Client.GameStates;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
Expand Down Expand Up @@ -64,6 +66,18 @@ private EntityState AddImplicitData(EntityState entState)
}
}

throw new Exception("Missing metadata component");
if (!entState.ComponentChanges.HasContents)
{
// This shouldn't be possible, yet it has happened?
// TODO this should probably also throw an exception.
_sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
return null;
}

if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new MissingMetadataException(entState.Uid);

_sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}.");
return null;
}
}
16 changes: 14 additions & 2 deletions Robust.Client/Replays/Loading/ReplayLoadManager.Read.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Robust.Shared;
using Robust.Shared.Replays;
using static Robust.Shared.Replays.ReplayConstants;

Expand Down Expand Up @@ -136,17 +137,28 @@ public async Task<ReplayData> LoadReplayAsync(IReplayFileReader fileReader, Load
if (data == null)
throw new Exception("Failed to load yaml metadata");

TimeSpan duration;
var finalData = LoadYamlFinalMetadata(fileReader);
if (finalData == null)
throw new Exception("Failed to load final yaml metadata");
{
var msg = "Failed to load final yaml metadata";
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new Exception(msg);

_sawmill.Error(msg);
duration = TimeSpan.FromDays(1);
}
else
{
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
}

var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
var startTick = ((ValueDataNode) data[MetaKeyStartTick]).Value;
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
var timeBaseTimespan = ((ValueDataNode) data[MetaKeyBaseTime]).Value;
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
var duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);

if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
Expand Down
14 changes: 12 additions & 2 deletions Robust.Shared/CVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1509,15 +1509,25 @@ protected CVars()
public static readonly CVarDef<bool> ReplayDynamicalScrubbing = CVarDef.Create("replay.dynamical_scrubbing", true);

/// <summary>
/// When recording replays,
/// should we attempt to make a valid content bundle that can be directly executed by the launcher?
/// When recording replays, should we attempt to make a valid content bundle that can be directly executed by
/// the launcher?
/// </summary>
/// <remarks>
/// This requires the server's build information to be sufficiently filled out.
/// </remarks>
public static readonly CVarDef<bool> ReplayMakeContentBundle =
CVarDef.Create("replay.make_content_bundle", true);

/// <summary>
/// If true, this will cause the replay client to ignore some errors while loading a replay file.
/// </summary>
/// <remarks>
/// This might make otherwise broken replays playable, but ignoring these errors is also very likely to
/// cause unexpected and confusing errors elsewhere. By default this is disabled so that users report the
/// original exception rather than sending people on a wild-goose chase to find a non-existent bug.
/// </remarks>
public static readonly CVarDef<bool> ReplayIgnoreErrors =
CVarDef.Create("replay.ignore_errors", false, CVar.CLIENTONLY | CVar.ARCHIVE);
/*
* CFG
*/
Expand Down

0 comments on commit aade062

Please sign in to comment.