mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Prediction (#1027)
This commit is contained in:
committed by
GitHub
parent
2da1640ab7
commit
0c8f869cb4
@@ -65,6 +65,7 @@ namespace Robust.Client
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgServerInfo>(MsgServerInfo.NAME, HandleServerInfo);
|
||||
_net.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME, HandleSetTickRate);
|
||||
_net.Connected += OnConnected;
|
||||
_net.ConnectFailed += OnConnectFailed;
|
||||
_net.Disconnect += OnNetDisconnect;
|
||||
@@ -120,7 +121,7 @@ namespace Robust.Client
|
||||
/// receiving states when they join the lobby.
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedServer(PlayerSession session)
|
||||
private void OnPlayerJoinedServer(IPlayerSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.Connected);
|
||||
@@ -135,7 +136,7 @@ namespace Robust.Client
|
||||
/// Player is joining the game
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedGame(PlayerSession session)
|
||||
private void OnPlayerJoinedGame(IPlayerSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.InGame);
|
||||
@@ -193,6 +194,11 @@ namespace Robust.Client
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleSetTickRate(MsgSetTickRate message)
|
||||
{
|
||||
_timing.TickRate = message.NewTickRate;
|
||||
}
|
||||
|
||||
private void OnLocalStatusChanged(object obj, StatusEventArgs eventArgs)
|
||||
{
|
||||
// player finished fully connecting to the server.
|
||||
@@ -253,12 +259,12 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// The session that triggered the event.
|
||||
/// </summary>
|
||||
private PlayerSession Session { get; }
|
||||
private IPlayerSession Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the class.
|
||||
/// </summary>
|
||||
public PlayerEventArgs(PlayerSession session)
|
||||
public PlayerEventArgs(IPlayerSession session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
|
||||
@@ -267,13 +267,13 @@ namespace Robust.Client
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
_taskManager.ProcessPendingTasks();
|
||||
_userInterfaceManager.Update(frameEventArgs);
|
||||
_stateManager.Update(frameEventArgs);
|
||||
|
||||
if (_client.RunLevel >= ClientRunLevel.Connected)
|
||||
{
|
||||
_gameStateManager.ApplyGameState();
|
||||
}
|
||||
|
||||
_stateManager.Update(frameEventArgs);
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,12 @@ namespace Robust.Client.GameObjects
|
||||
Started = true;
|
||||
}
|
||||
|
||||
public void ApplyEntityStates(List<EntityState> curEntStates, IEnumerable<EntityUid> deletions,
|
||||
public List<EntityUid> ApplyEntityStates(List<EntityState> curEntStates, IEnumerable<EntityUid> deletions,
|
||||
List<EntityState> nextEntStates)
|
||||
{
|
||||
var toApply = new Dictionary<IEntity, (EntityState, EntityState)>();
|
||||
var toInitialize = new List<Entity>();
|
||||
var created = new List<EntityUid>();
|
||||
deletions ??= new EntityUid[0];
|
||||
|
||||
if (curEntStates != null && curEntStates.Count != 0)
|
||||
@@ -64,6 +65,7 @@ namespace Robust.Client.GameObjects
|
||||
var newEntity = CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
created.Add(newEntity.Uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +162,8 @@ namespace Robust.Client.GameObjects
|
||||
entity.Delete();
|
||||
}
|
||||
#endif
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -14,13 +18,17 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IClientNetManager _networkManager;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager;
|
||||
[Dependency] private readonly IGameTiming _gameTiming;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<NetworkComponentMessage> ReceivedComponentMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<EntitySystemMessage> ReceivedSystemMessage;
|
||||
public event EventHandler<object> ReceivedSystemMessage;
|
||||
|
||||
private readonly PriorityQueue<MsgEntity> _queue = new PriorityQueue<MsgEntity>(new MessageTickComparer());
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
@@ -28,12 +36,28 @@ namespace Robust.Client.GameObjects
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
while (_queue.Count != 0 && _queue.Peek().SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
DispatchMsgEntity(_queue.Take());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntitySystemMessage message)
|
||||
{
|
||||
SendSystemNetworkMessage(message, default(uint));
|
||||
}
|
||||
|
||||
public void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
|
||||
{
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.SystemMessage;
|
||||
msg.SystemMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
msg.Sequence = sequence;
|
||||
|
||||
_networkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
@@ -54,10 +78,23 @@ namespace Robust.Client.GameObjects
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = component.NetID.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
_networkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
if (message.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
DispatchMsgEntity(message);
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Add(message);
|
||||
}
|
||||
|
||||
private void DispatchMsgEntity(MsgEntity message)
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
@@ -70,5 +107,16 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MessageTickComparer : IComparer<MsgEntity>
|
||||
{
|
||||
public int Compare(MsgEntity x, MsgEntity y)
|
||||
{
|
||||
DebugTools.AssertNotNull(x);
|
||||
DebugTools.AssertNotNull(y);
|
||||
|
||||
return y.SourceTick.CompareTo(x.SourceTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
Robust.Client/GameObjects/EntityManagerExt.cs
Normal file
23
Robust.Client/GameObjects/EntityManagerExt.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public static class EntityManagerExt
|
||||
{
|
||||
public static void RaisePredictiveEvent<T>(this IEntityManager entityManager, T msg)
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
var sequence = IoCManager.Resolve<IClientGameStateManager>().SystemMessageDispatched(msg);
|
||||
entityManager.EntityNetManager.SendSystemNetworkMessage(msg, sequence);
|
||||
|
||||
var eventArgs = new EntitySessionEventArgs(IoCManager.Resolve<IPlayerManager>().LocalPlayer.Session);
|
||||
|
||||
entityManager.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
entityManager.EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects.Components;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -21,6 +23,7 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IInputManager _inputManager;
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
[Dependency] private readonly IClientGameStateManager _stateManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
private readonly IPlayerCommandStates _cmdStates = new PlayerCommandStates();
|
||||
@@ -36,6 +39,11 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
/// </summary>
|
||||
public ICommandBindMapping BindMap => _bindMap;
|
||||
|
||||
/// <summary>
|
||||
/// If the input system is currently predicting input.
|
||||
/// </summary>
|
||||
public bool Predicted { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an Input Command into the simulation.
|
||||
/// </summary>
|
||||
@@ -52,6 +60,11 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
#endif
|
||||
|
||||
// set state, state change is updated regardless if it is locally bound
|
||||
if (_cmdStates.GetState(function) == message.State)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cmdStates.SetState(function, message.State);
|
||||
|
||||
// handle local binds before sending off
|
||||
@@ -62,6 +75,32 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
// send it off to the client
|
||||
DispatchInputCommand(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a predicted input command.
|
||||
/// </summary>
|
||||
/// <param name="inputCmd">Input command to handle as predicted.</param>
|
||||
public void PredictInputCommand(FullInputCmdMessage inputCmd)
|
||||
{
|
||||
var keyFunc = _inputManager.NetworkBindMap.KeyFunctionName(inputCmd.InputFunctionId);
|
||||
|
||||
if (!_bindMap.TryGetHandler(keyFunc, out var handler))
|
||||
return;
|
||||
|
||||
Predicted = true;
|
||||
|
||||
var session = _playerManager.LocalPlayer.Session;
|
||||
handler.HandleCmdMessage(session, inputCmd);
|
||||
|
||||
Predicted = false;
|
||||
}
|
||||
|
||||
private void DispatchInputCommand(FullInputCmdMessage message)
|
||||
{
|
||||
_stateManager.InputCommandDispatched(message);
|
||||
RaiseNetworkEvent(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Client.Interfaces;
|
||||
using Robust.Client.Interfaces.GameObjects;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
@@ -19,7 +27,14 @@ namespace Robust.Client.GameStates
|
||||
public class ClientGameStateManager : IClientGameStateManager
|
||||
{
|
||||
private GameStateProcessor _processor;
|
||||
|
||||
|
||||
private uint _nextInputCmdSeq = 1;
|
||||
private readonly Queue<FullInputCmdMessage> _pendingInputs = new Queue<FullInputCmdMessage>();
|
||||
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, EntitySystemMessage msg, object sessionMsg)>
|
||||
_pendingSystemMessages
|
||||
= new Queue<(uint, GameTick, EntitySystemMessage, object)>();
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IClientEntityManager _entities;
|
||||
[Dependency] private readonly IPlayerManager _players;
|
||||
@@ -28,6 +43,8 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
[Dependency] private readonly IGameTiming _timing;
|
||||
[Dependency] private readonly IConfigurationManager _config;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||
[Dependency] private readonly IComponentManager _componentManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -37,7 +54,16 @@ namespace Robust.Client.GameStates
|
||||
public int TargetBufferSize => _processor.TargetBufferSize;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CurrentBufferSize => _processor.CurrentBufferSize;
|
||||
public int CurrentBufferSize => _processor.CalculateBufferSize(CurServerTick);
|
||||
|
||||
public bool Predicting { get; private set; }
|
||||
|
||||
public int PredictSize { get; private set; }
|
||||
|
||||
private uint _lastProcessedSeq;
|
||||
private GameTick _lastProcessedTick = GameTick.Zero;
|
||||
|
||||
public GameTick CurServerTick => _lastProcessedTick;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<GameStateAppliedArgs> GameStateApplied;
|
||||
@@ -51,24 +77,26 @@ namespace Robust.Client.GameStates
|
||||
_network.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME);
|
||||
_client.RunLevelChanged += RunLevelChanged;
|
||||
|
||||
if(!_config.IsCVarRegistered("net.interp"))
|
||||
_config.RegisterCVar("net.interp", false, CVar.ARCHIVE, b => _processor.Interpolation = b);
|
||||
|
||||
if (!_config.IsCVarRegistered("net.interp_ratio"))
|
||||
_config.RegisterCVar("net.interp_ratio", 0, CVar.ARCHIVE, i => _processor.InterpRatio = i);
|
||||
|
||||
if (!_config.IsCVarRegistered("net.logging"))
|
||||
_config.RegisterCVar("net.logging", false, CVar.ARCHIVE, b => _processor.Logging = b);
|
||||
_config.RegisterCVar("net.interp", false, CVar.ARCHIVE, b => _processor.Interpolation = b);
|
||||
_config.RegisterCVar("net.interp_ratio", 0, CVar.ARCHIVE, i => _processor.InterpRatio = i);
|
||||
_config.RegisterCVar("net.logging", false, CVar.ARCHIVE, b => _processor.Logging = b);
|
||||
_config.RegisterCVar("net.predict", true, CVar.ARCHIVE, b => Predicting = b);
|
||||
_config.RegisterCVar("net.predict_size", 2, CVar.ARCHIVE, i => PredictSize = i);
|
||||
|
||||
_processor.Interpolation = _config.GetCVar<bool>("net.interp");
|
||||
_processor.InterpRatio = _config.GetCVar<int>("net.interp_ratio");
|
||||
_processor.Logging = _config.GetCVar<bool>("net.logging");
|
||||
Predicting = _config.GetCVar<bool>("net.predict");
|
||||
PredictSize = _config.GetCVar<int>("net.predict_size");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
_processor.Reset();
|
||||
|
||||
_lastProcessedTick = GameTick.Zero;
|
||||
_lastProcessedSeq = 0;
|
||||
}
|
||||
|
||||
private void RunLevelChanged(object sender, RunLevelChangedEventArgs args)
|
||||
@@ -80,23 +108,222 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
public void InputCommandDispatched(FullInputCmdMessage message)
|
||||
{
|
||||
if (!Predicting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
message.InputSequence = _nextInputCmdSeq;
|
||||
_pendingInputs.Enqueue(message);
|
||||
|
||||
var inputMan = IoCManager.Resolve<IInputManager>();
|
||||
inputMan.NetworkBindMap.TryGetKeyFunction(message.InputFunctionId, out var boundFunc);
|
||||
Logger.DebugS("State",
|
||||
$"CL> SENT tick={_timing.CurTick}, seq={_nextInputCmdSeq}, func={boundFunc.FunctionName}, state={message.State}");
|
||||
_nextInputCmdSeq++;
|
||||
}
|
||||
|
||||
public uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage
|
||||
{
|
||||
if (!Predicting)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalPlayer.Session);
|
||||
_pendingSystemMessages.Enqueue((_nextInputCmdSeq, _timing.CurTick, message,
|
||||
new EntitySessionMessage<T>(evArgs, message)));
|
||||
|
||||
return _nextInputCmdSeq++;
|
||||
}
|
||||
|
||||
private void HandleStateMessage(MsgState message)
|
||||
{
|
||||
var state = message.State;
|
||||
|
||||
var lastCurTick = _timing.CurTick;
|
||||
_timing.CurTick = _lastProcessedTick + 1;
|
||||
|
||||
_processor.AddNewState(state);
|
||||
|
||||
// we always ack everything we receive, even if it is late
|
||||
AckGameState(state.ToSequence);
|
||||
|
||||
_timing.CurTick = lastCurTick;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyGameState()
|
||||
{
|
||||
if (!_processor.ProcessTickStates(_timing.CurTick, out var curState, out var nextState))
|
||||
return;
|
||||
_timing.CurTick = _lastProcessedTick + 1;
|
||||
|
||||
ApplyGameState(curState, nextState);
|
||||
if (!_processor.ProcessTickStates(_timing.CurTick, out var curState, out var nextState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Predicting)
|
||||
{
|
||||
// Disable IsFirstTimePredicted while re-running HandleComponentState here.
|
||||
// Helps with debugging.
|
||||
using var _ = _timing.StartPastPredictionArea();
|
||||
|
||||
ResetPredictedEntities(_timing.CurTick);
|
||||
}
|
||||
|
||||
// apply current state
|
||||
var createdEntities = ApplyGameState(curState, nextState);
|
||||
|
||||
MergeImplicitData(createdEntities);
|
||||
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var inputMan = IoCManager.Resolve<IInputManager>();
|
||||
var input = sysMan.GetEntitySystem<InputSystem>();
|
||||
|
||||
if (_lastProcessedSeq < curState.LastProcessedInput)
|
||||
{
|
||||
Logger.DebugS("State", $"SV> RCV tick={_timing.CurTick}, seq={_lastProcessedSeq}");
|
||||
_lastProcessedSeq = curState.LastProcessedInput;
|
||||
}
|
||||
|
||||
// remove old pending inputs
|
||||
while (_pendingInputs.Count > 0 && _pendingInputs.Peek().InputSequence <= _lastProcessedSeq)
|
||||
{
|
||||
var inCmd = _pendingInputs.Dequeue();
|
||||
|
||||
inputMan.NetworkBindMap.TryGetKeyFunction(inCmd.InputFunctionId, out var boundFunc);
|
||||
Logger.DebugS("State",
|
||||
$"SV> seq={inCmd.InputSequence}, func={boundFunc.FunctionName}, state={inCmd.State}");
|
||||
}
|
||||
|
||||
while (_pendingSystemMessages.Count > 0 && _pendingSystemMessages.Peek().sequence <= _lastProcessedSeq)
|
||||
{
|
||||
_pendingSystemMessages.Dequeue();
|
||||
}
|
||||
|
||||
DebugTools.Assert(_timing.InSimulation);
|
||||
_lastProcessedTick = _timing.CurTick;
|
||||
|
||||
if (Predicting)
|
||||
{
|
||||
using var _ = _timing.StartPastPredictionArea();
|
||||
|
||||
/*
|
||||
if (_pendingInputs.Count > 0)
|
||||
{
|
||||
Logger.DebugS("State", "CL> Predicted:");
|
||||
}
|
||||
*/
|
||||
|
||||
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
|
||||
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel.Ping / 1000f; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize + _timing.TickRate * ping +
|
||||
PredictSize;
|
||||
|
||||
//Logger.DebugS("State", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
for (var t = _lastProcessedTick.Value; t < targetTick; t++)
|
||||
{
|
||||
var tick = new GameTick(t);
|
||||
_timing.CurTick = tick;
|
||||
|
||||
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
|
||||
{
|
||||
var inputCmd = pendingInputEnumerator.Current;
|
||||
|
||||
inputMan.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
|
||||
/*
|
||||
Logger.DebugS("State",
|
||||
$" seq={inputCmd.InputSequence}, dTick={tick}, func={boundFunc.FunctionName}, " +
|
||||
$"state={inputCmd.State}");
|
||||
*/
|
||||
|
||||
input.PredictInputCommand(inputCmd);
|
||||
|
||||
hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
Logger.DebugS("State", "doot");
|
||||
}
|
||||
|
||||
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
{
|
||||
foreach (var entity in _entities.GetEntities())
|
||||
{
|
||||
// TODO: 99% there's an off-by-one here.
|
||||
if (entity.Uid.IsClientSide() || entity.LastModifiedTick < curTick)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.DebugS("State", $"Entity {entity.Uid} was made dirty.");
|
||||
|
||||
var last = _processor.GetLastServerStates(entity.Uid);
|
||||
|
||||
// TODO: handle component deletions/creations.
|
||||
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
|
||||
{
|
||||
DebugTools.AssertNotNull(comp.NetID);
|
||||
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID.Value, out var compState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.DebugS("State", $" And also its component {comp.Name}");
|
||||
// TODO: Handle interpolation.
|
||||
comp.HandleComponentState(compState, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeImplicitData(List<EntityUid> createdEntities)
|
||||
{
|
||||
// The server doesn't send data that the server can replicate itself on entity creation.
|
||||
// As such, GameStateProcessor doesn't have that data either.
|
||||
// We have to feed it back this data by calling GetComponentState() and such,
|
||||
// so that we can later roll back to it (if necessary).
|
||||
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
|
||||
|
||||
foreach (var createdEntity in createdEntities)
|
||||
{
|
||||
var compData = new Dictionary<uint, ComponentState>();
|
||||
outputData.Add(createdEntity, compData);
|
||||
|
||||
foreach (var component in _componentManager.GetNetComponents(createdEntity))
|
||||
{
|
||||
var state = component.GetComponentState();
|
||||
|
||||
if (state.GetType() == typeof(ComponentState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
compData.Add(state.NetID, state);
|
||||
}
|
||||
}
|
||||
|
||||
_processor.MergeImplicitData(outputData);
|
||||
}
|
||||
|
||||
private void AckGameState(GameTick sequence)
|
||||
@@ -106,14 +333,16 @@ namespace Robust.Client.GameStates
|
||||
_network.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
private void ApplyGameState(GameState curState, GameState nextState)
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState nextState)
|
||||
{
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
_entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions, nextState?.EntityStates);
|
||||
var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates);
|
||||
_mapManager.ApplyGameStatePost(curState.MapData);
|
||||
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
|
||||
return createdEntities;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.Log;
|
||||
@@ -20,6 +21,9 @@ namespace Robust.Client.GameStates
|
||||
private GameTick _lastProcessedRealState;
|
||||
private GameTick _highestFromSequence;
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<uint, ComponentState>> _lastStateFullRep
|
||||
= new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public int MinBufferSize => Interpolation ? 3 : 2;
|
||||
|
||||
@@ -27,7 +31,7 @@ namespace Robust.Client.GameStates
|
||||
public int TargetBufferSize => MinBufferSize + InterpRatio;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CurrentBufferSize => _stateBuffer.Count(s => s.ToSequence >= _timing.CurTick);
|
||||
public int CurrentBufferSize => CalculateBufferSize(_timing.CurTick);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Interpolation { get; set; }
|
||||
@@ -119,7 +123,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (applyNextState && !curState.Extrapolated)
|
||||
_lastProcessedRealState = curState.ToSequence;
|
||||
|
||||
|
||||
if (!_waitingForFull)
|
||||
{
|
||||
if (!applyNextState)
|
||||
@@ -135,12 +139,72 @@ namespace Robust.Client.GameStates
|
||||
_timing.TickTimingAdjustment = 0f;
|
||||
}
|
||||
|
||||
if (Logging && applyNextState)
|
||||
Logger.DebugS("net.state", $"Applying State: ext={curState.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
|
||||
if (applyNextState)
|
||||
{
|
||||
if (Logging)
|
||||
{
|
||||
Logger.DebugS("net.state", $"Applying State: ext={curState.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
|
||||
}
|
||||
|
||||
if (!curState.Extrapolated)
|
||||
{
|
||||
UpdateFullRep(curState);
|
||||
}
|
||||
}
|
||||
|
||||
return applyNextState;
|
||||
}
|
||||
|
||||
private void UpdateFullRep(GameState state)
|
||||
{
|
||||
if (state.FromSequence == GameTick.Zero)
|
||||
{
|
||||
// Full state.
|
||||
_lastStateFullRep.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state.EntityDeletions != null)
|
||||
{
|
||||
foreach (var deletion in state.EntityDeletions)
|
||||
{
|
||||
_lastStateFullRep.Remove(deletion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.EntityStates != null)
|
||||
{
|
||||
foreach (var entityState in state.EntityStates)
|
||||
{
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData))
|
||||
{
|
||||
compData = new Dictionary<uint, ComponentState>();
|
||||
_lastStateFullRep.Add(entityState.Uid, compData);
|
||||
}
|
||||
|
||||
if (entityState.ComponentChanges != null)
|
||||
{
|
||||
foreach (var change in entityState.ComponentChanges)
|
||||
{
|
||||
if (change.Deleted)
|
||||
{
|
||||
compData.Remove(change.NetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entityState.ComponentStates != null)
|
||||
{
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
{
|
||||
compData[compState.NetID] = compState;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CalculateFullState(out GameState curState, out GameState nextState, int targetBufferSize)
|
||||
{
|
||||
if (_lastFullState != null)
|
||||
@@ -206,6 +270,7 @@ namespace Robust.Client.GameStates
|
||||
nextState = null;
|
||||
|
||||
GameState futureState = null;
|
||||
uint lastStateInput = 0;
|
||||
|
||||
for (var i = 0; i < _stateBuffer.Count; i++)
|
||||
{
|
||||
@@ -225,6 +290,10 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
futureState = state;
|
||||
}
|
||||
else if (state.ToSequence == lastTick)
|
||||
{
|
||||
lastStateInput = state.LastProcessedInput;
|
||||
}
|
||||
else if (state.ToSequence < _highestFromSequence) // remove any old states we find to keep the buffer clean
|
||||
{
|
||||
_stateBuffer.RemoveSwap(i);
|
||||
@@ -236,7 +305,7 @@ namespace Robust.Client.GameStates
|
||||
if (!Extrapolation && curState == null && futureState != null)
|
||||
{
|
||||
//this is not actually extrapolation
|
||||
curState = ExtrapolateState(_highestFromSequence, curTick);
|
||||
curState = ExtrapolateState(_highestFromSequence, curTick, lastStateInput);
|
||||
return true; // keep moving, we have a future state
|
||||
}
|
||||
|
||||
@@ -253,12 +322,12 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (curState == null)
|
||||
{
|
||||
curState = ExtrapolateState(_highestFromSequence, curTick);
|
||||
curState = ExtrapolateState(_highestFromSequence, curTick, lastStateInput);
|
||||
}
|
||||
|
||||
if (nextState == null && Interpolation)
|
||||
{
|
||||
nextState = ExtrapolateState(_highestFromSequence, nextTick);
|
||||
nextState = ExtrapolateState(_highestFromSequence, nextTick, lastStateInput);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -267,9 +336,9 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// Generates a completely fake GameState.
|
||||
/// </summary>
|
||||
private static GameState ExtrapolateState(GameTick fromSequence, GameTick toSequence)
|
||||
private static GameState ExtrapolateState(GameTick fromSequence, GameTick toSequence, uint lastInput)
|
||||
{
|
||||
var state = new GameState(fromSequence, toSequence, null, null, null, null);
|
||||
var state = new GameState(fromSequence, toSequence, lastInput, null, null, null, null);
|
||||
state.Extrapolated = true;
|
||||
return state;
|
||||
}
|
||||
@@ -281,5 +350,31 @@ namespace Robust.Client.GameStates
|
||||
_lastFullState = null;
|
||||
_waitingForFull = true;
|
||||
}
|
||||
|
||||
public void MergeImplicitData(Dictionary<EntityUid, Dictionary<uint, ComponentState>> data)
|
||||
{
|
||||
foreach (var (uid, compData) in data)
|
||||
{
|
||||
var fullRep = _lastStateFullRep[uid];
|
||||
|
||||
foreach (var (netId, compState) in compData)
|
||||
{
|
||||
if (!fullRep.ContainsKey(netId))
|
||||
{
|
||||
fullRep.Add(netId, compState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<uint, ComponentState> GetLastServerStates(EntityUid entity)
|
||||
{
|
||||
return _lastStateFullRep[entity];
|
||||
}
|
||||
|
||||
public int CalculateBufferSize(GameTick fromTick)
|
||||
{
|
||||
return _stateBuffer.Count(s => s.ToSequence >= fromTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds a collection of game states and calculates which ones to apply at a given game tick.
|
||||
/// It also stores a copy of all the last entity states from the server,
|
||||
/// allowing the game to be reset to a server-like condition at any point.
|
||||
/// </summary>
|
||||
internal interface IGameStateProcessor
|
||||
{
|
||||
@@ -26,6 +30,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// Number of game states currently in the state buffer.
|
||||
/// </summary>
|
||||
/// <seealso cref="CalculateBufferSize"/>
|
||||
int CurrentBufferSize { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -71,5 +76,31 @@ namespace Robust.Client.GameStates
|
||||
/// Resets the processor back to its initial state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Merges entity data into the full copy of the server states.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is necessary because the server does not send data
|
||||
/// that can be inferred from entity creation on new entity states.
|
||||
/// This data thus has to be re-constructed client-side and merged with this method.
|
||||
/// </remarks>
|
||||
/// <param name="data">
|
||||
/// The data to merge.
|
||||
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
|
||||
/// </param>
|
||||
void MergeImplicitData(Dictionary<EntityUid, Dictionary<uint, ComponentState>> data);
|
||||
|
||||
/// <summary>
|
||||
/// Get the last state data from the server for an entity.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary (net ID -> ComponentState)</returns>
|
||||
Dictionary<uint, ComponentState> GetLastServerStates(EntityUid entity);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the size of the game state buffer from a given tick.
|
||||
/// </summary>
|
||||
/// <param name="fromTick">The tick to calculate from.</param>
|
||||
int CalculateBufferSize(GameTick fromTick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,16 +215,22 @@ namespace Robust.Client.Input
|
||||
UIKeyBindStateChanged?.Invoke(eventArgs);
|
||||
if (state == BoundKeyState.Up || !eventArgs.Handled)
|
||||
{
|
||||
KeyBindStateChanged?.Invoke(eventArgs);
|
||||
|
||||
var cmd = GetInputCommand(binding.Function);
|
||||
if (state == BoundKeyState.Up)
|
||||
// TODO: Allow input commands to still get forwarded to server if necessary.
|
||||
if (cmd != null)
|
||||
{
|
||||
cmd?.Disabled(null);
|
||||
if (state == BoundKeyState.Up)
|
||||
{
|
||||
cmd.Disabled(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.Enabled(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd?.Enabled(null);
|
||||
KeyBindStateChanged?.Invoke(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Robust.Client.Interfaces.GameObjects
|
||||
{
|
||||
public interface IClientEntityManager : IEntityManager
|
||||
{
|
||||
void ApplyEntityStates(List<EntityState> curEntStates, IEnumerable<EntityUid> deletions, List<EntityState> nextEntStates);
|
||||
/// <returns>The list of new entities created.</returns>
|
||||
List<EntityUid> ApplyEntityStates(List<EntityState> curEntStates, IEnumerable<EntityUid> deletions,
|
||||
List<EntityState> nextEntStates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Interfaces.GameStates
|
||||
{
|
||||
@@ -28,6 +31,14 @@ namespace Robust.Client.Interfaces.GameStates
|
||||
/// </summary>
|
||||
int CurrentBufferSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current tick of the last server game state applied.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this to synchronize server-sent simulation events with the client's game loop.
|
||||
/// </remarks>
|
||||
GameTick CurServerTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This is called after the game state has been applied for the current tick.
|
||||
/// </summary>
|
||||
@@ -47,5 +58,13 @@ namespace Robust.Client.Interfaces.GameStates
|
||||
/// Applies the game state for this tick.
|
||||
/// </summary>
|
||||
void ApplyGameState();
|
||||
|
||||
/// <summary>
|
||||
/// An input command has been dispatched.
|
||||
/// </summary>
|
||||
/// <param name="message">Message being dispatched.</param>
|
||||
void InputCommandDispatched(FullInputCmdMessage message);
|
||||
|
||||
uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,10 @@ namespace Robust.Client.Player
|
||||
/// <summary>
|
||||
/// Session of the local client.
|
||||
/// </summary>
|
||||
[ViewVariables] public PlayerSession Session { get; set; }
|
||||
[ViewVariables]
|
||||
public IPlayerSession Session => InternalSession;
|
||||
|
||||
internal PlayerSession InternalSession { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OOC name of the local player.
|
||||
@@ -74,6 +77,7 @@ namespace Robust.Client.Player
|
||||
DetachEntity();
|
||||
|
||||
ControlledEntity = entity;
|
||||
InternalSession.AttachedEntity = entity;
|
||||
|
||||
if (!ControlledEntity.TryGetComponent<EyeComponent>(out var eye))
|
||||
{
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Robust.Client.Player
|
||||
_sessions.Add(state.SessionId, newSession);
|
||||
if (state.SessionId == LocalPlayer.SessionId)
|
||||
{
|
||||
LocalPlayer.Session = newSession;
|
||||
LocalPlayer.InternalSession = newSession;
|
||||
|
||||
// We just connected to the server, hurray!
|
||||
LocalPlayer.SwitchState(SessionStatus.Connecting, newSession.Status);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
|
||||
public class PlayerSession : IPlayerSession
|
||||
internal sealed class PlayerSession : IPlayerSession
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SessionStatus Status { get; set; } = SessionStatus.Connecting;
|
||||
|
||||
public IEntity AttachedEntity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetSessionId SessionId { get; }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Client.Interfaces.State;
|
||||
@@ -8,6 +9,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
@@ -54,10 +56,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_debugNetPanel = new DebugNetPanel(netManager, gameTiming1);
|
||||
AddChild(_debugNetPanel);
|
||||
|
||||
_timeDebug = new DebugTimePanel(gameTiming1)
|
||||
{
|
||||
Visible = false,
|
||||
};
|
||||
_timeDebug = new DebugTimePanel(gameTiming1, IoCManager.Resolve<IClientGameStateManager>());
|
||||
AddChild(_timeDebug);
|
||||
|
||||
_frameGraph = new FrameGraph(gameTiming1);
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
public class DebugTimePanel : PanelContainer
|
||||
{
|
||||
private readonly IGameTiming _gameTiming;
|
||||
private readonly IClientGameStateManager _gameState;
|
||||
|
||||
private Label _contents;
|
||||
|
||||
public DebugTimePanel(IGameTiming gameTiming)
|
||||
public DebugTimePanel(IGameTiming gameTiming, IClientGameStateManager gameState)
|
||||
{
|
||||
_gameTiming = gameTiming;
|
||||
_gameState = gameState;
|
||||
|
||||
_contents = new Label
|
||||
{
|
||||
FontColorShadowOverride = Color.Black,
|
||||
/*MarginTop = 5,
|
||||
MarginLeft = 5*/
|
||||
};
|
||||
AddChild(_contents);
|
||||
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = new Color(67, 105, 255, 138),
|
||||
BackgroundColor = new Color(35, 134, 37, 138),
|
||||
ContentMarginLeftOverride = 5,
|
||||
ContentMarginTopOverride = 5
|
||||
};
|
||||
|
||||
MouseFilter = _contents.MouseFilter = MouseFilterMode.Ignore;
|
||||
@@ -46,8 +46,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
return;
|
||||
}
|
||||
|
||||
_contents.Text = $@"Paused: {_gameTiming.Paused}, CurTick: {_gameTiming.CurTick},
|
||||
CurTime: {_gameTiming.CurTime}, RealTime: {_gameTiming.RealTime}, CurFrame: {_gameTiming.CurFrame}";
|
||||
_contents.Text = $@"Paused: {_gameTiming.Paused}, CurTick: {_gameTiming.CurTick}, CurServerTick: {_gameState.CurServerTick}, Pred: {_gameTiming.CurTick.Value - _gameState.CurServerTick.Value}
|
||||
CurTime: {_gameTiming.CurTime:hh\:mm\:ss\.ff}, RealTime: {_gameTiming.RealTime:hh\:mm\:ss\.ff}, CurFrame: {_gameTiming.CurFrame}
|
||||
TickTimingAdjustment: {_gameTiming.TickTimingAdjustment}";
|
||||
|
||||
MinimumSizeChanged();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ using Robust.Shared.Localization;
|
||||
using Robust.Server.Interfaces.Debugging;
|
||||
using Robust.Server.ServerStatus;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Network.Messages;
|
||||
|
||||
namespace Robust.Server
|
||||
{
|
||||
@@ -170,6 +171,7 @@ namespace Robust.Server
|
||||
{
|
||||
netMan.Initialize(true);
|
||||
netMan.StartServer();
|
||||
netMan.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -325,7 +327,12 @@ namespace Robust.Server
|
||||
{
|
||||
var cfgMgr = IoCManager.Resolve<IConfigurationManager>();
|
||||
|
||||
cfgMgr.RegisterCVar("net.tickrate", 60, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
cfgMgr.RegisterCVar("net.tickrate", 60, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER, i =>
|
||||
{
|
||||
var b = (byte) i;
|
||||
_time.TickRate = b;
|
||||
SendTickRateUpdateToClients(b);
|
||||
});
|
||||
|
||||
cfgMgr.RegisterCVar("game.hostname", "MyServer", CVar.ARCHIVE);
|
||||
cfgMgr.RegisterCVar("game.maxplayers", 32, CVar.ARCHIVE);
|
||||
@@ -338,6 +345,14 @@ namespace Robust.Server
|
||||
Logger.InfoS("srv", $"Max players: {MaxPlayers}");
|
||||
}
|
||||
|
||||
private void SendTickRateUpdateToClients(byte newTickRate)
|
||||
{
|
||||
var msg = _network.CreateNetMessage<MsgSetTickRate>();
|
||||
msg.NewTickRate = newTickRate;
|
||||
|
||||
_network.ServerSendToAll(msg);
|
||||
}
|
||||
|
||||
// called right before main loop returns, do all saving/cleanup in here
|
||||
private void Cleanup()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -12,7 +13,7 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
/// <summary>
|
||||
/// Server side processing of incoming user commands.
|
||||
/// </summary>
|
||||
public class InputSystem : EntitySystem
|
||||
public class InputSystem : SharedInputSystem
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
@@ -21,10 +22,12 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
private readonly Dictionary<IPlayerSession, IPlayerCommandStates> _playerInputs = new Dictionary<IPlayerSession, IPlayerCommandStates>();
|
||||
private readonly CommandBindMapping _bindMap = new CommandBindMapping();
|
||||
|
||||
private readonly Dictionary<IPlayerSession, uint> _lastProcessedInputCmd = new Dictionary<IPlayerSession, uint>();
|
||||
|
||||
/// <summary>
|
||||
/// Server side input command binds.
|
||||
/// </summary>
|
||||
public ICommandBindMapping BindMap => _bindMap;
|
||||
public override ICommandBindMapping BindMap => _bindMap;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -33,18 +36,19 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleCommandMessage(IPlayerSession session, InputCmdHandler cmdHandler, FullInputCmdMessage msg)
|
||||
{
|
||||
cmdHandler.HandleCmdMessage(session, msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Shutdown()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void InputMessageHandler(InputCmdMessage message)
|
||||
private void InputMessageHandler(InputCmdMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
var channel = message.NetChannel;
|
||||
if(channel == null)
|
||||
return;
|
||||
|
||||
if (!(message is FullInputCmdMessage msg))
|
||||
return;
|
||||
|
||||
@@ -52,21 +56,24 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
if (!_playerManager.KeyMap.TryGetKeyFunction(msg.InputFunctionId, out var function))
|
||||
return;
|
||||
|
||||
var session = _playerManager.GetSessionByChannel(channel);
|
||||
|
||||
//Client Sanitization: bad enum key state value
|
||||
if (!Enum.IsDefined(typeof(BoundKeyState), msg.State))
|
||||
return;
|
||||
|
||||
var session = (IPlayerSession) eventArgs.SenderSession;
|
||||
|
||||
if (_lastProcessedInputCmd[session] < msg.InputSequence)
|
||||
_lastProcessedInputCmd[session] = msg.InputSequence;
|
||||
|
||||
// route the cmdMessage to the proper bind
|
||||
//Client Sanitization: unbound command, just ignore
|
||||
if (_bindMap.TryGetHandler(function, out var command))
|
||||
if (_bindMap.TryGetHandler(function, out var cmdHandler))
|
||||
{
|
||||
// set state, only bound key functions get state changes
|
||||
var states = GetInputStates(session);
|
||||
states.SetState(function, msg.State);
|
||||
|
||||
command.HandleCmdMessage(session, msg);
|
||||
HandleCommandMessage(session, cmdHandler, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,16 +82,23 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
return _playerInputs[session];
|
||||
}
|
||||
|
||||
public uint GetLastInputCommand(IPlayerSession session)
|
||||
{
|
||||
return _lastProcessedInputCmd[session];
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs args)
|
||||
{
|
||||
switch (args.NewStatus)
|
||||
{
|
||||
case SessionStatus.Connected:
|
||||
_playerInputs.Add(args.Session, new PlayerCommandStates());
|
||||
_lastProcessedInputCmd.Add(args.Session, 0);
|
||||
break;
|
||||
|
||||
case SessionStatus.Disconnected:
|
||||
_playerInputs.Remove(args.Session);
|
||||
_lastProcessedInputCmd.Remove(args.Session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// The server implementation of the Entity Network Manager.
|
||||
/// </summary>
|
||||
public class ServerEntityNetworkManager : IEntityNetworkManager
|
||||
public class ServerEntityNetworkManager : IServerEntityNetworkManager
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IServerNetManager _networkManager;
|
||||
[Dependency] private readonly IGameTiming _gameTiming;
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<NetworkComponentMessage> ReceivedComponentMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<EntitySystemMessage> ReceivedSystemMessage;
|
||||
public event EventHandler<object> ReceivedSystemMessage;
|
||||
|
||||
private readonly PriorityQueue<MsgEntity> _queue = new PriorityQueue<MsgEntity>(new MessageSequenceComparer());
|
||||
|
||||
private readonly Dictionary<IPlayerSession, uint> _lastProcessedSequencesCmd =
|
||||
new Dictionary<IPlayerSession, uint>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
{
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
while (_queue.Count != 0 && _queue.Peek().SourceTick <= _gameTiming.CurTick)
|
||||
{
|
||||
DispatchEntityNetworkMessage(_queue.Take());
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetLastMessageSequence(IPlayerSession session)
|
||||
{
|
||||
return _lastProcessedSequencesCmd[session];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendComponentNetworkMessage(INetChannel channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
public void SendComponentNetworkMessage(INetChannel channel, IEntity entity, IComponent component,
|
||||
ComponentMessage message)
|
||||
{
|
||||
if (_networkManager.IsClient)
|
||||
return;
|
||||
@@ -42,6 +73,7 @@ namespace Robust.Server.GameObjects
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = component.NetID.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
//Send the message
|
||||
if (channel == null)
|
||||
@@ -56,6 +88,7 @@ namespace Robust.Server.GameObjects
|
||||
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
newMsg.Type = EntityMessageType.SystemMessage;
|
||||
newMsg.SystemMessage = message;
|
||||
newMsg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
_networkManager.ServerSendToAll(newMsg);
|
||||
}
|
||||
@@ -66,12 +99,43 @@ namespace Robust.Server.GameObjects
|
||||
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
newMsg.Type = EntityMessageType.SystemMessage;
|
||||
newMsg.SystemMessage = message;
|
||||
newMsg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
_networkManager.ServerSendMessage(newMsg, targetConnection);
|
||||
}
|
||||
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
var msgT = message.SourceTick;
|
||||
var cT = _gameTiming.CurTick;
|
||||
Logger.Info($"{msgT}, {cT}");
|
||||
if (msgT <= cT)
|
||||
{
|
||||
if (msgT < cT)
|
||||
{
|
||||
Logger.WarningS("net.ent", "Got late MsgEntity! Diff: {0}, msgT: {2}, cT: {3}, player: {1}",
|
||||
(int) msgT.Value - (int) cT.Value, message.MsgChannel.SessionId, msgT, cT);
|
||||
}
|
||||
|
||||
DispatchEntityNetworkMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Add(message);
|
||||
}
|
||||
|
||||
private void DispatchEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
var player = _playerManager.GetSessionByChannel(message.MsgChannel);
|
||||
|
||||
if (message.Sequence != 0)
|
||||
{
|
||||
if (_lastProcessedSequencesCmd[player] < message.Sequence)
|
||||
{
|
||||
_lastProcessedSequencesCmd[player] = message.Sequence;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.Type)
|
||||
{
|
||||
case EntityMessageType.ComponentMessage:
|
||||
@@ -79,9 +143,37 @@ namespace Robust.Server.GameObjects
|
||||
return;
|
||||
|
||||
case EntityMessageType.SystemMessage:
|
||||
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(player), msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs args)
|
||||
{
|
||||
switch (args.NewStatus)
|
||||
{
|
||||
case SessionStatus.Connected:
|
||||
_lastProcessedSequencesCmd.Add(args.Session, 0);
|
||||
break;
|
||||
|
||||
case SessionStatus.Disconnected:
|
||||
_lastProcessedSequencesCmd.Remove(args.Session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MessageSequenceComparer : IComparer<MsgEntity>
|
||||
{
|
||||
public int Compare(MsgEntity x, MsgEntity y)
|
||||
{
|
||||
DebugTools.AssertNotNull(x);
|
||||
DebugTools.AssertNotNull(y);
|
||||
|
||||
return y.Sequence.CompareTo(x.Sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.GameState;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
@@ -30,6 +33,8 @@ namespace Robust.Server.GameStates
|
||||
[Dependency] private readonly IServerNetManager _networkManager;
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
[Dependency] private readonly IEntitySystemManager _systemManager;
|
||||
[Dependency] private readonly IServerEntityNetworkManager _entityNetworkManager;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
@@ -106,6 +111,8 @@ namespace Robust.Server.GameStates
|
||||
return;
|
||||
}
|
||||
|
||||
var inputSystem = _systemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var oldestAck = GameTick.MaxValue;
|
||||
|
||||
|
||||
@@ -132,7 +139,10 @@ namespace Robust.Server.GameStates
|
||||
|
||||
|
||||
// lastAck varies with each client based on lag and such, we can't just make 1 global state and send it to everyone
|
||||
var state = new GameState(lastAck, _gameTiming.CurTick, entStates, playerStates, deletions, mapData);
|
||||
var lastInputCommand = inputSystem.GetLastInputCommand(session);
|
||||
var lastSystemMessage = _entityNetworkManager.GetLastMessageSequence(session);
|
||||
var state = new GameState(lastAck, _gameTiming.CurTick,
|
||||
Math.Max(lastInputCommand, lastSystemMessage), entStates, playerStates, deletions, mapData);
|
||||
if (lastAck < oldestAck)
|
||||
{
|
||||
oldestAck = lastAck;
|
||||
@@ -151,7 +161,6 @@ namespace Robust.Server.GameStates
|
||||
{
|
||||
_ackedStates[channel.ConnectionId] = _gameTiming.CurTick;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// keep the deletion history buffers clean
|
||||
@@ -162,6 +171,5 @@ namespace Robust.Server.GameStates
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Interfaces.GameObjects
|
||||
{
|
||||
public interface IServerEntityNetworkManager : IEntityNetworkManager
|
||||
{
|
||||
uint GetLastMessageSequence(IPlayerSession session);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ namespace Robust.Server.Interfaces.Player
|
||||
{
|
||||
public interface IPlayerSession : ICommonSession
|
||||
{
|
||||
IEntity AttachedEntity { get; }
|
||||
EntityUid? AttachedEntityUid { get; }
|
||||
INetChannel ConnectedClient { get; }
|
||||
DateTime ConnectedTime { get; }
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Robust.Server
|
||||
IoCManager.Register<IConsoleShell, ConsoleShell>();
|
||||
IoCManager.Register<IEntityManager, ServerEntityManager>();
|
||||
IoCManager.Register<IEntityNetworkManager, ServerEntityNetworkManager>();
|
||||
IoCManager.Register<IServerEntityNetworkManager, ServerEntityNetworkManager>();
|
||||
IoCManager.Register<IMapLoader, MapLoader>();
|
||||
IoCManager.Register<IPauseManager, PauseManager>();
|
||||
IoCManager.Register<IPlacementManager, PlacementManager>();
|
||||
|
||||
@@ -235,6 +235,8 @@ namespace Robust.Shared.GameObjects.Components
|
||||
public class CollisionEnabledEvent : EntitySystemMessage
|
||||
{
|
||||
public bool Value { get; }
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
|
||||
public CollisionEnabledEvent(EntityUid uid, bool value)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
@@ -21,8 +22,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="source"></param>
|
||||
/// <param name="subscriber">Subscriber that owns the handler.</param>
|
||||
/// <param name="eventHandler">Delegate that handles the event.</param>
|
||||
void SubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber, EntityEventHandler<T> eventHandler)
|
||||
where T : EntityEventArgs;
|
||||
void SubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber,
|
||||
EntityEventHandler<T> eventHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes all event handlers of a given type.
|
||||
@@ -30,15 +31,16 @@ namespace Robust.Shared.GameObjects
|
||||
/// <typeparam name="T">Event type being unsubscribed from.</typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="subscriber">Subscriber that owns the handlers.</param>
|
||||
void UnsubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber)
|
||||
where T : EntityEventArgs;
|
||||
void UnsubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber);
|
||||
|
||||
/// <summary>
|
||||
/// Immediately raises an event onto the bus.
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="toRaise">Event being raised.</param>
|
||||
void RaiseEvent(EventSource source, EntityEventArgs toRaise);
|
||||
void RaiseEvent(EventSource source, object toRaise);
|
||||
|
||||
void RaiseEvent<T>(EventSource source, T toRaise);
|
||||
|
||||
/// <summary>
|
||||
/// Queues an event to be raised at a later time.
|
||||
@@ -53,8 +55,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <typeparam name="T">Event type being waited for.</typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
Task<T> AwaitEvent<T>(EventSource source)
|
||||
where T : EntityEventArgs;
|
||||
Task<T> AwaitEvent<T>(EventSource source);
|
||||
|
||||
/// <summary>
|
||||
/// Waits for an event to be raised. You do not have to subscribe to the event.
|
||||
@@ -63,8 +64,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="source"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<T> AwaitEvent<T>(EventSource source, CancellationToken cancellationToken)
|
||||
where T : EntityEventArgs;
|
||||
Task<T> AwaitEvent<T>(EventSource source, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes all event handlers for a given subscriber.
|
||||
@@ -95,19 +95,19 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
internal class EntityEventBus : IEntityEventBus
|
||||
{
|
||||
private delegate void EventHandler(EntityEventArgs ev);
|
||||
private delegate void EventHandler(object ev);
|
||||
|
||||
private readonly Dictionary<Type, List<(EventSource mask, Delegate originalHandler, EventHandler callback)>> _eventSubscriptions
|
||||
= new Dictionary<Type, List<(EventSource mask, Delegate originalHandler, EventHandler callback)>>();
|
||||
private readonly Dictionary<Type, List<Registration>> _eventSubscriptions
|
||||
= new Dictionary<Type, List<Registration>>();
|
||||
|
||||
private readonly Dictionary<IEntityEventSubscriber, Dictionary<Type, (EventSource source, Delegate originalHandler, EventHandler handler)>> _inverseEventSubscriptions
|
||||
= new Dictionary<IEntityEventSubscriber, Dictionary<Type, (EventSource source, Delegate originalHandler, EventHandler handler)>>();
|
||||
private readonly Dictionary<IEntityEventSubscriber, Dictionary<Type, Registration>> _inverseEventSubscriptions
|
||||
= new Dictionary<IEntityEventSubscriber, Dictionary<Type, Registration>>();
|
||||
|
||||
private readonly Queue<(EventSource source, EntityEventArgs args)> _eventQueue = new Queue<(EventSource source, EntityEventArgs args)>();
|
||||
private readonly Queue<(EventSource source, object args)> _eventQueue = new Queue<(EventSource source, object args)>();
|
||||
|
||||
private readonly Dictionary<Type, (EventSource, CancellationTokenRegistration, TaskCompletionSource<EntityEventArgs>)>
|
||||
private readonly Dictionary<Type, (EventSource, CancellationTokenRegistration, TaskCompletionSource<object>)>
|
||||
_awaitingMessages
|
||||
= new Dictionary<Type, (EventSource, CancellationTokenRegistration, TaskCompletionSource<EntityEventArgs>)>();
|
||||
= new Dictionary<Type, (EventSource, CancellationTokenRegistration, TaskCompletionSource<object>)>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnsubscribeEvents(IEntityEventSubscriber subscriber)
|
||||
@@ -137,7 +137,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber, EntityEventHandler<T> eventHandler)
|
||||
where T : EntityEventArgs
|
||||
{
|
||||
if (source == EventSource.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(source));
|
||||
@@ -149,15 +148,15 @@ namespace Robust.Shared.GameObjects
|
||||
throw new ArgumentNullException(nameof(subscriber));
|
||||
|
||||
var eventType = typeof(T);
|
||||
var subscriptionTuple = (source, eventHandler, (EventHandler) (ev => eventHandler((T) ev)));
|
||||
var subscriptionTuple = new Registration(source, eventHandler, ev => eventHandler((T) ev), eventHandler);
|
||||
if (!_eventSubscriptions.TryGetValue(eventType, out var subscriptions))
|
||||
_eventSubscriptions.Add(eventType, new List<(EventSource, Delegate, EventHandler)> {subscriptionTuple});
|
||||
else if (!subscriptions.Any(p => p.mask == source && p.originalHandler == (Delegate) eventHandler))
|
||||
_eventSubscriptions.Add(eventType, new List<Registration> {subscriptionTuple});
|
||||
else if (!subscriptions.Any(p => p.Mask == source && p.Original == (Delegate) eventHandler))
|
||||
subscriptions.Add(subscriptionTuple);
|
||||
|
||||
if (!_inverseEventSubscriptions.TryGetValue(subscriber, out var inverseSubscription))
|
||||
{
|
||||
inverseSubscription = new Dictionary<Type, (EventSource, Delegate, EventHandler)>
|
||||
inverseSubscription = new Dictionary<Type, Registration>
|
||||
{
|
||||
{eventType, subscriptionTuple}
|
||||
};
|
||||
@@ -176,7 +175,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnsubscribeEvent<T>(EventSource source, IEntityEventSubscriber subscriber)
|
||||
where T : EntityEventArgs
|
||||
{
|
||||
if (source == EventSource.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(source));
|
||||
@@ -188,11 +186,22 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (_inverseEventSubscriptions.TryGetValue(subscriber, out var inverse)
|
||||
&& inverse.TryGetValue(eventType, out var tuple))
|
||||
UnsubscribeEvent(source, eventType, tuple.originalHandler, tuple.handler, subscriber);
|
||||
UnsubscribeEvent(source, eventType, tuple.Original, tuple.Handler, subscriber);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RaiseEvent(EventSource source, EntityEventArgs toRaise)
|
||||
public void RaiseEvent(EventSource source, object toRaise)
|
||||
{
|
||||
if (source == EventSource.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(source));
|
||||
|
||||
if (toRaise == null)
|
||||
throw new ArgumentNullException(nameof(toRaise));
|
||||
|
||||
ProcessSingleEvent(source, toRaise);
|
||||
}
|
||||
|
||||
public void RaiseEvent<T>(EventSource source, T toRaise)
|
||||
{
|
||||
if (source == EventSource.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(source));
|
||||
@@ -217,14 +226,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> AwaitEvent<T>(EventSource source)
|
||||
where T : EntityEventArgs
|
||||
{
|
||||
return AwaitEvent<T>(source,default);
|
||||
return AwaitEvent<T>(source, default);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> AwaitEvent<T>(EventSource source, CancellationToken cancellationToken)
|
||||
where T : EntityEventArgs
|
||||
{
|
||||
if(source == EventSource.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(source));
|
||||
@@ -235,7 +242,7 @@ namespace Robust.Shared.GameObjects
|
||||
throw new InvalidOperationException("Cannot await the same message type twice at once.");
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<EntityEventArgs>();
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
CancellationTokenRegistration reg = default;
|
||||
if (cancellationToken != default)
|
||||
{
|
||||
@@ -247,7 +254,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
// Tiny trick so we can return T while the tcs is passed an EntitySystemMessage.
|
||||
async Task<T> DoCast(Task<EntityEventArgs> task)
|
||||
async Task<T> DoCast(Task<object> task)
|
||||
{
|
||||
return (T)await task;
|
||||
}
|
||||
@@ -258,7 +265,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void UnsubscribeEvent(EventSource source, Type eventType, Delegate originalHandler, EventHandler handler, IEntityEventSubscriber subscriber)
|
||||
{
|
||||
var tuple = (source, originalHandler, evh: handler);
|
||||
var tuple = new Registration(source, originalHandler, handler, originalHandler);
|
||||
if (_eventSubscriptions.TryGetValue(eventType, out var subscriptions) && subscriptions.Contains(tuple))
|
||||
subscriptions.Remove(tuple);
|
||||
|
||||
@@ -266,7 +273,7 @@ namespace Robust.Shared.GameObjects
|
||||
inverse.Remove(eventType);
|
||||
}
|
||||
|
||||
private void ProcessSingleEvent(EventSource source, EntityEventArgs eventArgs)
|
||||
private void ProcessSingleEvent(EventSource source, object eventArgs)
|
||||
{
|
||||
var eventType = eventArgs.GetType();
|
||||
|
||||
@@ -274,8 +281,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
foreach (var handler in subs)
|
||||
{
|
||||
if((handler.mask & source) != 0)
|
||||
handler.callback(eventArgs);
|
||||
if((handler.Mask & source) != 0)
|
||||
handler.Handler(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,12 +290,92 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var (mask, _, tcs) = awaiting;
|
||||
|
||||
if((source & mask) != 0)
|
||||
if ((source & mask) != 0)
|
||||
{
|
||||
tcs.TrySetResult(eventArgs);
|
||||
_awaitingMessages.Remove(eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSingleEvent<T>(EventSource source, T eventArgs)
|
||||
{
|
||||
var eventType = typeof(T);
|
||||
|
||||
if (_eventSubscriptions.TryGetValue(eventType, out var subs))
|
||||
{
|
||||
foreach (var (mask, originalHandler, _) in subs)
|
||||
{
|
||||
if ((mask & source) != 0)
|
||||
{
|
||||
var foo = (EntityEventHandler<T>) originalHandler;
|
||||
foo(eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_awaitingMessages.TryGetValue(eventType, out var awaiting))
|
||||
{
|
||||
var (mask1, _, tcs) = awaiting;
|
||||
|
||||
if ((source & mask1) != 0)
|
||||
{
|
||||
tcs.TrySetResult(eventArgs);
|
||||
_awaitingMessages.Remove(eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct Registration : IEquatable<Registration>
|
||||
{
|
||||
public readonly EventSource Mask;
|
||||
public readonly object EqualityToken;
|
||||
|
||||
public readonly Delegate Original;
|
||||
public readonly EventHandler Handler;
|
||||
|
||||
public Registration(EventSource mask, Delegate original, EventHandler handler, object equalityToken)
|
||||
{
|
||||
Mask = mask;
|
||||
Original = original;
|
||||
Handler = handler;
|
||||
EqualityToken = equalityToken;
|
||||
}
|
||||
|
||||
public bool Equals(Registration other)
|
||||
{
|
||||
return Mask == other.Mask && Equals(EqualityToken, other.EqualityToken);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Registration other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((int) Mask * 397) ^ (EqualityToken != null ? EqualityToken.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(Registration left, Registration right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Registration left, Registration right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public void Deconstruct(out EventSource mask, out Delegate originalHandler, out EventHandler handler)
|
||||
{
|
||||
mask = Mask;
|
||||
originalHandler = Original;
|
||||
handler = Handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
using System;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public interface IEntityEventSubscriber { }
|
||||
|
||||
public delegate void EntityEventHandler<in T>(T ev)
|
||||
where T : EntityEventArgs;
|
||||
public delegate void EntityEventHandler<in T>(T ev);
|
||||
|
||||
public delegate void EntitySessionEventHandler<in T>(T msg, EntitySessionEventArgs args);
|
||||
|
||||
public class EntityEventArgs : EventArgs { }
|
||||
|
||||
public readonly struct EntitySessionEventArgs
|
||||
{
|
||||
public EntitySessionEventArgs(ICommonSession senderSession)
|
||||
{
|
||||
SenderSession = senderSession;
|
||||
}
|
||||
|
||||
public ICommonSession SenderSession { get; }
|
||||
}
|
||||
|
||||
internal readonly struct EntitySessionMessage<T>
|
||||
{
|
||||
public EntitySessionMessage(EntitySessionEventArgs eventArgs, T message)
|
||||
{
|
||||
EventArgs = eventArgs;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public EntitySessionEventArgs EventArgs { get; }
|
||||
public T Message { get; }
|
||||
|
||||
public void Deconstruct(out EntitySessionEventArgs eventArgs, out T message)
|
||||
{
|
||||
eventArgs = EventArgs;
|
||||
message = Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public virtual void Update(float frameTime)
|
||||
{
|
||||
EntityNetworkManager.Update();
|
||||
EntitySystemManager.Update(frameTime);
|
||||
_eventBus.ProcessEventQueue();
|
||||
CullDeletedEntities();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -8,7 +9,9 @@ namespace Robust.Shared.GameObjects
|
||||
public sealed class EntityState
|
||||
{
|
||||
public EntityUid Uid { get; }
|
||||
[CanBeNull]
|
||||
public List<ComponentChanged> ComponentChanges { get; }
|
||||
[CanBeNull]
|
||||
public List<ComponentState> ComponentStates { get; }
|
||||
|
||||
public EntityState(EntityUid uid, List<ComponentChanged> changedComponents, List<ComponentState> componentStates)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -7,16 +6,5 @@ namespace Robust.Shared.GameObjects
|
||||
[Serializable, NetSerializable]
|
||||
public class EntitySystemMessage : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Remote network channel this message came from.
|
||||
/// If this is null, the message was raised locally.
|
||||
/// </summary>
|
||||
[field: NonSerialized]
|
||||
public INetChannel NetChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Entity this message is raised for.
|
||||
/// </summary>
|
||||
public EntityUid Owner { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
42
Robust.Shared/GameObjects/EventBusExt.cs
Normal file
42
Robust.Shared/GameObjects/EventBusExt.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public static class EventBusExt
|
||||
{
|
||||
public static void SubscribeSessionEvent<T>(this IEventBus eventBus, EventSource source,
|
||||
IEntityEventSubscriber subscriber, EntitySessionEventHandler<T> handler)
|
||||
{
|
||||
var wrapper = new HandlerWrapper<T>(handler);
|
||||
eventBus.SubscribeEvent<EntitySessionMessage<T>>(source, subscriber, wrapper.Invoke);
|
||||
}
|
||||
|
||||
private sealed class HandlerWrapper<T>
|
||||
{
|
||||
public HandlerWrapper(EntitySessionEventHandler<T> handler)
|
||||
{
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
public EntitySessionEventHandler<T> Handler { get; }
|
||||
|
||||
public void Invoke(EntitySessionMessage<T> msg)
|
||||
{
|
||||
Handler(msg.Message, msg.EventArgs);
|
||||
}
|
||||
|
||||
private bool Equals(HandlerWrapper<T> other)
|
||||
{
|
||||
return Handler.Equals(other.Handler);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is HandlerWrapper<T> other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Handler.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// This event is raised when a component message comes in from the network.
|
||||
/// </summary>
|
||||
event EventHandler<EntitySystemMessage> ReceivedSystemMessage;
|
||||
event EventHandler<object> ReceivedSystemMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes networking for this manager. This should only be called once.
|
||||
@@ -47,6 +47,11 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="message">Message that should be sent.</param>
|
||||
void SendSystemNetworkMessage(EntitySystemMessage message);
|
||||
|
||||
void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an Entity System Message to relevant System on a client.
|
||||
/// Server: Sends the message to the relevant systems of the client on <paramref name="channel"/>
|
||||
@@ -57,5 +62,10 @@ namespace Robust.Shared.GameObjects
|
||||
/// Thrown if called on the client.
|
||||
/// </exception>
|
||||
void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel);
|
||||
|
||||
/// <summary>
|
||||
/// Sends out queued messages based on current tick.
|
||||
/// </summary>
|
||||
void Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,24 @@ namespace Robust.Shared.GameObjects.Systems
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, handler);
|
||||
}
|
||||
|
||||
protected void SubscribeNetworkEvent<T>(EntitySessionEventHandler<T> handler)
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Network, this, handler);
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<T>(EntityEventHandler<T> handler)
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, handler);
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<T>(EntitySessionEventHandler<T> handler)
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(EventSource.Local, this, handler);
|
||||
}
|
||||
|
||||
protected void UnsubscribeNetworkEvent<T>()
|
||||
where T : EntitySystemMessage
|
||||
{
|
||||
|
||||
9
Robust.Shared/GameObjects/Systems/SharedInputSystem.cs
Normal file
9
Robust.Shared/GameObjects/Systems/SharedInputSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Robust.Shared.GameObjects.Systems
|
||||
{
|
||||
public abstract class SharedInputSystem : EntitySystem
|
||||
{
|
||||
public abstract ICommandBindMapping BindMap { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameStates
|
||||
@@ -25,10 +26,11 @@ namespace Robust.Shared.GameStates
|
||||
/// <summary>
|
||||
/// Constructor!
|
||||
/// </summary>
|
||||
public GameState(GameTick fromSequence, GameTick toSequence, List<EntityState> entities, List<PlayerState> players, List<EntityUid> deletions, GameStateMapData mapData)
|
||||
public GameState(GameTick fromSequence, GameTick toSequence, uint lastInput, List<EntityState> entities, List<PlayerState> players, List<EntityUid> deletions, GameStateMapData mapData)
|
||||
{
|
||||
FromSequence = fromSequence;
|
||||
ToSequence = toSequence;
|
||||
LastProcessedInput = lastInput;
|
||||
EntityStates = entities;
|
||||
PlayerStates = players;
|
||||
EntityDeletions = deletions;
|
||||
@@ -38,8 +40,12 @@ namespace Robust.Shared.GameStates
|
||||
public readonly GameTick FromSequence;
|
||||
public readonly GameTick ToSequence;
|
||||
|
||||
public readonly uint LastProcessedInput;
|
||||
|
||||
[CanBeNull]
|
||||
public readonly List<EntityState> EntityStates;
|
||||
public readonly List<PlayerState> PlayerStates;
|
||||
[CanBeNull]
|
||||
public readonly List<EntityUid> EntityDeletions;
|
||||
public readonly GameStateMapData MapData;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ namespace Robust.Shared.Input
|
||||
/// <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)
|
||||
StateInputCmdDelegate disabled = null, bool handle=true)
|
||||
{
|
||||
return new StateInputCmdHandler
|
||||
{
|
||||
EnabledDelegate = enabled,
|
||||
DisabledDelegate = disabled,
|
||||
Handle = handle,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +39,7 @@ namespace Robust.Shared.Input
|
||||
{
|
||||
public StateInputCmdDelegate EnabledDelegate;
|
||||
public StateInputCmdDelegate DisabledDelegate;
|
||||
public bool Handle { get; set; }
|
||||
|
||||
public override void Enabled(ICommonSession session)
|
||||
{
|
||||
@@ -58,10 +60,10 @@ namespace Robust.Shared.Input
|
||||
{
|
||||
case BoundKeyState.Up:
|
||||
Disabled(session);
|
||||
return true;
|
||||
return Handle;
|
||||
case BoundKeyState.Down:
|
||||
Enabled(session);
|
||||
return true;
|
||||
return Handle;
|
||||
}
|
||||
|
||||
//Client Sanitization: unknown key state, just ignore
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Shared.Input
|
||||
/// Abstract class that all Input Commands derive from.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public abstract class InputCmdMessage : EntitySystemMessage
|
||||
public abstract class InputCmdMessage : EntitySystemMessage, IComparable<InputCmdMessage>
|
||||
{
|
||||
/// <summary>
|
||||
/// Client tick this was created.
|
||||
@@ -22,6 +22,11 @@ namespace Robust.Shared.Input
|
||||
/// </summary>
|
||||
public KeyFunctionId InputFunctionId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sequence number of this input command.
|
||||
/// </summary>
|
||||
public uint InputSequence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="InputCmdMessage"/>.
|
||||
/// </summary>
|
||||
@@ -32,6 +37,13 @@ namespace Robust.Shared.Input
|
||||
Tick = tick;
|
||||
InputFunctionId = inputFunctionId;
|
||||
}
|
||||
|
||||
public int CompareTo(InputCmdMessage other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
if (ReferenceEquals(null, other)) return 1;
|
||||
return InputSequence.CompareTo(other.InputSequence);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -143,11 +155,12 @@ namespace Robust.Shared.Input
|
||||
/// Creates an instance of <see cref="FullInputCmdMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="tick">Client tick this was created.</param>
|
||||
/// <param name="inputSequence"></param>
|
||||
/// <param name="inputFunctionId">Function this command is changing.</param>
|
||||
/// <param name="state">New state of the Input Function.</param>
|
||||
/// <param name="coordinates">Local Coordinates of the pointer when the command was created.</param>
|
||||
/// <param name="screenCoordinates"></param>
|
||||
public FullInputCmdMessage(GameTick tick, KeyFunctionId inputFunctionId, BoundKeyState state, GridCoordinates coordinates, ScreenCoordinates screenCoordinates)
|
||||
public FullInputCmdMessage(GameTick tick, int inputSequence, KeyFunctionId inputFunctionId, BoundKeyState state, GridCoordinates coordinates, ScreenCoordinates screenCoordinates)
|
||||
: this(tick, inputFunctionId, state, coordinates, screenCoordinates, EntityUid.Invalid) { }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Interfaces.Timing
|
||||
@@ -101,5 +102,18 @@ namespace Robust.Shared.Interfaces.Timing
|
||||
/// Resets the real uptime of the server.
|
||||
/// </summary>
|
||||
void ResetRealTime();
|
||||
|
||||
bool IsFirstTimePredicted { get; }
|
||||
|
||||
void StartPastPrediction();
|
||||
void EndPastPrediction();
|
||||
|
||||
[MustUseReturnValue]
|
||||
PredictionGuard StartPastPredictionArea()
|
||||
{
|
||||
StartPastPrediction();
|
||||
|
||||
return new PredictionGuard(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Robust.Shared/Interfaces/Timing/PredictionGuard.cs
Normal file
19
Robust.Shared/Interfaces/Timing/PredictionGuard.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Interfaces.Timing
|
||||
{
|
||||
public readonly struct PredictionGuard : IDisposable
|
||||
{
|
||||
private readonly IGameTiming _gameTiming;
|
||||
|
||||
public PredictionGuard(IGameTiming gameTiming)
|
||||
{
|
||||
_gameTiming = gameTiming;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gameTiming.EndPastPrediction();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
@@ -25,10 +26,14 @@ namespace Robust.Shared.Network.Messages
|
||||
|
||||
public EntityUid EntityUid { get; set; }
|
||||
public uint NetId { get; set; }
|
||||
public uint Sequence { get; set; }
|
||||
public GameTick SourceTick { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
Type = (EntityMessageType)buffer.ReadByte();
|
||||
SourceTick = buffer.ReadGameTick();
|
||||
Sequence = buffer.ReadUInt32();
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
@@ -39,7 +44,6 @@ namespace Robust.Shared.Network.Messages
|
||||
using (var stream = new MemoryStream(buffer.ReadBytes(messageLength)))
|
||||
{
|
||||
SystemMessage = serializer.Deserialize<EntitySystemMessage>(stream);
|
||||
SystemMessage.NetChannel = MsgChannel;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -63,6 +67,8 @@ namespace Robust.Shared.Network.Messages
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
buffer.Write((byte)Type);
|
||||
buffer.Write(SourceTick);
|
||||
buffer.Write(Sequence);
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
|
||||
26
Robust.Shared/Network/Messages/MsgSetTickRate.cs
Normal file
26
Robust.Shared/Network/Messages/MsgSetTickRate.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
|
||||
namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
public class MsgSetTickRate : NetMessage
|
||||
{
|
||||
#region REQUIRED
|
||||
public static readonly MsgGroups GROUP = MsgGroups.Core;
|
||||
public static readonly string NAME = nameof(MsgSetTickRate);
|
||||
public MsgSetTickRate(INetChannel channel) : base(NAME, GROUP) { }
|
||||
#endregion
|
||||
|
||||
public byte NewTickRate { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
NewTickRate = buffer.ReadByte();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
{
|
||||
buffer.Write(NewTickRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
_config.RegisterCVar("net.sendbuffersize", 131071, CVar.ARCHIVE);
|
||||
_config.RegisterCVar("net.receivebuffersize", 131071, CVar.ARCHIVE);
|
||||
_config.RegisterCVar("net.verbose", false, CVar.ARCHIVE, NetVerboseChanged);
|
||||
|
||||
if (!isServer)
|
||||
{
|
||||
@@ -196,6 +197,14 @@ namespace Robust.Shared.Network
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private void NetVerboseChanged(bool on)
|
||||
{
|
||||
foreach (var peer in _netPeers)
|
||||
{
|
||||
peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage, on);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
DebugTools.Assert(IsServer);
|
||||
@@ -348,13 +357,16 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
var netConfig = new NetPeerConfiguration("SS14_NetTag");
|
||||
|
||||
// ping the client 4 times every second.
|
||||
netConfig.PingInterval = 0.25f;
|
||||
// ping the client once per second.
|
||||
netConfig.PingInterval = 1f;
|
||||
|
||||
netConfig.SendBufferSize = _config.GetCVar<int>("net.sendbuffersize");
|
||||
netConfig.ReceiveBufferSize = _config.GetCVar<int>("net.receivebuffersize");
|
||||
netConfig.MaximumHandshakeAttempts = 1;
|
||||
|
||||
var verbose = _config.GetCVar<bool>("net.verbose");
|
||||
netConfig.SetMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage, verbose);
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
netConfig.MaximumConnections = _config.GetCVar<int>("game.maxplayers");
|
||||
|
||||
@@ -2,6 +2,7 @@ using Lidgren.Network;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
@@ -44,5 +45,15 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
message.Write((int)entityUid);
|
||||
}
|
||||
|
||||
public static GameTick ReadGameTick(this NetIncomingMessage message)
|
||||
{
|
||||
return new GameTick(message.ReadUInt32());
|
||||
}
|
||||
|
||||
public static void Write(this NetOutgoingMessage message, GameTick tick)
|
||||
{
|
||||
message.Write(tick.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Players
|
||||
{
|
||||
@@ -11,5 +12,7 @@ namespace Robust.Shared.Players
|
||||
/// Status of the session.
|
||||
/// </summary>
|
||||
SessionStatus Status { get; set; }
|
||||
|
||||
IEntity AttachedEntity { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,16 @@ namespace Robust.Shared.Timing
|
||||
public static bool operator <(GameTick a, GameTick b) => a.Value < b.Value;
|
||||
public static bool operator <=(GameTick a, GameTick b) => a.Value <= b.Value;
|
||||
|
||||
public static GameTick operator +(GameTick a, uint b)
|
||||
{
|
||||
return new GameTick(a.Value + b);
|
||||
}
|
||||
|
||||
public static GameTick operator -(GameTick a, uint b)
|
||||
{
|
||||
return new GameTick(a.Value - b);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Timing
|
||||
{
|
||||
@@ -154,6 +155,24 @@ namespace Robust.Shared.Timing
|
||||
_lastRealTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
public bool IsFirstTimePredicted { get; private set; } = true;
|
||||
|
||||
public void StartPastPrediction()
|
||||
{
|
||||
// Don't allow recursive predictions.
|
||||
// Not sure if it's necessary yet and if not, great!
|
||||
DebugTools.Assert(IsFirstTimePredicted);
|
||||
|
||||
IsFirstTimePredicted = false;
|
||||
}
|
||||
|
||||
public void EndPastPrediction()
|
||||
{
|
||||
DebugTools.Assert(!IsFirstTimePredicted);
|
||||
|
||||
IsFirstTimePredicted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the average FPS of the last 50 real frame times.
|
||||
/// </summary>
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Robust.UnitTesting.Client.GameStates
|
||||
/// </summary>
|
||||
private static GameState GameStateFactory(uint from, uint to)
|
||||
{
|
||||
return new GameState(new GameTick(from), new GameTick(to), null, null, null, null);
|
||||
return new GameState(new GameTick(@from), new GameTick(to), 0, null, null, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user