Make disabled prediction work again.

Simulation input and Update() does not happen when prediction is disabled. Both of these can be re-opted in on a per-handler/system basis with a bool flag. Stuff like physics opts out of this now.
This commit is contained in:
Pieter-Jan Briers
2021-12-30 03:03:39 +01:00
parent 720f33a12a
commit 069ebbc8d0
23 changed files with 104 additions and 34 deletions

View File

@@ -438,7 +438,7 @@ namespace Robust.Client
// In singleplayer, however, we're in full control instead.
else if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
{
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds);
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
_lookup.Update();
}

View File

@@ -67,7 +67,7 @@ namespace Robust.Client.GameObjects
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
}
public override void TickUpdate(float frameTime, Histogram? histogram)
public override void TickUpdate(float frameTime, bool noPredictions, Histogram? histogram)
{
using (histogram?.WithLabels("EntityNet").NewTimer())
{
@@ -79,7 +79,7 @@ namespace Robust.Client.GameObjects
}
}
base.TickUpdate(frameTime, histogram);
base.TickUpdate(frameTime, noPredictions, histogram);
}
/// <inheritdoc />

View File

@@ -2,6 +2,8 @@ using System;
using Robust.Client.GameStates;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
@@ -62,6 +64,9 @@ namespace Robust.Client.GameObjects
// handle local binds before sending off
foreach (var handler in BindRegistry.GetHandlers(function))
{
if (!_stateManager.IsPredictionEnabled && !handler.FireOutsidePrediction)
continue;
// local handlers can block sending over the network.
if (handler.HandleCmdMessage(session, message))
{

View File

@@ -69,7 +69,7 @@ namespace Robust.Client.GameStates
/// <inheritdoc />
public int CurrentBufferSize => _processor.CalculateBufferSize(CurServerTick);
public bool Predicting { get; private set; }
public bool IsPredictionEnabled { get; private set; }
public int PredictTickBias { get; private set; }
public float PredictLagBias { get; private set; }
@@ -96,7 +96,7 @@ namespace Robust.Client.GameStates
_config.OnValueChanged(CVars.NetInterp, b => _processor.Interpolation = b, true);
_config.OnValueChanged(CVars.NetInterpRatio, i => _processor.InterpRatio = i, true);
_config.OnValueChanged(CVars.NetLogging, b => _processor.Logging = b, true);
_config.OnValueChanged(CVars.NetPredict, b => Predicting = b, true);
_config.OnValueChanged(CVars.NetPredict, b => IsPredictionEnabled = b, true);
_config.OnValueChanged(CVars.NetPredictTickBias, i => PredictTickBias = i, true);
_config.OnValueChanged(CVars.NetPredictLagBias, i => PredictLagBias = i, true);
_config.OnValueChanged(CVars.NetStateBufMergeThreshold, i => StateBufferMergeThreshold = i, true);
@@ -104,7 +104,7 @@ namespace Robust.Client.GameStates
_processor.Interpolation = _config.GetCVar(CVars.NetInterp);
_processor.InterpRatio = _config.GetCVar(CVars.NetInterpRatio);
_processor.Logging = _config.GetCVar(CVars.NetLogging);
Predicting = _config.GetCVar(CVars.NetPredict);
IsPredictionEnabled = _config.GetCVar(CVars.NetPredict);
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
@@ -135,7 +135,7 @@ namespace Robust.Client.GameStates
public void InputCommandDispatched(FullInputCmdMessage message)
{
if (!Predicting)
if (!IsPredictionEnabled)
{
return;
}
@@ -151,7 +151,7 @@ namespace Robust.Client.GameStates
public uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs
{
if (!Predicting)
if (!IsPredictionEnabled)
{
return default;
}
@@ -214,7 +214,7 @@ namespace Robust.Client.GameStates
// TODO: If Predicting gets disabled *while* the world state is dirty from a prediction,
// this won't run meaning it could potentially get stuck dirty.
if (Predicting && i == 0)
if (IsPredictionEnabled && i == 0)
{
// Disable IsFirstTimePredicted while re-running HandleComponentState here.
// Helps with debugging.
@@ -268,7 +268,7 @@ namespace Robust.Client.GameStates
DebugTools.Assert(_timing.InSimulation);
if (Predicting)
if (IsPredictionEnabled)
{
using var _ = _timing.StartPastPredictionArea();
@@ -324,13 +324,13 @@ namespace Robust.Client.GameStates
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: false);
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
}
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
_lookup.Update();
}

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Timing;
@@ -44,6 +45,13 @@ namespace Robust.Client.GameStates
/// </summary>
int StateBufferMergeThreshold { get; }
/// <summary>
/// Whether prediction is currently enabled on the client entirely.
/// This is NOT equal to <see cref="IGameTiming.InPrediction"/> or <see cref="IGameTiming.IsFirstTimePredicted"/>.
/// </summary>
/// <remarks>This is effectively an alias of <see cref="CVars.NetPredict"/>.</remarks>
bool IsPredictionEnabled { get; }
/// <summary>
/// This is called after the game state has been applied for the current tick.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using JetBrains.Annotations;
using Robust.Client.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -11,6 +12,7 @@ namespace Robust.Client.Physics
public class PhysicsSystem : SharedPhysicsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientGameStateManager _gameState = default!;
private TimeSpan _lastRem;
@@ -22,6 +24,9 @@ namespace Robust.Client.Physics
public override void FrameUpdate(float frameTime)
{
if (!_gameState.IsPredictionEnabled)
return;
if (_lastRem > _gameTiming.TickRemainder)
{
_lastRem = TimeSpan.Zero;

View File

@@ -615,7 +615,7 @@ namespace Robust.Server
}
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, TickUsage);
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false, TickUsage);
_lookup.Update();

View File

@@ -128,7 +128,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public override void TickUpdate(float frameTime, Histogram? histogram)
public override void TickUpdate(float frameTime, bool noPredictions, Histogram? histogram)
{
using (histogram?.WithLabels("EntityNet").NewTimer())
{
@@ -138,7 +138,7 @@ namespace Robust.Server.GameObjects
}
}
base.TickUpdate(frameTime, histogram);
base.TickUpdate(frameTime, noPredictions, histogram);
EntitiesCount.Set(Entities.Count);
}

View File

@@ -61,6 +61,12 @@ namespace Robust.Shared
public static readonly CVarDef<bool> NetLogging =
CVarDef.Create("net.logging", false, CVar.ARCHIVE);
/// <summary>
/// Whether prediction is enabled on the client.
/// </summary>
/// <remarks>
/// If off, simulation input commands will not fire and most entity methods will not run update.
/// </remarks>
public static readonly CVarDef<bool> NetPredict =
CVarDef.Create("net.predict", true, CVar.CLIENTONLY);

View File

@@ -109,11 +109,11 @@ namespace Robust.Shared.GameObjects
Started = false;
}
public virtual void TickUpdate(float frameTime, Histogram? histogram)
public virtual void TickUpdate(float frameTime, bool noPredictions, Histogram? histogram)
{
using (histogram?.WithLabels("EntitySystems").NewTimer())
{
EntitySystemManager.TickUpdate(frameTime);
EntitySystemManager.TickUpdate(frameTime, noPredictions);
}
using (histogram?.WithLabels("EntityEventBus").NewTimer())

View File

@@ -25,6 +25,9 @@ namespace Robust.Shared.GameObjects
protected internal List<Type> UpdatesAfter { get; } = new();
protected internal List<Type> UpdatesBefore { get; } = new();
public bool UpdatesOutsidePrediction { get; protected internal set; }
IEnumerable<Type> IEntitySystem.UpdatesAfter => UpdatesAfter;
IEnumerable<Type> IEntitySystem.UpdatesBefore => UpdatesBefore;
@@ -40,6 +43,10 @@ namespace Robust.Shared.GameObjects
public virtual void Initialize() { }
/// <inheritdoc />
/// <remarks>
/// Not ran on the client if prediction is disabled and
/// <see cref="UpdatesOutsidePrediction"/> is false (the default).
/// </remarks>
public virtual void Update(float frameTime) { }
/// <inheritdoc />

View File

@@ -259,10 +259,13 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public void TickUpdate(float frameTime)
public void TickUpdate(float frameTime, bool noPredictions)
{
foreach (var updReg in _updateOrder)
{
if (noPredictions && !updReg.System.UpdatesOutsidePrediction)
continue;
if (MetricsEnabled)
{
_stopwatch.Restart();

View File

@@ -27,7 +27,11 @@ namespace Robust.Shared.GameObjects
/// Drops every entity, component and entity system.
/// </summary>
void Cleanup();
void TickUpdate(float frameTime, Histogram? histogram=null);
/// <param name="noPredictions">
/// Only run systems with <see cref="EntitySystem.UpdatesOutsidePrediction"/> set true.
/// </param>
void TickUpdate(float frameTime, bool noPredictions, Histogram? histogram=null);
/// <summary>
/// Client-specific per-render frame updating.

View File

@@ -16,6 +16,11 @@ namespace Robust.Shared.GameObjects
IEnumerable<Type> UpdatesAfter { get; }
IEnumerable<Type> UpdatesBefore { get; }
/// <summary>
/// If prediction is disabled on the client, <see cref="Update"/> will not be ran unless this flag is set.
/// </summary>
bool UpdatesOutsidePrediction { get; }
/// <summary>
/// Called once when the system is created to initialize its state.
/// </summary>

View File

@@ -104,8 +104,11 @@ namespace Robust.Shared.GameObjects
/// Update all systems.
/// </summary>
/// <param name="frameTime">Time since the last frame was rendered.</param>
/// <param name="noPredictions">
/// Only run systems with <see cref="EntitySystem.UpdatesOutsidePrediction"/> set true.
/// </param>
/// <seealso cref="IEntitySystem.Update(float)"/>
void TickUpdate(float frameTime);
void TickUpdate(float frameTime, bool noPredictions);
void FrameUpdate(float frameTime);
/// <summary>

View File

@@ -20,6 +20,8 @@ namespace Robust.Shared.GameObjects
{
base.Initialize();
UpdatesOutsidePrediction = true;
_mapManager.MapCreated += OnMapCreated;
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInit);

View File

@@ -25,6 +25,8 @@ namespace Robust.Shared.GameObjects
{
base.Update(frameTime);
UpdatesOutsidePrediction = true;
// Need to queue because otherwise calling HandleMove during FrameUpdate will lead to prediction issues.
// TODO: Need to check if that's even still relevant since transform lerping fix?
ProcessChanges();

View File

@@ -18,6 +18,9 @@ namespace Robust.Shared.GameObjects
public override void Initialize()
{
base.Initialize();
UpdatesOutsidePrediction = true;
_mapManager.TileChanged += MapManagerOnTileChanged;
SubscribeLocalEvent<TransformComponent, EntityDirtyEvent>(OnTransformDirty);
}

View File

@@ -8,6 +8,8 @@ namespace Robust.Shared.Input.Binding
public abstract class InputCmdHandler
{
public virtual bool FireOutsidePrediction => false;
public virtual void Enabled(ICommonSession? session)
{
}
@@ -25,13 +27,14 @@ namespace Robust.Shared.Input.Binding
/// <param name="disabled">The delegate to be ran when this command is disabled.</param>
/// <returns>The new input command.</returns>
public static InputCmdHandler FromDelegate(StateInputCmdDelegate? enabled = null,
StateInputCmdDelegate? disabled = null, bool handle=true)
StateInputCmdDelegate? disabled = null, bool handle=true, bool outsidePrediction=false)
{
return new StateInputCmdHandler
{
EnabledDelegate = enabled,
DisabledDelegate = disabled,
Handle = handle,
OutsidePrediction = outsidePrediction
};
}
@@ -40,6 +43,8 @@ namespace Robust.Shared.Input.Binding
public StateInputCmdDelegate? EnabledDelegate;
public StateInputCmdDelegate? DisabledDelegate;
public bool Handle { get; set; }
public bool OutsidePrediction;
public override bool FireOutsidePrediction => OutsidePrediction;
public override void Enabled(ICommonSession? session)
{
@@ -81,15 +86,7 @@ namespace Robust.Shared.Input.Binding
private PointerInputCmdDelegate2 _callback;
private bool _ignoreUp;
/// <summary>
/// Handler which will handle the command using the indicated callback
/// </summary>
/// <param name="callback">callback to handle the command</param>
/// <param name="ignoreUp">whether keyup actions will be ignored by this handler (like lifting a key or releasing
/// mouse button)</param>
public PointerInputCmdHandler(PointerInputCmdDelegate callback, bool ignoreUp = true)
: this((in PointerInputCmdArgs args) =>
callback(args.Session, args.Coordinates, args.EntityUid), ignoreUp) { }
public override bool FireOutsidePrediction { get; }
/// <summary>
/// Handler which will handle the command using the indicated callback
@@ -97,11 +94,21 @@ namespace Robust.Shared.Input.Binding
/// <param name="callback">callback to handle the command</param>
/// <param name="ignoreUp">whether keyup actions will be ignored by this handler (like lifting a key or releasing
/// mouse button)</param>
public PointerInputCmdHandler(PointerInputCmdDelegate2 callback, bool ignoreUp = true)
public PointerInputCmdHandler(PointerInputCmdDelegate callback, bool ignoreUp = true, bool outsidePrediction = false)
: this((in PointerInputCmdArgs args) =>
callback(args.Session, args.Coordinates, args.EntityUid), ignoreUp, outsidePrediction) { }
/// <summary>
/// Handler which will handle the command using the indicated callback
/// </summary>
/// <param name="callback">callback to handle the command</param>
/// <param name="ignoreUp">whether keyup actions will be ignored by this handler (like lifting a key or releasing
/// mouse button)</param>
public PointerInputCmdHandler(PointerInputCmdDelegate2 callback, bool ignoreUp = true, bool outsidePrediction = false)
{
_callback = callback;
_ignoreUp = ignoreUp;
FireOutsidePrediction = outsidePrediction;
}
public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message)
@@ -141,11 +148,13 @@ namespace Robust.Shared.Input.Binding
{
private PointerInputCmdDelegate _enabled;
private PointerInputCmdDelegate _disabled;
public override bool FireOutsidePrediction { get; }
public PointerStateInputCmdHandler(PointerInputCmdDelegate enabled, PointerInputCmdDelegate disabled)
public PointerStateInputCmdHandler(PointerInputCmdDelegate enabled, PointerInputCmdDelegate disabled, bool outsidePrediction = false)
{
_enabled = enabled;
_disabled = disabled;
FireOutsidePrediction = outsidePrediction;
}
/// <inheritdoc />

View File

@@ -57,6 +57,9 @@ namespace Robust.Shared.Physics
public override void Initialize()
{
base.Initialize();
UpdatesOutsidePrediction = true;
UpdatesAfter.Add(typeof(SharedTransformSystem));
SubscribeLocalEvent<BroadphaseComponent, ComponentAdd>(OnBroadphaseAdd);

View File

@@ -51,6 +51,9 @@ namespace Robust.Shared.Physics
public override void Initialize()
{
base.Initialize();
UpdatesOutsidePrediction = true;
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
SubscribeLocalEvent<JointComponent, ComponentShutdown>(HandleShutdown);
}

View File

@@ -28,6 +28,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
public virtual IEnumerable<Type> UpdatesAfter => Enumerable.Empty<Type>();
public virtual IEnumerable<Type> UpdatesBefore => Enumerable.Empty<Type>();
public bool UpdatesOutsidePrediction => true;
public void Initialize() { }
public void Shutdown() { }
@@ -99,7 +100,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
systems.GetEntitySystem<TestSystemC>().Counter = counter;
systems.GetEntitySystem<TestSystemD>().Counter = counter;
systems.TickUpdate(1);
systems.TickUpdate(1, noPredictions: false);
Assert.That(counter.X, Is.EqualTo(4));

View File

@@ -16,6 +16,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
{
public virtual IEnumerable<Type> UpdatesAfter => Enumerable.Empty<Type>();
public virtual IEnumerable<Type> UpdatesBefore => Enumerable.Empty<Type>();
public bool UpdatesOutsidePrediction => true;
public void Initialize() { }
public void Shutdown() { }
public void Update(float frameTime) { }