mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
* Warning fixes in Robust.Shared * Robust.Client warning fixes * Fix test failure Test failures were due to broken system registrations for the client RobustUnitTest. It was accidentally registering some server systems, which means DebugPhysicsSystem wasn't gettings its dependencies properly. Fixing this meant pulling half a dozen extra dependencies that client ContainerSystem and TransformSystem are supposed to have, but didn't.
254 lines
9.3 KiB
C#
254 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Prometheus;
|
|
using Robust.Client.GameStates;
|
|
using Robust.Client.Player;
|
|
using Robust.Client.Timing;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Network.Messages;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Replays;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Client.GameObjects
|
|
{
|
|
/// <summary>
|
|
/// Manager for entities -- controls things like template loading and instantiation
|
|
/// </summary>
|
|
public sealed partial class ClientEntityManager : EntityManager, IClientEntityManagerInternal
|
|
{
|
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
|
[Dependency] private readonly IClientGameTiming _gameTiming = default!;
|
|
[Dependency] private readonly IClientGameStateManager _stateMan = default!;
|
|
[Dependency] private readonly IBaseClient _client = default!;
|
|
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
SetupNetworking();
|
|
ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
|
|
|
|
base.Initialize();
|
|
}
|
|
|
|
public override void FlushEntities()
|
|
{
|
|
// Server doesn't network deletions on client shutdown so we need to
|
|
// manually clear these out or risk stale data getting used.
|
|
PendingNetEntityStates.Clear();
|
|
using var _ = _gameTiming.StartStateApplicationArea();
|
|
base.FlushEntities();
|
|
}
|
|
|
|
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, out MetaDataComponent metadata)
|
|
{
|
|
return base.CreateEntity(prototypeName, out metadata);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void DirtyEntity(EntityUid uid, MetaDataComponent? meta = null)
|
|
{
|
|
// Client only dirties during prediction
|
|
if (_gameTiming.InPrediction)
|
|
base.DirtyEntity(uid, meta);
|
|
}
|
|
|
|
public override void QueueDeleteEntity(EntityUid? uid)
|
|
{
|
|
if (uid == null || uid == EntityUid.Invalid)
|
|
return;
|
|
|
|
if (IsClientSide(uid.Value))
|
|
{
|
|
base.QueueDeleteEntity(uid);
|
|
return;
|
|
}
|
|
|
|
if (ShuttingDown)
|
|
return;
|
|
|
|
// Client-side entity deletion is not supported and will cause errors.
|
|
if (_client.RunLevel == ClientRunLevel.Connected || _client.RunLevel == ClientRunLevel.InGame)
|
|
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}");
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dirty(EntityUid uid, IComponent component, MetaDataComponent? meta = null)
|
|
{
|
|
Dirty(new Entity<IComponent>(uid, component), meta);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null)
|
|
{
|
|
// Client only dirties during prediction
|
|
if (_gameTiming.InPrediction)
|
|
base.Dirty(ent, meta);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
|
{
|
|
if (_gameTiming.InPrediction)
|
|
base.Dirty(ent, meta);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dirty<T1, T2, T3>(Entity<T1, T2, T3> ent, MetaDataComponent? meta = null)
|
|
{
|
|
if (_gameTiming.InPrediction)
|
|
base.Dirty(ent, meta);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dirty<T1, T2, T3, T4>(Entity<T1, T2, T3, T4> ent, MetaDataComponent? meta = null)
|
|
{
|
|
if (_gameTiming.InPrediction)
|
|
base.Dirty(ent, meta);
|
|
}
|
|
|
|
public override void RaisePredictiveEvent<T>(T msg)
|
|
{
|
|
var session = _playerManager.LocalSession;
|
|
DebugTools.AssertNotNull(session);
|
|
|
|
var sequence = _stateMan.SystemMessageDispatched(msg);
|
|
EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
|
|
|
|
DebugTools.Assert(!_stateMan.IsPredictionEnabled || _gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel != ClientRunLevel.Connected);
|
|
|
|
var eventArgs = new EntitySessionEventArgs(session!);
|
|
EventBus.RaiseEvent(EventSource.Local, msg);
|
|
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void RaiseSharedEvent<T>(T message, EntityUid? user = null)
|
|
{
|
|
if (user == null || user != _playerManager.LocalEntity || !_gameTiming.IsFirstTimePredicted)
|
|
return;
|
|
|
|
EventBus.RaiseEvent(EventSource.Local, ref message);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void RaiseSharedEvent<T>(T message, ICommonSession? user = null)
|
|
{
|
|
if (user == null || user != _playerManager.LocalSession || !_gameTiming.IsFirstTimePredicted)
|
|
return;
|
|
|
|
EventBus.RaiseEvent(EventSource.Local, ref message);
|
|
}
|
|
|
|
#region IEntityNetworkManager impl
|
|
|
|
public override IEntityNetworkManager EntityNetManager => this;
|
|
|
|
/// <inheritdoc />
|
|
public event EventHandler<object>? ReceivedSystemMessage;
|
|
|
|
private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer());
|
|
private uint _incomingMsgSequence = 0;
|
|
|
|
/// <inheritdoc />
|
|
public void SetupNetworking()
|
|
{
|
|
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
|
|
}
|
|
|
|
public override void TickUpdate(float frameTime, bool noPredictions, Histogram? histogram)
|
|
{
|
|
using (histogram?.WithLabels("EntityNet").NewTimer())
|
|
{
|
|
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameTiming.LastRealTick)
|
|
{
|
|
var (_, msg) = _queue.Take();
|
|
// Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg);
|
|
DispatchReceivedNetworkMsg(msg);
|
|
}
|
|
}
|
|
|
|
base.TickUpdate(frameTime, noPredictions, histogram);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SendSystemNetworkMessage(EntityEventArgs message, bool recordReplay = true)
|
|
{
|
|
SendSystemNetworkMessage(message, default(uint));
|
|
}
|
|
|
|
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
|
|
{
|
|
var msg = new MsgEntity();
|
|
msg.Type = EntityMessageType.SystemMessage;
|
|
msg.SystemMessage = message;
|
|
msg.SourceTick = _gameTiming.CurTick;
|
|
msg.Sequence = sequence;
|
|
|
|
_networkManager.ClientSendMessage(msg);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel? channel)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
private void HandleEntityNetworkMessage(MsgEntity message)
|
|
{
|
|
if (message.SourceTick <= _gameTiming.LastRealTick)
|
|
{
|
|
DispatchReceivedNetworkMsg(message);
|
|
return;
|
|
}
|
|
|
|
// MsgEntity is sent with ReliableOrdered so Lidgren guarantees ordering of incoming messages.
|
|
// We still need to store a sequence input number to ensure ordering remains consistent in
|
|
// the priority queue.
|
|
_queue.Add((++_incomingMsgSequence, message));
|
|
}
|
|
|
|
private void DispatchReceivedNetworkMsg(MsgEntity message)
|
|
{
|
|
switch (message.Type)
|
|
{
|
|
case EntityMessageType.SystemMessage:
|
|
|
|
// TODO REPLAYS handle late messages.
|
|
// If a message was received late, it will be recorded late here.
|
|
// Maybe process the replay to prevent late messages when playing back?
|
|
_replayRecording.RecordReplayMessage(message.SystemMessage);
|
|
|
|
DispatchReceivedNetworkMsg(message.SystemMessage);
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void DispatchReceivedNetworkMsg(EntityEventArgs msg)
|
|
{
|
|
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
|
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalSession!), 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)
|
|
{
|
|
var cmp = y.msg.SourceTick.CompareTo(x.msg.SourceTick);
|
|
if (cmp != 0)
|
|
{
|
|
return cmp;
|
|
}
|
|
|
|
return y.seq.CompareTo(x.seq);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|