Expose more state/tick logic to content (#3474)

This commit is contained in:
Leon Friedrich
2022-11-17 08:56:02 +13:00
committed by GitHub
parent a0c23c7fee
commit 60e0c0b804
10 changed files with 68 additions and 20 deletions

View File

@@ -518,9 +518,16 @@ namespace Robust.Client
{
using (_prof.Group("Entity"))
{
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.LastProcessedTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
if (ContentEntityTickUpdate != null)
{
ContentEntityTickUpdate.Invoke(frameEventArgs);
}
else
{
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.LastProcessedTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
}
}
}
@@ -687,5 +694,7 @@ namespace Robust.Client
string? SplashLogo,
bool AutoConnect
);
public event Action<FrameEventArgs>? ContentEntityTickUpdate;
}
}

View File

@@ -124,7 +124,7 @@ namespace Robust.Client.GameObjects
{
var (_, msg) = _queue.Take();
// Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg);
DispatchMsgEntity(msg);
DispatchReceivedNetworkMsg(msg);
}
}
@@ -158,7 +158,7 @@ namespace Robust.Client.GameObjects
{
if (message.SourceTick <= _gameTiming.LastRealTick)
{
DispatchMsgEntity(message);
DispatchReceivedNetworkMsg(message);
return;
}
@@ -168,20 +168,24 @@ namespace Robust.Client.GameObjects
_queue.Add((++_incomingMsgSequence, message));
}
private void DispatchMsgEntity(MsgEntity message)
private void DispatchReceivedNetworkMsg(MsgEntity message)
{
switch (message.Type)
{
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
DispatchReceivedNetworkMsg(message.SystemMessage);
return;
}
}
public void DispatchReceivedNetworkMsg(EntityEventArgs msg)
{
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
}
private sealed class MessageTickComparer : IComparer<(uint seq, MsgEntity msg)>
{
public int Compare((uint seq, MsgEntity msg) x, (uint seq, MsgEntity msg) y)

View File

@@ -4,5 +4,9 @@ namespace Robust.Client.GameObjects
{
public interface IClientEntityManager : IEntityManager, IEntityNetworkManager
{
/// <summary>
/// Raises a networked message as if it had arrived from the sever.
/// </summary>
public void DispatchReceivedNetworkMsg(EntityEventArgs msg);
}
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.GameStates;
/// <summary>
/// Tracks dirty entities on the client for the purposes of gamestatemanager.
/// </summary>
internal sealed class ClientDirtySystem : EntitySystem
public sealed class ClientDirtySystem : EntitySystem
{
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IComponentFactory _compFact = default!;
@@ -65,7 +65,7 @@ internal sealed class ClientDirtySystem : EntitySystem
RemovedComponents.GetOrNew(comp.Owner).Add(netId.Value);
}
internal void Reset()
public void Reset()
{
DirtyEntities.Clear();
RemovedComponents.Clear();

View File

@@ -192,6 +192,8 @@ namespace Robust.Client.GameStates
AckGameState(message.State.ToSequence);
}
public void UpdateFullRep(GameState state) => _processor.UpdateFullRep(state);
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
{
_processor.AddLeavePvsMessage(message);
@@ -272,7 +274,7 @@ namespace Robust.Client.GameStates
// Update the cached server state.
using (_prof.Group("FullRep"))
{
_processor.UpdateFullRep(curState, _entities);
_processor.UpdateFullRep(curState);
}
IEnumerable<EntityUid> createdEntities;
@@ -440,7 +442,7 @@ namespace Robust.Client.GameStates
}
}
private void ResetPredictedEntities()
public void ResetPredictedEntities()
{
PredictionNeedsResetting = false;
@@ -587,7 +589,7 @@ namespace Robust.Client.GameStates
_network.ClientSendMessage(new MsgStateAck() { Sequence = sequence });
}
private IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
public IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
{
using var _ = _timing.StartStateApplicationArea();

View File

@@ -152,7 +152,7 @@ namespace Robust.Client.GameStates
return applyNextState;
}
public void UpdateFullRep(GameState state, IEntityManager entMan)
public void UpdateFullRep(GameState state)
{
// Note: the most recently received server state currently doesn't include pvs-leave messages (detaching
// transform to null-space). This is because a client should never predict an entity being moved back from

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Input;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
@@ -70,6 +72,16 @@ namespace Robust.Client.GameStates
/// </summary>
void ApplyGameState();
/// <summary>
/// Applies a given set of game states.
/// </summary>
IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState);
/// <summary>
/// Resets any entities that have changed while predicting future ticks.
/// </summary>
void ResetPredictedEntities();
/// <summary>
/// An input command has been dispatched.
/// </summary>
@@ -82,5 +94,7 @@ namespace Robust.Client.GameStates
public void RequestFullState(EntityUid? missingEntity = null);
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
void UpdateFullRep(GameState state);
}
}

View File

@@ -1,4 +1,8 @@
namespace Robust.Client;
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
namespace Robust.Client;
public interface IGameController
{
@@ -15,5 +19,12 @@ public interface IGameController
/// <param name="address">The server address, such as "ss14://localhost:1212/".</param>
/// <param name="text">Informational text on the cause of the reconnect. Empty or null gives a default reason.</param>
void Redial(string address, string? text = null);
/// <summary>
/// This event gets invoked prior to performing entity tick update logic. If this is null the game
/// controller will simply call <see cref="IEntityManager.TickUpdate(float, bool, Prometheus.Histogram?)"/>.
/// This exists to give content module more control over tick updating.
/// </summary>
event Action<FrameEventArgs>? ContentEntityTickUpdate;
}

View File

@@ -111,7 +111,8 @@ namespace Robust.Client.Player
// This happens when the server says "nothing changed!"
return;
}
DebugTools.Assert(_network.IsConnected, "Received player state without being connected?");
DebugTools.Assert(_network.IsConnected || _client.RunLevel == ClientRunLevel.SinglePlayerGame // replays use state application.
, "Received player state without being connected?");
DebugTools.Assert(LocalPlayer != null, "Call Startup()");
DebugTools.Assert(LocalPlayer!.Session != null, "Received player state before Session finished setup.");
@@ -213,7 +214,8 @@ namespace Robust.Client.Player
// clear slot, player left
if (!hitSet.Contains(existing))
{
DebugTools.Assert(LocalPlayer!.UserId != existing, "I'm still connected to the server, but i left?");
DebugTools.Assert(LocalPlayer!.UserId != existing || _client.RunLevel == ClientRunLevel.SinglePlayerGame, // replays apply player states.
"I'm still connected to the server, but i left?");
_sessions.Remove(existing);
dirty = true;
}

View File

@@ -12,6 +12,8 @@ namespace Robust.UnitTesting
public GameControllerOptions Options { get; } = new();
public bool ContentStart { get; set; }
public event Action<FrameEventArgs>? ContentEntityTickUpdate;
public void Shutdown(string? reason = null)
{
}