From 02af42da30da7829fe93757a2b28ff6007d8e5f5 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Date: Wed, 14 Apr 2021 20:39:21 +0200 Subject: [PATCH] Refactors EntityManager to not do any networking. (#1695) * Refactors EntityManager to not do any networking. ServerEntityManager and ClientEntityManager now do the networking instead. * Rename property for "backwards compat." * Remove comented out code in robust server simulation --- Robust.Client/ClientIoC.cs | 3 +- .../GameObjects/ClientEntityManager.cs | 362 ++++++------------ .../GameObjects/ClientEntityNetworkManager.cs | 128 ------- Robust.Client/GameObjects/EntityManagerExt.cs | 2 +- .../GameObjects/EntitySystems/InputSystem.cs | 2 +- .../GameObjects/IClientEntityManager.cs | 6 +- .../IClientEntityManagerInternal.cs | 15 + .../GameStates/ClientGameStateManager.cs | 222 ++++++++++- .../GameObjects/IServerEntityManager.cs | 2 +- .../GameObjects/ServerEntityManager.cs | 278 ++++++++++---- .../GameObjects/ServerEntityNetworkManager.cs | 195 ---------- Robust.Server/ServerIoC.cs | 4 +- Robust.Shared/GameObjects/Component.cs | 2 + Robust.Shared/GameObjects/Entity.cs | 4 +- Robust.Shared/GameObjects/EntityManager.cs | 90 +++-- Robust.Shared/GameObjects/EntitySystem.cs | 7 +- Robust.Shared/GameObjects/IEntityManager.cs | 2 +- .../GameObjects/IEntityNetworkManager.cs | 5 - .../ServerEntityNetworkManagerTest.cs | 16 +- .../Server/RobustServerSimulation.cs | 7 +- 20 files changed, 649 insertions(+), 703 deletions(-) delete mode 100644 Robust.Client/GameObjects/ClientEntityNetworkManager.cs create mode 100644 Robust.Client/GameObjects/IClientEntityManagerInternal.cs delete mode 100644 Robust.Server/GameObjects/ServerEntityNetworkManager.cs diff --git a/Robust.Client/ClientIoC.cs b/Robust.Client/ClientIoC.cs index b69d014bd..a78ae09bc 100644 --- a/Robust.Client/ClientIoC.cs +++ b/Robust.Client/ClientIoC.cs @@ -54,7 +54,8 @@ namespace Robust.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); + IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Robust.Client/GameObjects/ClientEntityManager.cs b/Robust.Client/GameObjects/ClientEntityManager.cs index 5b802c4a0..dc0a3e7e9 100644 --- a/Robust.Client/GameObjects/ClientEntityManager.cs +++ b/Robust.Client/GameObjects/ClientEntityManager.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; +using Prometheus; +using Robust.Client.GameStates; using Robust.Shared.Exceptions; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Network.Messages; +using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Client.GameObjects @@ -13,288 +19,152 @@ namespace Robust.Client.GameObjects /// /// Manager for entities -- controls things like template loading and instantiation /// - public sealed class ClientEntityManager : EntityManager, IClientEntityManager + public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IComponentFactory _compFactory = default!; -#if EXCEPTION_TOLERANCE - [Dependency] private readonly IRuntimeLog _runtimeLog = default!; -#endif + [Dependency] private readonly IClientNetManager _networkManager = default!; + [Dependency] private readonly IClientGameStateManager _gameStateManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; - private int _nextClientEntityUid = EntityUid.ClientUid + 1; + protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1; - public override void Startup() + public override void Initialize() { - if (Started) - { - throw new InvalidOperationException("Startup() called multiple times"); - } + SetupNetworking(); + ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg); + ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg); - EntitySystemManager.Initialize(); - base.Startup(); - Started = true; + base.Initialize(); } - public List ApplyEntityStates(EntityState[]? curEntStates, IEnumerable? deletions, - EntityState[]? nextEntStates) + IEntity IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid? uid) { - var toApply = new Dictionary(); - var toInitialize = new List(); - var created = new List(); - deletions ??= new EntityUid[0]; + return base.CreateEntity(prototypeName, uid); + } - if (curEntStates != null && curEntStates.Length != 0) + void IClientEntityManagerInternal.InitializeEntity(IEntity entity) + { + EntityManager.InitializeEntity((Entity)entity); + } + + void IClientEntityManagerInternal.StartEntity(IEntity entity) + { + base.StartEntity((Entity)entity); + } + + #region IEntityNetworkManager impl + + public override IEntityNetworkManager EntityNetManager => this; + + /// + public event EventHandler? ReceivedComponentMessage; + + /// + public event EventHandler? ReceivedSystemMessage; + + private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer()); + private uint _incomingMsgSequence = 0; + + /// + public void SetupNetworking() + { + _networkManager.RegisterNetMessage(MsgEntity.NAME, HandleEntityNetworkMessage); + } + + public override void TickUpdate(float frameTime, Histogram? histogram) + { + using (histogram?.WithLabels("EntityNet").NewTimer()) { - foreach (var es in curEntStates) + while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick) { - //Known entities - if (Entities.TryGetValue(es.Uid, out var entity)) - { - toApply.Add(entity, (es, null)); - } - else //Unknown entities - { - var metaState = (MetaDataComponentState?) es.ComponentStates - ?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA); - if (metaState == null) - { - throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!"); - } - var newEntity = CreateEntity(metaState.PrototypeId, es.Uid); - toApply.Add(newEntity, (es, null)); - toInitialize.Add(newEntity); - created.Add(newEntity.Uid); - } + var (_, msg) = _queue.Take(); + // Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg); + DispatchMsgEntity(msg); } } - if (nextEntStates != null && nextEntStates.Length != 0) - { - foreach (var es in nextEntStates) - { - if (Entities.TryGetValue(es.Uid, out var entity)) - { - if (toApply.TryGetValue(entity, out var state)) - { - toApply[entity] = (state.Item1, es); - } - else - { - toApply[entity] = (null, es); - } - } - } - } - - // Make sure this is done after all entities have been instantiated. - foreach (var kvStates in toApply) - { - var ent = kvStates.Key; - var entity = (Entity) ent; - HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1, - kvStates.Value.Item2); - } - - foreach (var id in deletions) - { - DeleteEntity(id); - } - -#if EXCEPTION_TOLERANCE - HashSet brokenEnts = new HashSet(); -#endif - - foreach (var entity in toInitialize) - { -#if EXCEPTION_TOLERANCE - try - { -#endif - InitializeEntity(entity); -#if EXCEPTION_TOLERANCE - } - catch (Exception e) - { - Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}"); - brokenEnts.Add(entity); - } -#endif - } - - foreach (var entity in toInitialize) - { -#if EXCEPTION_TOLERANCE - if (brokenEnts.Contains(entity)) - continue; - - try - { -#endif - StartEntity(entity); -#if EXCEPTION_TOLERANCE - } - catch (Exception e) - { - Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}"); - brokenEnts.Add(entity); - } -#endif - } - - foreach (var entity in toInitialize) - { -#if EXCEPTION_TOLERANCE - if (brokenEnts.Contains(entity)) - continue; -#endif - } -#if EXCEPTION_TOLERANCE - foreach (var entity in brokenEnts) - { - entity.Delete(); - } -#endif - - return created; + base.TickUpdate(frameTime, histogram); } /// - public override IEntity CreateEntityUninitialized(string? prototypeName) + public void SendSystemNetworkMessage(EntityEventArgs message) { - return CreateEntity(prototypeName); + SendSystemNetworkMessage(message, default(uint)); + } + + public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence) + { + var msg = _networkManager.CreateNetMessage(); + msg.Type = EntityMessageType.SystemMessage; + msg.SystemMessage = message; + msg.SourceTick = _gameTiming.CurTick; + msg.Sequence = sequence; + + _networkManager.ClientSendMessage(msg); } /// - public override IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates) + public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel) { - var newEntity = CreateEntity(prototypeName, GenerateEntityUid()); - - if (TryGetEntity(coordinates.EntityId, out var entity)) - { - newEntity.Transform.AttachParent(entity); - newEntity.Transform.Coordinates = coordinates; - } - - return newEntity; + throw new NotSupportedException(); } /// - public override IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates) + public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message) { - var newEntity = CreateEntity(prototypeName, GenerateEntityUid()); - newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId)); - newEntity.Transform.WorldPosition = coordinates.Position; - return newEntity; + if (!component.NetID.HasValue) + throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component)); + + var msg = _networkManager.CreateNetMessage(); + msg.Type = EntityMessageType.ComponentMessage; + msg.EntityUid = entity.Uid; + msg.NetId = component.NetID.Value; + msg.ComponentMessage = message; + msg.SourceTick = _gameTiming.CurTick; + + _networkManager.ClientSendMessage(msg); } - /// - public override IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates) + private void HandleEntityNetworkMessage(MsgEntity message) { - var newEnt = CreateEntityUninitialized(protoName, coordinates); - InitializeAndStartEntity((Entity) newEnt); - return newEnt; - } - - /// - public override IEntity SpawnEntity(string? protoName, MapCoordinates coordinates) - { - var entity = CreateEntityUninitialized(protoName, coordinates); - InitializeAndStartEntity((Entity) entity); - return entity; - } - - protected override EntityUid GenerateEntityUid() - { - return new(_nextClientEntityUid++); - } - - private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState, - EntityState? nextState) - { - var compStateWork = new Dictionary(); - var entityUid = entity.Uid; - - if (curState?.ComponentChanges != null) + if (message.SourceTick <= _gameStateManager.CurServerTick) { - foreach (var compChange in curState.ComponentChanges) - { - if (compChange.Deleted) - { - if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp)) - { - compMan.RemoveComponent(entityUid, comp); - } - } - else - { - if (compMan.HasComponent(entityUid, compChange.NetID)) - continue; - - var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!); - newComp.Owner = entity; - compMan.AddComponent(entity, newComp, true); - } - } + DispatchMsgEntity(message); + return; } - if (curState?.ComponentStates != null) - { - foreach (var compState in curState.ComponentStates) - { - compStateWork[compState.NetID] = (compState, null); - } - } + // 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)); + } - if (nextState?.ComponentStates != null) + private void DispatchMsgEntity(MsgEntity message) + { + switch (message.Type) { - foreach (var compState in nextState.ComponentStates) - { - if (compStateWork.TryGetValue(compState.NetID, out var state)) - { - compStateWork[compState.NetID] = (state.curState, compState); - } - else - { - compStateWork[compState.NetID] = (null, compState); - } - } - } + case EntityMessageType.ComponentMessage: + ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message)); + return; - foreach (var (netId, (cur, next)) in compStateWork) - { - if (compMan.TryGetComponent(entityUid, netId, out var component)) - { - try - { - component.HandleComponentState(cur, next); - } - catch (Exception e) - { - var wrapper = new ComponentStateApplyException( - $"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e); -#if EXCEPTION_TOLERANCE - _runtimeLog.LogException(wrapper, "Component state apply"); -#else - throw wrapper; -#endif - } - } - else - { - // The component can be null here due to interp. - // Because the NEXT state will have a new component, but this one doesn't yet. - // That's fine though. - if (cur == null) - { - continue; - } - - var eUid = entityUid; - var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name; - DebugTools.Assert( - $"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}"); - } + case EntityMessageType.SystemMessage: + ReceivedSystemMessage?.Invoke(this, message.SystemMessage); + return; } } + + 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 } } diff --git a/Robust.Client/GameObjects/ClientEntityNetworkManager.cs b/Robust.Client/GameObjects/ClientEntityNetworkManager.cs deleted file mode 100644 index f7e56d1c8..000000000 --- a/Robust.Client/GameObjects/ClientEntityNetworkManager.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Client.GameStates; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Network; -using Robust.Shared.Network.Messages; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Robust.Client.GameObjects -{ - /// - /// The client implementation of the Entity Network Manager. - /// - public class ClientEntityNetworkManager : IEntityNetworkManager - { - [Dependency] private readonly IClientNetManager _networkManager = default!; - [Dependency] private readonly IClientGameStateManager _gameStateManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - - /// - public event EventHandler? ReceivedComponentMessage; - - /// - public event EventHandler? ReceivedSystemMessage; - - private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer()); - private uint _incomingMsgSequence = 0; - - /// - public void SetupNetworking() - { - _networkManager.RegisterNetMessage(MsgEntity.NAME, HandleEntityNetworkMessage); - } - - public void TickUpdate() - { - while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick) - { - var (_, msg) = _queue.Take(); - // Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg); - DispatchMsgEntity(msg); - } - } - - /// - public void SendSystemNetworkMessage(EntityEventArgs message) - { - SendSystemNetworkMessage(message, default(uint)); - } - - public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence) - { - var msg = _networkManager.CreateNetMessage(); - msg.Type = EntityMessageType.SystemMessage; - msg.SystemMessage = message; - msg.SourceTick = _gameTiming.CurTick; - msg.Sequence = sequence; - - _networkManager.ClientSendMessage(msg); - } - - /// - public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel) - { - throw new NotSupportedException(); - } - - /// - public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message) - { - if (!component.NetID.HasValue) - throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component)); - - var msg = _networkManager.CreateNetMessage(); - msg.Type = EntityMessageType.ComponentMessage; - 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; - } - - // 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 DispatchMsgEntity(MsgEntity message) - { - switch (message.Type) - { - case EntityMessageType.ComponentMessage: - ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message)); - return; - - case EntityMessageType.SystemMessage: - ReceivedSystemMessage?.Invoke(this, message.SystemMessage); - return; - } - } - - 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); - } - } - } -} diff --git a/Robust.Client/GameObjects/EntityManagerExt.cs b/Robust.Client/GameObjects/EntityManagerExt.cs index ff2799da9..6a3cb75ee 100644 --- a/Robust.Client/GameObjects/EntityManagerExt.cs +++ b/Robust.Client/GameObjects/EntityManagerExt.cs @@ -15,7 +15,7 @@ namespace Robust.Client.GameObjects DebugTools.AssertNotNull(localPlayer); var sequence = IoCManager.Resolve().SystemMessageDispatched(msg); - entityManager.EntityNetManager.SendSystemNetworkMessage(msg, sequence); + entityManager.EntityNetManager?.SendSystemNetworkMessage(msg, sequence); var eventArgs = new EntitySessionEventArgs(localPlayer!.Session); diff --git a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs index dfc36ce6f..6e95a56c1 100644 --- a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs @@ -97,7 +97,7 @@ namespace Robust.Client.GameObjects private void DispatchInputCommand(FullInputCmdMessage message) { _stateManager.InputCommandDispatched(message); - EntityNetworkManager.SendSystemNetworkMessage(message, message.InputSequence); + EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence); } public override void Initialize() diff --git a/Robust.Client/GameObjects/IClientEntityManager.cs b/Robust.Client/GameObjects/IClientEntityManager.cs index 191349d3d..11eb7d926 100644 --- a/Robust.Client/GameObjects/IClientEntityManager.cs +++ b/Robust.Client/GameObjects/IClientEntityManager.cs @@ -3,10 +3,8 @@ using Robust.Shared.GameObjects; namespace Robust.Client.GameObjects { - public interface IClientEntityManager : IEntityManager + public interface IClientEntityManager : IEntityManager, IEntityNetworkManager { - /// The list of new entities created. - List ApplyEntityStates(EntityState[]? curEntStates, IEnumerable? deletions, - EntityState[]? nextEntStates); + } } diff --git a/Robust.Client/GameObjects/IClientEntityManagerInternal.cs b/Robust.Client/GameObjects/IClientEntityManagerInternal.cs new file mode 100644 index 000000000..cb902b8b7 --- /dev/null +++ b/Robust.Client/GameObjects/IClientEntityManagerInternal.cs @@ -0,0 +1,15 @@ +using Robust.Shared.GameObjects; + +namespace Robust.Client.GameObjects +{ + internal interface IClientEntityManagerInternal : IClientEntityManager + { + // These methods are used by the Game State Manager. + + IEntity CreateEntity(string? prototypeName, EntityUid? uid = null); + + void InitializeEntity(IEntity entity); + + void StartEntity(IEntity entity); + } +} diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 58838090d..67bf8e8ce 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Robust.Client.GameObjects; using Robust.Client.Input; using Robust.Client.Map; @@ -10,6 +11,7 @@ using Robust.Shared.Network.Messages; using Robust.Client.Player; using Robust.Shared; using Robust.Shared.Configuration; +using Robust.Shared.Exceptions; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.Log; @@ -32,7 +34,8 @@ namespace Robust.Client.GameStates _pendingSystemMessages = new(); - [Dependency] private readonly IClientEntityManager _entities = default!; + [Dependency] private readonly IComponentFactory _compFactory = default!; + [Dependency] private readonly IClientEntityManagerInternal _entities = default!; [Dependency] private readonly IEntityLookup _lookup = default!; [Dependency] private readonly IPlayerManager _players = default!; [Dependency] private readonly IClientNetManager _network = default!; @@ -43,6 +46,9 @@ namespace Robust.Client.GameStates [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IComponentManager _componentManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; +#if EXCEPTION_TOLERANCE + [Dependency] private readonly IRuntimeLog _runtimeLog = default!; +#endif /// public int MinBufferSize => _processor.MinBufferSize; @@ -387,7 +393,7 @@ namespace Robust.Client.GameStates { _config.TickProcessMessages(); _mapManager.ApplyGameStatePre(curState.MapData); - var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions, + var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions, nextState?.EntityStates); _players.ApplyPlayerStates(curState.PlayerStates); _mapManager.ApplyGameStatePost(curState.MapData); @@ -395,6 +401,218 @@ namespace Robust.Client.GameStates GameStateApplied?.Invoke(new GameStateAppliedArgs(curState)); return createdEntities; } + + private List ApplyEntityStates(EntityState[]? curEntStates, IEnumerable? deletions, + EntityState[]? nextEntStates) + { + var toApply = new Dictionary(); + var toInitialize = new List(); + var created = new List(); + deletions ??= new EntityUid[0]; + + if (curEntStates != null && curEntStates.Length != 0) + { + foreach (var es in curEntStates) + { + //Known entities + if (_entities.TryGetEntity(es.Uid, out var entity)) + { + toApply.Add(entity, (es, null)); + } + else //Unknown entities + { + var metaState = (MetaDataComponentState?) es.ComponentStates + ?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA); + if (metaState == null) + { + throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!"); + } + var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid); + toApply.Add(newEntity, (es, null)); + toInitialize.Add(newEntity); + created.Add(newEntity.Uid); + } + } + } + + if (nextEntStates != null && nextEntStates.Length != 0) + { + foreach (var es in nextEntStates) + { + if (_entities.TryGetEntity(es.Uid, out var entity)) + { + if (toApply.TryGetValue(entity, out var state)) + { + toApply[entity] = (state.Item1, es); + } + else + { + toApply[entity] = (null, es); + } + } + } + } + + // Make sure this is done after all entities have been instantiated. + foreach (var kvStates in toApply) + { + var ent = kvStates.Key; + var entity = (Entity) ent; + HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1, + kvStates.Value.Item2); + } + + foreach (var id in deletions) + { + _entities.DeleteEntity(id); + } + +#if EXCEPTION_TOLERANCE + HashSet brokenEnts = new HashSet(); +#endif + + foreach (var entity in toInitialize) + { +#if EXCEPTION_TOLERANCE + try + { +#endif + _entities.InitializeEntity(entity); +#if EXCEPTION_TOLERANCE + } + catch (Exception e) + { + Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}"); + brokenEnts.Add(entity); + } +#endif + } + + foreach (var entity in toInitialize) + { +#if EXCEPTION_TOLERANCE + if (brokenEnts.Contains(entity)) + continue; + + try + { +#endif + _entities.StartEntity(entity); +#if EXCEPTION_TOLERANCE + } + catch (Exception e) + { + Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}"); + brokenEnts.Add(entity); + } +#endif + } + + foreach (var entity in toInitialize) + { +#if EXCEPTION_TOLERANCE + if (brokenEnts.Contains(entity)) + continue; +#endif + } +#if EXCEPTION_TOLERANCE + foreach (var entity in brokenEnts) + { + entity.Delete(); + } +#endif + + return created; + } + + private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState, + EntityState? nextState) + { + var compStateWork = new Dictionary(); + var entityUid = entity.Uid; + + if (curState?.ComponentChanges != null) + { + foreach (var compChange in curState.ComponentChanges) + { + if (compChange.Deleted) + { + if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp)) + { + compMan.RemoveComponent(entityUid, comp); + } + } + else + { + if (compMan.HasComponent(entityUid, compChange.NetID)) + continue; + + var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!); + newComp.Owner = entity; + compMan.AddComponent(entity, newComp, true); + } + } + } + + if (curState?.ComponentStates != null) + { + foreach (var compState in curState.ComponentStates) + { + compStateWork[compState.NetID] = (compState, null); + } + } + + if (nextState?.ComponentStates != null) + { + foreach (var compState in nextState.ComponentStates) + { + if (compStateWork.TryGetValue(compState.NetID, out var state)) + { + compStateWork[compState.NetID] = (state.curState, compState); + } + else + { + compStateWork[compState.NetID] = (null, compState); + } + } + } + + foreach (var (netId, (cur, next)) in compStateWork) + { + if (compMan.TryGetComponent(entityUid, netId, out var component)) + { + try + { + component.HandleComponentState(cur, next); + } + catch (Exception e) + { + var wrapper = new ComponentStateApplyException( + $"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e); +#if EXCEPTION_TOLERANCE + _runtimeLog.LogException(wrapper, "Component state apply"); +#else + throw wrapper; +#endif + } + } + else + { + // The component can be null here due to interp. + // Because the NEXT state will have a new component, but this one doesn't yet. + // That's fine though. + if (cur == null) + { + continue; + } + + var eUid = entityUid; + var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name; + DebugTools.Assert( + $"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}"); + } + } + } } public class GameStateAppliedArgs : EventArgs diff --git a/Robust.Server/GameObjects/IServerEntityManager.cs b/Robust.Server/GameObjects/IServerEntityManager.cs index b3e60188f..72b02a652 100644 --- a/Robust.Server/GameObjects/IServerEntityManager.cs +++ b/Robust.Server/GameObjects/IServerEntityManager.cs @@ -5,5 +5,5 @@ namespace Robust.Server.GameObjects /// /// Server side version of the . /// - public interface IServerEntityManager : IEntityManager { } + public interface IServerEntityManager : IEntityManager, IServerEntityNetworkManager { } } diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs index 39f784d67..575ef5837 100644 --- a/Robust.Server/GameObjects/ServerEntityManager.cs +++ b/Robust.Server/GameObjects/ServerEntityManager.cs @@ -1,11 +1,20 @@ using System; +using System.Collections.Generic; using JetBrains.Annotations; using Prometheus; +using Robust.Server.Player; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Network.Messages; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Robust.Server.GameObjects { @@ -19,77 +28,20 @@ namespace Robust.Server.GameObjects "robust_entities_count", "Amount of alive entities."); - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IPauseManager _pauseManager = default!; + [Dependency] private readonly IServerNetManager _networkManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; - private int _nextServerEntityUid = (int) EntityUid.FirstUid; + protected override int NextEntityUid { get; set; } = (int) EntityUid.FirstUid; - /// - public override IEntity CreateEntityUninitialized(string? prototypeName) + public override void Initialize() { - return CreateEntityServer(prototypeName); - } + SetupNetworking(); + ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg); + ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg); - /// - public override IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates) - { - var newEntity = CreateEntityServer(prototypeName); - - if (TryGetEntity(coordinates.EntityId, out var entity)) - { - newEntity.Transform.AttachParent(entity); - newEntity.Transform.Coordinates = coordinates; - } - - return newEntity; - } - - /// - public override IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates) - { - var newEntity = CreateEntityServer(prototypeName); - newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId)); - newEntity.Transform.WorldPosition = coordinates.Position; - return newEntity; - } - - /// - public override IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates) - { - if (!coordinates.IsValid(this)) - throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}."); - - var entity = CreateEntityUninitialized(protoName, coordinates); - - InitializeAndStartEntity((Entity) entity); - - if (_pauseManager.IsMapInitialized(coordinates.GetMapId(this))) entity.RunMapInit(); - - return entity; - } - - /// - public override IEntity SpawnEntity(string? protoName, MapCoordinates coordinates) - { - var entity = CreateEntityUninitialized(protoName, coordinates); - InitializeAndStartEntity((Entity) entity); - return entity; - } - - /// - public override void Startup() - { - EntitySystemManager.Initialize(); - base.Startup(); - Started = true; - } - - /// - public override void TickUpdate(float frameTime, Histogram? histogram) - { - base.TickUpdate(frameTime, histogram); - - EntitiesCount.Set(AllEntities.Count); + base.Initialize(); } IEntity IServerEntityManagerInternal.AllocEntity(string? prototypeName, EntityUid? uid) @@ -112,15 +64,9 @@ namespace Robust.Server.GameObjects StartEntity((Entity) entity); } - /// - protected override EntityUid GenerateEntityUid() + private protected override Entity CreateEntity(string? prototypeName, EntityUid? uid = null) { - return new(_nextServerEntityUid++); - } - - private Entity CreateEntityServer(string? prototypeName) - { - var entity = CreateEntity(prototypeName); + var entity = base.CreateEntity(prototypeName, uid); if (prototypeName != null) { @@ -141,5 +87,187 @@ namespace Robust.Server.GameObjects return entity; } + + #region IEntityNetworkManager impl + + public override IEntityNetworkManager EntityNetManager => this; + + /// + public event EventHandler? ReceivedComponentMessage; + + /// + public event EventHandler? ReceivedSystemMessage; + + private readonly PriorityQueue _queue = new(new MessageSequenceComparer()); + + private readonly Dictionary _lastProcessedSequencesCmd = + new(); + + private bool _logLateMsgs; + + /// + public void SetupNetworking() + { + _networkManager.RegisterNetMessage(MsgEntity.NAME, HandleEntityNetworkMessage); + + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + + _configurationManager.OnValueChanged(CVars.NetLogLateMsg, b => _logLateMsgs = b, true); + } + + /// + public override void TickUpdate(float frameTime, Histogram? histogram) + { + using (histogram?.WithLabels("EntityNet").NewTimer()) + { + while (_queue.Count != 0 && _queue.Peek().SourceTick <= _gameTiming.CurTick) + { + DispatchEntityNetworkMessage(_queue.Take()); + } + } + + base.TickUpdate(frameTime, histogram); + + EntitiesCount.Set(AllEntities.Count); + } + + public uint GetLastMessageSequence(IPlayerSession session) + { + return _lastProcessedSequencesCmd[session]; + } + + /// + public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, + ComponentMessage message) + { + if (_networkManager.IsClient) + return; + + if (!component.NetID.HasValue) + throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component)); + + var msg = _networkManager.CreateNetMessage(); + msg.Type = EntityMessageType.ComponentMessage; + msg.EntityUid = entity.Uid; + msg.NetId = component.NetID.Value; + msg.ComponentMessage = message; + msg.SourceTick = _gameTiming.CurTick; + + // Logger.DebugS("net.ent", "Sending: {0}", msg); + + //Send the message + if (channel == null) + _networkManager.ServerSendToAll(msg); + else + _networkManager.ServerSendMessage(msg, channel); + } + + /// + public void SendSystemNetworkMessage(EntityEventArgs message) + { + var newMsg = _networkManager.CreateNetMessage(); + newMsg.Type = EntityMessageType.SystemMessage; + newMsg.SystemMessage = message; + newMsg.SourceTick = _gameTiming.CurTick; + + _networkManager.ServerSendToAll(newMsg); + } + + /// + public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection) + { + var newMsg = _networkManager.CreateNetMessage(); + 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; + + if (msgT <= cT) + { + if (msgT < cT && _logLateMsgs) + { + Logger.WarningS("net.ent", "Got late MsgEntity! Diff: {0}, msgT: {2}, cT: {3}, player: {1}", + (int) msgT.Value - (int) cT.Value, message.MsgChannel.UserName, msgT, cT); + } + + DispatchEntityNetworkMessage(message); + return; + } + + _queue.Add(message); + } + + private void DispatchEntityNetworkMessage(MsgEntity message) + { + // Don't try to retrieve the session if the client disconnected + if (!message.MsgChannel.IsConnected) + { + return; + } + + 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: + ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player)); + return; + + case EntityMessageType.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; + } + } + + internal sealed class MessageSequenceComparer : IComparer + { + public int Compare(MsgEntity? x, MsgEntity? y) + { + DebugTools.AssertNotNull(x); + DebugTools.AssertNotNull(y); + + var cmp = y!.SourceTick.CompareTo(x!.SourceTick); + if (cmp != 0) + { + return cmp; + } + + return y.Sequence.CompareTo(x.Sequence); + } + } + + #endregion } } diff --git a/Robust.Server/GameObjects/ServerEntityNetworkManager.cs b/Robust.Server/GameObjects/ServerEntityNetworkManager.cs deleted file mode 100644 index 2ae03b0f9..000000000 --- a/Robust.Server/GameObjects/ServerEntityNetworkManager.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Server.Player; -using Robust.Shared; -using Robust.Shared.Configuration; -using Robust.Shared.Enums; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Network; -using Robust.Shared.Network.Messages; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Robust.Server.GameObjects -{ - /// - /// The server implementation of the Entity Network Manager. - /// - public class ServerEntityNetworkManager : IServerEntityNetworkManager - { - [Dependency] private readonly IServerNetManager _networkManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - - /// - public event EventHandler? ReceivedComponentMessage; - - /// - public event EventHandler? ReceivedSystemMessage; - - private readonly PriorityQueue _queue = new(new MessageSequenceComparer()); - - private readonly Dictionary _lastProcessedSequencesCmd = - new(); - - private bool _logLateMsgs; - - /// - public void SetupNetworking() - { - _networkManager.RegisterNetMessage(MsgEntity.NAME, HandleEntityNetworkMessage); - - _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; - - _configurationManager.OnValueChanged(CVars.NetLogLateMsg, b => _logLateMsgs = b, true); - } - - public void TickUpdate() - { - while (_queue.Count != 0 && _queue.Peek().SourceTick <= _gameTiming.CurTick) - { - DispatchEntityNetworkMessage(_queue.Take()); - } - } - - public uint GetLastMessageSequence(IPlayerSession session) - { - return _lastProcessedSequencesCmd[session]; - } - - /// - public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, - ComponentMessage message) - { - if (_networkManager.IsClient) - return; - - if (!component.NetID.HasValue) - throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component)); - - var msg = _networkManager.CreateNetMessage(); - msg.Type = EntityMessageType.ComponentMessage; - msg.EntityUid = entity.Uid; - msg.NetId = component.NetID.Value; - msg.ComponentMessage = message; - msg.SourceTick = _gameTiming.CurTick; - - // Logger.DebugS("net.ent", "Sending: {0}", msg); - - //Send the message - if (channel == null) - _networkManager.ServerSendToAll(msg); - else - _networkManager.ServerSendMessage(msg, channel); - } - - /// - public void SendSystemNetworkMessage(EntityEventArgs message) - { - var newMsg = _networkManager.CreateNetMessage(); - newMsg.Type = EntityMessageType.SystemMessage; - newMsg.SystemMessage = message; - newMsg.SourceTick = _gameTiming.CurTick; - - _networkManager.ServerSendToAll(newMsg); - } - - /// - public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection) - { - var newMsg = _networkManager.CreateNetMessage(); - 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; - - if (msgT <= cT) - { - if (msgT < cT && _logLateMsgs) - { - Logger.WarningS("net.ent", "Got late MsgEntity! Diff: {0}, msgT: {2}, cT: {3}, player: {1}", - (int) msgT.Value - (int) cT.Value, message.MsgChannel.UserName, msgT, cT); - } - - DispatchEntityNetworkMessage(message); - return; - } - - _queue.Add(message); - } - - private void DispatchEntityNetworkMessage(MsgEntity message) - { - // Don't try to retrieve the session if the client disconnected - if (!message.MsgChannel.IsConnected) - { - return; - } - - 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: - ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message, player)); - return; - - case EntityMessageType.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; - } - } - - internal sealed class MessageSequenceComparer : IComparer - { - public int Compare(MsgEntity? x, MsgEntity? y) - { - DebugTools.AssertNotNull(x); - DebugTools.AssertNotNull(y); - - var cmp = y!.SourceTick.CompareTo(x!.SourceTick); - if (cmp != 0) - { - return cmp; - } - - return y.Sequence.CompareTo(x.Sequence); - } - } - } -} diff --git a/Robust.Server/ServerIoC.cs b/Robust.Server/ServerIoC.cs index b271cb503..cb0461c74 100644 --- a/Robust.Server/ServerIoC.cs +++ b/Robust.Server/ServerIoC.cs @@ -46,8 +46,8 @@ namespace Robust.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); + IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Robust.Shared/GameObjects/Component.cs b/Robust.Shared/GameObjects/Component.cs index 712307728..06b0e685f 100644 --- a/Robust.Shared/GameObjects/Component.cs +++ b/Robust.Shared/GameObjects/Component.cs @@ -191,6 +191,7 @@ namespace Robust.Shared.GameObjects /// This is an alias of 'Owner.SendMessage(this, message);' /// /// Message to send. + [Obsolete("Component Messages are deprecated, use Entity Events instead.")] protected void SendMessage(ComponentMessage message) { Owner.SendMessage(this, message); @@ -202,6 +203,7 @@ namespace Robust.Shared.GameObjects /// /// Message to send. /// Network channel to send the message over. If null, broadcast to all channels. + [Obsolete("Component Messages are deprecated, use Entity Events instead.")] protected void SendNetworkMessage(ComponentMessage message, INetChannel? channel = null) { Owner.SendNetworkMessage(this, message, channel); diff --git a/Robust.Shared/GameObjects/Entity.cs b/Robust.Shared/GameObjects/Entity.cs index d3314cb87..645ffdc98 100644 --- a/Robust.Shared/GameObjects/Entity.cs +++ b/Robust.Shared/GameObjects/Entity.cs @@ -187,6 +187,7 @@ namespace Robust.Shared.GameObjects #region Component Messaging /// + [Obsolete("Component Messages are deprecated, use Entity Events instead.")] public void SendMessage(IComponent? owner, ComponentMessage message) { var components = EntityManager.ComponentManager.GetComponents(Uid); @@ -198,9 +199,10 @@ namespace Robust.Shared.GameObjects } /// + [Obsolete("Component Messages are deprecated, use Entity Events instead.")] public void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null) { - EntityManager.EntityNetManager.SendComponentNetworkMessage(channel, this, owner, message); + EntityManager.EntityNetManager?.SendComponentNetworkMessage(channel, this, owner, message); } #endregion Component Messaging diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 0d69cfd6e..e032f5c68 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -18,16 +18,17 @@ namespace Robust.Shared.GameObjects public delegate void EntityQueryCallback(IEntity entity); /// - public abstract class EntityManager : IEntityManager + public class EntityManager : IEntityManager { #region Dependencies - [IoC.Dependency] private readonly IEntityNetworkManager EntityNetworkManager = default!; [IoC.Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!; [IoC.Dependency] private readonly IComponentFactory ComponentFactory = default!; [IoC.Dependency] private readonly IComponentManager _componentManager = default!; + [IoC.Dependency] private readonly IMapManager _mapManager = default!; [IoC.Dependency] private readonly IGameTiming _gameTiming = default!; + [IoC.Dependency] private readonly IPauseManager _pauseManager = default!; #endregion Dependencies @@ -38,10 +39,10 @@ namespace Robust.Shared.GameObjects public IComponentManager ComponentManager => _componentManager; /// - public IEntityNetworkManager EntityNetManager => EntityNetworkManager; + public IEntitySystemManager EntitySysManager => EntitySystemManager; /// - public IEntitySystemManager EntitySysManager => EntitySystemManager; + public virtual IEntityNetworkManager? EntityNetManager => null; /// /// All entities currently stored in the manager. @@ -52,6 +53,8 @@ namespace Robust.Shared.GameObjects private EntityEventBus _eventBus = null!; + protected virtual int NextEntityUid { get; set; } = (int)EntityUid.FirstUid; + /// public IEventBus EventBus => _eventBus; @@ -73,15 +76,20 @@ namespace Robust.Shared.GameObjects { _eventBus = new EntityEventBus(this); - EntityNetworkManager.SetupNetworking(); - EntityNetworkManager.ReceivedComponentMessage += (sender, compMsg) => DispatchComponentMessage(compMsg); - EntityNetworkManager.ReceivedSystemMessage += (sender, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg); - ComponentManager.Initialize(); _componentManager.ComponentRemoved += (sender, args) => _eventBus.UnsubscribeEvents(args.Component); } - public virtual void Startup() {} + public virtual void Startup() + { + if (Started) + { + throw new InvalidOperationException("Startup() called multiple times"); + } + + EntitySystemManager.Initialize(); + Started = true; + } public virtual void Shutdown() { @@ -94,11 +102,6 @@ namespace Robust.Shared.GameObjects public virtual void TickUpdate(float frameTime, Histogram? histogram) { - using (histogram?.WithLabels("EntityNet").NewTimer()) - { - EntityNetworkManager.TickUpdate(); - } - using (histogram?.WithLabels("EntitySystems").NewTimer()) { EntitySystemManager.TickUpdate(frameTime); @@ -128,19 +131,56 @@ namespace Robust.Shared.GameObjects #region Entity Management /// - public abstract IEntity CreateEntityUninitialized(string? prototypeName); + public virtual IEntity CreateEntityUninitialized(string? prototypeName) + { + return CreateEntity(prototypeName); + } /// - public abstract IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates); + public virtual IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates) + { + var newEntity = CreateEntity(prototypeName); + + if (TryGetEntity(coordinates.EntityId, out var entity)) + { + newEntity.Transform.AttachParent(entity); + newEntity.Transform.Coordinates = coordinates; + } + + return newEntity; + } /// - public abstract IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates); + public virtual IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates) + { + var newEntity = CreateEntity(prototypeName); + newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId)); + newEntity.Transform.WorldPosition = coordinates.Position; + return newEntity; + } /// - public abstract IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates); + public virtual IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates) + { + if (!coordinates.IsValid(this)) + throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}."); + + var entity = CreateEntityUninitialized(protoName, coordinates); + + InitializeAndStartEntity((Entity) entity); + + if (_pauseManager.IsMapInitialized(coordinates.GetMapId(this))) entity.RunMapInit(); + + return entity; + } /// - public abstract IEntity SpawnEntity(string? protoName, MapCoordinates coordinates); + public virtual IEntity SpawnEntity(string? protoName, MapCoordinates coordinates) + { + var entity = CreateEntityUninitialized(protoName, coordinates); + InitializeAndStartEntity((Entity) entity); + return entity; + } /// /// Returns an entity by id @@ -327,7 +367,7 @@ namespace Robust.Shared.GameObjects /// /// Allocates an entity and loads components but does not do initialization. /// - private protected Entity CreateEntity(string? prototypeName, EntityUid? uid = null) + private protected virtual Entity CreateEntity(string? prototypeName, EntityUid? uid = null) { if (prototypeName == null) return AllocEntity(uid); @@ -401,7 +441,7 @@ namespace Robust.Shared.GameObjects #endregion Entity Management - private void DispatchComponentMessage(NetworkComponentMessage netMsg) + protected void DispatchComponentMessage(NetworkComponentMessage netMsg) { var compMsg = netMsg.Message; var compChannel = netMsg.Channel; @@ -424,9 +464,13 @@ namespace Robust.Shared.GameObjects } /// - /// Factory for generating a new EntityUid for an entity currently being created. + /// Factory for generating a new EntityUid for an entity currently being created. /// - protected abstract EntityUid GenerateEntityUid(); + /// + protected virtual EntityUid GenerateEntityUid() + { + return new(NextEntityUid++); + } } public enum EntityMessageType : byte diff --git a/Robust.Shared/GameObjects/EntitySystem.cs b/Robust.Shared/GameObjects/EntitySystem.cs index cb06c8557..76da667fd 100644 --- a/Robust.Shared/GameObjects/EntitySystem.cs +++ b/Robust.Shared/GameObjects/EntitySystem.cs @@ -21,7 +21,6 @@ namespace Robust.Shared.GameObjects [Dependency] protected readonly IEntityManager EntityManager = default!; [Dependency] protected readonly IComponentManager ComponentManager = default!; [Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!; - [Dependency] protected readonly IEntityNetworkManager EntityNetworkManager = default!; [Obsolete("You need to create and store the query yourself in a field.")] protected IEntityQuery? EntityQuery; @@ -103,12 +102,12 @@ namespace Robust.Shared.GameObjects protected void RaiseNetworkEvent(EntityEventArgs message) { - EntityNetworkManager.SendSystemNetworkMessage(message); + EntityManager.EntityNetManager?.SendSystemNetworkMessage(message); } protected void RaiseNetworkEvent(EntityEventArgs message, INetChannel channel) { - EntityNetworkManager.SendSystemNetworkMessage(message, channel); + EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, channel); } protected Task AwaitNetworkEvent(CancellationToken cancellationToken) @@ -136,7 +135,7 @@ namespace Robust.Shared.GameObjects { EntityManager.EventBus.RaiseLocalEvent(uid, args, broadcast); } - + #endregion #region Static Helpers diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs index 807a0cbd1..b95e9a67c 100644 --- a/Robust.Shared/GameObjects/IEntityManager.cs +++ b/Robust.Shared/GameObjects/IEntityManager.cs @@ -26,8 +26,8 @@ namespace Robust.Shared.GameObjects void FrameUpdate(float frameTime); IComponentManager ComponentManager { get; } - IEntityNetworkManager EntityNetManager { get; } IEntitySystemManager EntitySysManager { get; } + IEntityNetworkManager? EntityNetManager { get; } IEventBus EventBus { get; } #region Entity Management diff --git a/Robust.Shared/GameObjects/IEntityNetworkManager.cs b/Robust.Shared/GameObjects/IEntityNetworkManager.cs index cacff7005..37f3ee6ce 100644 --- a/Robust.Shared/GameObjects/IEntityNetworkManager.cs +++ b/Robust.Shared/GameObjects/IEntityNetworkManager.cs @@ -61,10 +61,5 @@ namespace Robust.Shared.GameObjects /// Thrown if called on the client. /// void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel); - - /// - /// Sends out queued messages based on current tick. - /// - void TickUpdate(); } } diff --git a/Robust.UnitTesting/Server/GameObjects/ServerEntityNetworkManagerTest.cs b/Robust.UnitTesting/Server/GameObjects/ServerEntityNetworkManagerTest.cs index 4e4f11656..d0c50bf4c 100644 --- a/Robust.UnitTesting/Server/GameObjects/ServerEntityNetworkManagerTest.cs +++ b/Robust.UnitTesting/Server/GameObjects/ServerEntityNetworkManagerTest.cs @@ -24,14 +24,16 @@ namespace Robust.UnitTesting.Server.GameObjects var msgE = new MsgEntity(channel) {Type = EntityMessageType.SystemMessage, SourceTick = tickB, Sequence = 7}; var msgF = new MsgEntity(channel) {Type = EntityMessageType.SystemMessage, SourceTick = tickB, Sequence = 4}; - var pq = new PriorityQueue(new ServerEntityNetworkManager.MessageSequenceComparer()); + var pq = new PriorityQueue(new ServerEntityManager.MessageSequenceComparer()) + { + msgA, + msgB, + msgC, + msgD, + msgE, + msgF + }; - pq.Add(msgA); - pq.Add(msgB); - pq.Add(msgC); - pq.Add(msgD); - pq.Add(msgE); - pq.Add(msgF); Assert.That(pq.Take(), Is.EqualTo(msgF)); Assert.That(pq.Take(), Is.EqualTo(msgE)); diff --git a/Robust.UnitTesting/Server/RobustServerSimulation.cs b/Robust.UnitTesting/Server/RobustServerSimulation.cs index 925e3c9fe..f6e18d855 100644 --- a/Robust.UnitTesting/Server/RobustServerSimulation.cs +++ b/Robust.UnitTesting/Server/RobustServerSimulation.cs @@ -175,8 +175,7 @@ namespace Robust.UnitTesting.Server container.RegisterInstance(new Mock().Object); // TODO: get timing working similar to RobustIntegrationTest //Tier 2: Simulation - container.Register(); - container.Register(); + container.Register(); container.Register(); container.Register(); container.Register(); @@ -188,10 +187,6 @@ namespace Robust.UnitTesting.Server container.Register(); container.RegisterInstance(new Mock().Object); // TODO: get timing working similar to RobustIntegrationTest - //Tier 3: Networking - //TODO: Try to remove these - container.RegisterInstance(new Mock().Object); - _diFactory?.Invoke(container); container.BuildGraph();