diff --git a/Robust.Client/Console/Commands/AddCompCommand.cs b/Robust.Client/Console/Commands/AddCompCommand.cs index 49f259f3b..9c92e738d 100644 --- a/Robust.Client/Console/Commands/AddCompCommand.cs +++ b/Robust.Client/Console/Commands/AddCompCommand.cs @@ -22,7 +22,8 @@ namespace Robust.Client.Console.Commands return; } - var entity = EntityUid.Parse(args[0]); + var netEntity = NetEntity.Parse(args[0]); + var entity = _entityManager.GetEntity(netEntity); var componentName = args[1]; var component = (Component) _componentFactory.GetComponent(componentName); @@ -49,7 +50,8 @@ namespace Robust.Client.Console.Commands return; } - var entityUid = EntityUid.Parse(args[0]); + var netEntity = NetEntity.Parse(args[0]); + var entityUid = _entityManager.GetEntity(netEntity); var componentName = args[1]; var registration = _componentFactory.GetRegistration(componentName); diff --git a/Robust.Client/Console/Commands/Debug.cs b/Robust.Client/Console/Commands/Debug.cs index 6e8444de0..5e3c0dae3 100644 --- a/Robust.Client/Console/Commands/Debug.cs +++ b/Robust.Client/Console/Commands/Debug.cs @@ -296,6 +296,7 @@ namespace Robust.Client.Console.Commands internal sealed class SnapGridGetCell : LocalizedCommands { + [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IMapManager _map = default!; public override string Command => "sggcell"; @@ -310,7 +311,7 @@ namespace Robust.Client.Console.Commands string indices = args[1]; - if (!EntityUid.TryParse(args[0], out var gridUid)) + if (!NetEntity.TryParse(args[0], out var gridNet)) { shell.WriteError($"{args[0]} is not a valid entity UID."); return; @@ -322,7 +323,7 @@ namespace Robust.Client.Console.Commands return; } - if (_map.TryGetGrid(gridUid, out var grid)) + if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid)) { foreach (var entity in grid.GetAnchoredEntities(new Vector2i( int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture), @@ -430,6 +431,7 @@ namespace Robust.Client.Console.Commands internal sealed class GridTileCount : LocalizedCommands { + [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IMapManager _map = default!; public override string Command => "gridtc"; @@ -442,7 +444,8 @@ namespace Robust.Client.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var gridUid)) + if (!NetEntity.TryParse(args[0], out var gridUidNet) || + !_entManager.TryGetEntity(gridUidNet, out var gridUid)) { shell.WriteLine($"{args[0]} is not a valid entity UID."); return; diff --git a/Robust.Client/GameObjects/ClientEntityManager.Network.cs b/Robust.Client/GameObjects/ClientEntityManager.Network.cs new file mode 100644 index 000000000..28eac343e --- /dev/null +++ b/Robust.Client/GameObjects/ClientEntityManager.Network.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Robust.Client.GameObjects; + +public sealed partial class ClientEntityManager +{ + protected override NetEntity GenerateNetEntity() => new(NextNetworkId++ | NetEntity.ClientEntity); + + /// + /// If the client fails to resolve a NetEntity then during component state handling or the likes we + /// flag that comp state as requiring re-running if that NetEntity comes in. + /// + /// + internal readonly Dictionary> PendingNetEntityStates = new(); + + public override bool IsClientSide(EntityUid uid, MetaDataComponent? metadata = null) + { + // Can't log false because some content code relies on invalid UIDs. + if (!MetaQuery.Resolve(uid, ref metadata, false)) + return false; + + return metadata.NetEntity.IsClientSide(); + } + + public override EntityUid EnsureEntity(NetEntity nEntity, EntityUid callerEntity) + { + if (!nEntity.Valid) + { + return EntityUid.Invalid; + } + + if (NetEntityLookup.TryGetValue(nEntity, out var entity)) + { + return entity; + } + + // Flag the callerEntity to have their state potentially re-run later. + var pending = PendingNetEntityStates.GetOrNew(nEntity); + pending.Add((typeof(T), callerEntity)); + + return entity; + } + + public override EntityCoordinates EnsureCoordinates(NetCoordinates netCoordinates, EntityUid callerEntity) + { + var entity = EnsureEntity(netCoordinates.NetEntity, callerEntity); + return new EntityCoordinates(entity, netCoordinates.Position); + } +} diff --git a/Robust.Client/GameObjects/ClientEntityManager.cs b/Robust.Client/GameObjects/ClientEntityManager.cs index 3a7829b32..13014b490 100644 --- a/Robust.Client/GameObjects/ClientEntityManager.cs +++ b/Robust.Client/GameObjects/ClientEntityManager.cs @@ -16,7 +16,7 @@ namespace Robust.Client.GameObjects /// /// Manager for entities -- controls things like template loading and instantiation /// - public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal + public sealed partial class ClientEntityManager : EntityManager, IClientEntityManagerInternal { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IClientNetManager _networkManager = default!; @@ -25,8 +25,6 @@ namespace Robust.Client.GameObjects [Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IReplayRecordingManager _replayRecording = default!; - protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1; - public override void Initialize() { SetupNetworking(); @@ -37,13 +35,16 @@ namespace Robust.Client.GameObjects 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, EntityUid uid) + EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName) { - return base.CreateEntity(prototypeName, uid); + return base.CreateEntity(prototypeName); } void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta) @@ -66,7 +67,7 @@ namespace Robust.Client.GameObjects public override void QueueDeleteEntity(EntityUid uid) { - if (uid.IsClientSide()) + if (IsClientSide(uid)) { base.QueueDeleteEntity(uid); return; diff --git a/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs b/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs index 08acd32c0..4f2655bda 100644 --- a/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/AnimationPlayerSystem.cs @@ -128,7 +128,7 @@ namespace Robust.Client.GameObjects return; } - if (component.Owner.IsClientSide() || !animatedComp.NetSyncEnabled) + if (IsClientSide(component.Owner) || !animatedComp.NetSyncEnabled) continue; var reg = _compFact.GetRegistration(animatedComp); diff --git a/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs b/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs index 7e26c062d..32db78501 100644 --- a/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs @@ -81,9 +81,13 @@ public sealed class AudioSystem : SharedAudioSystem #region Event Handlers private void PlayAudioEntityHandler(PlayAudioEntityMessage ev) { - var stream = EntityManager.EntityExists(ev.EntityUid) - ? (PlayingStream?) Play(ev.FileName, ev.EntityUid, ev.FallbackCoordinates, ev.AudioParams, false) - : (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false); + var uid = GetEntity(ev.NetEntity); + var coords = GetCoordinates(ev.Coordinates); + var fallback = GetCoordinates(ev.FallbackCoordinates); + + var stream = EntityManager.EntityExists(uid) + ? (PlayingStream?) Play(ev.FileName, uid, fallback, ev.AudioParams, false) + : (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false); if (stream != null) stream.NetIdentifier = ev.Identifier; @@ -98,7 +102,10 @@ public sealed class AudioSystem : SharedAudioSystem private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev) { - var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false); + var coords = GetCoordinates(ev.Coordinates); + var fallback = GetCoordinates(ev.FallbackCoordinates); + + var stream = (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false); if (stream != null) stream.NetIdentifier = ev.Identifier; } @@ -383,8 +390,8 @@ public sealed class AudioSystem : SharedAudioSystem _replayRecording.RecordReplayMessage(new PlayAudioEntityMessage { FileName = filename, - EntityUid = entity, - FallbackCoordinates = fallbackCoordinates ?? default, + NetEntity = GetNetEntity(entity), + FallbackCoordinates = GetNetCoordinates(fallbackCoordinates) ?? default, AudioParams = audioParams ?? AudioParams.Default }); } @@ -437,8 +444,8 @@ public sealed class AudioSystem : SharedAudioSystem _replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage { FileName = filename, - Coordinates = coordinates, - FallbackCoordinates = fallbackCoordinates, + Coordinates = GetNetCoordinates(coordinates), + FallbackCoordinates = GetNetCoordinates(fallbackCoordinates), AudioParams = audioParams ?? AudioParams.Default }); } diff --git a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs index 713a0f4d6..8fe68c3e7 100644 --- a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs @@ -1,3 +1,4 @@ +using System; using Robust.Shared.Collections; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -5,13 +6,11 @@ using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Network; -using Robust.Shared.Serialization; using Robust.Shared.Utility; -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Robust.Shared.Physics.Components; +using Robust.Shared.Serialization; using static Robust.Shared.Containers.ContainerManagerComponent; namespace Robust.Client.GameObjects @@ -23,14 +22,20 @@ namespace Robust.Client.GameObjects [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!; [Dependency] private readonly PointLightSystem _lightSys = default!; + private EntityQuery _pointLightQuery; + private EntityQuery _spriteQuery; + private readonly HashSet _updateQueue = new(); - public readonly Dictionary ExpectedEntities = new(); + public readonly Dictionary ExpectedEntities = new(); public override void Initialize() { base.Initialize(); + _pointLightQuery = GetEntityQuery(); + _spriteQuery = GetEntityQuery(); + EntityManager.EntityInitialized += HandleEntityInitialized; SubscribeLocalEvent(HandleComponentState); @@ -43,20 +48,18 @@ namespace Robust.Client.GameObjects base.Shutdown(); } - protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing) + protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing) { - DebugTools.Assert(ExpectedEntities.TryGetValue(missing, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(missing)); + var netEntity = GetNetEntity(missing); + DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity)); } private void HandleEntityInitialized(EntityUid uid) { - if (!RemoveExpectedEntity(uid, out var container)) + if (!RemoveExpectedEntity(GetNetEntity(uid), out var container)) return; - if (container.Deleted) - return; - - container.Insert(uid); + container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid)); } private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args) @@ -64,23 +67,24 @@ namespace Robust.Client.GameObjects if (args.Current is not ContainerManagerComponentState cast) return; - var metaQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(uid); + var xform = TransformQuery.GetComponent(uid); // Delete now-gone containers. var toDelete = new ValueList(); foreach (var (id, container) in component.Containers) { if (cast.Containers.ContainsKey(id)) + { + DebugTools.Assert(cast.Containers[id].ContainerType == container.GetType().Name); continue; + } foreach (var entity in container.ContainedEntities.ToArray()) { container.Remove(entity, EntityManager, - xformQuery.GetComponent(entity), - metaQuery.GetComponent(entity), + TransformQuery.GetComponent(entity), + MetaQuery.GetComponent(entity), force: true, reparent: false); @@ -98,26 +102,32 @@ namespace Robust.Client.GameObjects // Add new containers and update existing contents. - foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.Containers.Values) + foreach (var (id, data) in cast.Containers) { if (!component.Containers.TryGetValue(id, out var container)) { - container = ContainerFactory(component, containerType, id); + var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType); + container = _dynFactory.CreateInstanceUnchecked(type!, inject:false); + container.Init(id, uid, component); component.Containers.Add(id, container); } - // sync show flag - container.ShowContents = showEnts; - container.OccludesLight = occludesLight; + DebugTools.Assert(container.ID == id); + container.ShowContents = data.ShowContents; + container.OccludesLight = data.OccludesLight; // Remove gone entities. var toRemove = new ValueList(); + + DebugTools.Assert(!container.Contains(EntityUid.Invalid)); + + var stateNetEnts = data.ContainedEntities; + var stateEnts = GetEntityArray(stateNetEnts); // No need to ensure entities. + foreach (var entity in container.ContainedEntities) { - if (!entityUids.Contains(entity)) - { + if (!stateEnts.Contains(entity)) toRemove.Add(entity); - } } foreach (var entity in toRemove) @@ -125,8 +135,8 @@ namespace Robust.Client.GameObjects container.Remove( entity, EntityManager, - xformQuery.GetComponent(entity), - metaQuery.GetComponent(entity), + TransformQuery.GetComponent(entity), + MetaQuery.GetComponent(entity), force: true, reparent: false); @@ -134,13 +144,11 @@ namespace Robust.Client.GameObjects } // Remove entities that were expected, but have been removed from the container. - var removedExpected = new ValueList(); - foreach (var entityUid in container.ExpectedEntities) + var removedExpected = new ValueList(); + foreach (var netEntity in container.ExpectedEntities) { - if (!entityUids.Contains(entityUid)) - { - removedExpected.Add(entityUid); - } + if (!stateNetEnts.Contains(netEntity)) + removedExpected.Add(netEntity); } foreach (var entityUid in removedExpected) @@ -149,14 +157,20 @@ namespace Robust.Client.GameObjects } // Add new entities. - foreach (var entity in entityUids) + for (var i = 0; i < stateNetEnts.Length; i++) { - if (!EntityManager.TryGetComponent(entity, out MetaDataComponent? meta)) + var entity = stateEnts[i]; + var netEnt = stateNetEnts[i]; + if (!entity.IsValid()) { - AddExpectedEntity(entity, container); + DebugTools.Assert(netEnt.IsValid()); + AddExpectedEntity(netEnt, container); continue; } + var meta = MetaData(entity); + DebugTools.Assert(meta.NetEntity == netEnt); + // If an entity is currently in the shadow realm, it means we probably left PVS and are now getting // back into range. We do not want to directly insert this entity, as IF the container and entity // transform states did not get sent simultaneously, the entity's transform will be modified by the @@ -166,18 +180,18 @@ namespace Robust.Client.GameObjects // containers/players. if ((meta.Flags & MetaDataFlags.Detached) != 0) { - AddExpectedEntity(entity, container); + AddExpectedEntity(netEnt, container); continue; } if (container.Contains(entity)) continue; - RemoveExpectedEntity(entity, out _); + RemoveExpectedEntity(netEnt, out _); container.Insert(entity, EntityManager, - xformQuery.GetComponent(entity), + TransformQuery.GetComponent(entity), xform, - metaQuery.GetComponent(entity), + MetaQuery.GetComponent(entity), force: true); DebugTools.Assert(container.Contains(entity)); @@ -198,7 +212,7 @@ namespace Robust.Client.GameObjects if (message.OldParent != null && message.OldParent.Value.IsValid()) return; - if (!RemoveExpectedEntity(message.Entity, out var container)) + if (!RemoveExpectedEntity(GetNetEntity(message.Entity), out var container)) return; if (xform.ParentUid != container.Owner) @@ -208,84 +222,69 @@ namespace Robust.Client.GameObjects return; } - if (container.Deleted) - return; - container.Insert(message.Entity, EntityManager); } - private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id) + public void AddExpectedEntity(NetEntity netEntity, BaseContainer container) { - var type = _serializer.FindSerializedType(typeof(IContainer), containerType); - if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found."); +#if DEBUG + var uid = GetEntity(netEntity); - var newContainer = _dynFactory.CreateInstanceUnchecked(type); - newContainer.ID = id; - newContainer.Manager = component; - return newContainer; - } + if (TryComp(uid, out var meta)) + { + DebugTools.Assert((meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached, + $"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container."); + } +#endif - public void AddExpectedEntity(EntityUid uid, IContainer container) - { - DebugTools.Assert(!TryComp(uid, out MetaDataComponent? meta) || - (meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached, - $"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container."); - - if (!ExpectedEntities.TryAdd(uid, container)) + if (!ExpectedEntities.TryAdd(netEntity, container)) { // It is possible that we were expecting this entity in one container, but it has now moved to another // container, and this entity's state is just being applied before the old container is getting updated. - var oldContainer = ExpectedEntities[uid]; - ExpectedEntities[uid] = container; - DebugTools.Assert(oldContainer.ExpectedEntities.Contains(uid), - $"Entity {ToPrettyString(uid)} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}"); - oldContainer.ExpectedEntities.Remove(uid); + var oldContainer = ExpectedEntities[netEntity]; + ExpectedEntities[netEntity] = container; + DebugTools.Assert(oldContainer.ExpectedEntities.Contains(netEntity), + $"Entity {netEntity} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}"); + oldContainer.ExpectedEntities.Remove(netEntity); } - DebugTools.Assert(!container.ExpectedEntities.Contains(uid), - $"Contained entity {ToPrettyString(uid)} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}"); - container.ExpectedEntities.Add(uid); + DebugTools.Assert(!container.ExpectedEntities.Contains(netEntity), + $"Contained entity {netEntity} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}"); + container.ExpectedEntities.Add(netEntity); } - public bool RemoveExpectedEntity(EntityUid uid, [NotNullWhen(true)] out IContainer? container) + public bool RemoveExpectedEntity(NetEntity netEntity, [NotNullWhen(true)] out BaseContainer? container) { - if (!ExpectedEntities.Remove(uid, out container)) + if (!ExpectedEntities.Remove(netEntity, out container)) return false; - DebugTools.Assert(container.ExpectedEntities.Contains(uid), - $"While removing expected contained entity {ToPrettyString(uid)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}"); - container.ExpectedEntities.Remove(uid); + DebugTools.Assert(container.ExpectedEntities.Contains(netEntity), + $"While removing expected contained entity {ToPrettyString(netEntity)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}"); + container.ExpectedEntities.Remove(netEntity); return true; } public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); - var pointQuery = EntityManager.GetEntityQuery(); - var spriteQuery = EntityManager.GetEntityQuery(); - var xformQuery = EntityManager.GetEntityQuery(); foreach (var toUpdate in _updateQueue) { if (Deleted(toUpdate)) continue; - UpdateEntityRecursively(toUpdate, xformQuery, pointQuery, spriteQuery); + UpdateEntityRecursively(toUpdate); } _updateQueue.Clear(); } - private void UpdateEntityRecursively( - EntityUid entity, - EntityQuery xformQuery, - EntityQuery pointQuery, - EntityQuery spriteQuery) + private void UpdateEntityRecursively(EntityUid entity) { // Recursively go up parents and containers to see whether both sprites and lights need to be occluded // Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container // occluded but this probably isn't a big perf issue. - var xform = xformQuery.GetComponent(entity); + var xform = TransformQuery.GetComponent(entity); var parent = xform.ParentUid; var child = entity; var spriteOccluded = false; @@ -293,7 +292,7 @@ namespace Robust.Client.GameObjects while (parent.IsValid() && (!spriteOccluded || !lightOccluded)) { - var parentXform = xformQuery.GetComponent(parent); + var parentXform = TransformQuery.GetComponent(parent); if (TryComp(parent, out var manager) && manager.TryGetContainer(child, out var container)) { spriteOccluded = spriteOccluded || !container.ShowContents; @@ -308,24 +307,21 @@ namespace Robust.Client.GameObjects // This is the CBT bit. // The issue is we need to go through the children and re-check whether they are or are not contained. // if they are contained then the occlusion values may need updating for all those children - UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded); + UpdateEntity(entity, xform, spriteOccluded, lightOccluded); } private void UpdateEntity( EntityUid entity, TransformComponent xform, - EntityQuery xformQuery, - EntityQuery pointQuery, - EntityQuery spriteQuery, bool spriteOccluded, bool lightOccluded) { - if (spriteQuery.TryGetComponent(entity, out var sprite)) + if (_spriteQuery.TryGetComponent(entity, out var sprite)) { sprite.ContainerOccluded = spriteOccluded; } - if (pointQuery.TryGetComponent(entity, out var light)) + if (_pointLightQuery.TryGetComponent(entity, out var light)) _lightSys.SetContainerOccluded(entity, lightOccluded, light); var childEnumerator = xform.ChildEnumerator; @@ -346,14 +342,14 @@ namespace Robust.Client.GameObjects childLightOccluded = childLightOccluded || container.OccludesLight; } - UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded); + UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded); } } else { while (childEnumerator.MoveNext(out var child)) { - UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded); + UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded); } } } diff --git a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs index a96cbd7f4..f4691116c 100644 --- a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs @@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects /// Arguments for this event. /// if true, current cmd state will not be checked or updated - use this for "replaying" an /// old input that was saved or buffered until further processing could be done - public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false) + public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, IFullInputCmdMessage message, bool replay = false) { #if DEBUG @@ -78,14 +78,27 @@ namespace Robust.Client.GameObjects continue; // local handlers can block sending over the network. - if (handler.HandleCmdMessage(session, message)) + if (handler.HandleCmdMessage(EntityManager, session, message)) { return true; } } // send it off to the server - DispatchInputCommand(message); + var clientMsg = (ClientFullInputCmdMessage)message; + var fullMsg = new FullInputCmdMessage( + clientMsg.Tick, + clientMsg.SubTick, + (int)clientMsg.InputSequence, + clientMsg.InputFunctionId, + clientMsg.State, + GetNetCoordinates(clientMsg.Coordinates), + clientMsg.ScreenCoordinates) + { + Uid = GetNetEntity(clientMsg.Uid) + }; + + DispatchInputCommand(clientMsg, fullMsg); return false; } @@ -93,7 +106,7 @@ namespace Robust.Client.GameObjects /// Handle a predicted input command. /// /// Input command to handle as predicted. - public void PredictInputCommand(FullInputCmdMessage inputCmd) + public void PredictInputCommand(IFullInputCmdMessage inputCmd) { DebugTools.AssertNotNull(_playerManager.LocalPlayer); @@ -103,15 +116,16 @@ namespace Robust.Client.GameObjects var session = _playerManager.LocalPlayer!.Session; foreach (var handler in BindRegistry.GetHandlers(keyFunc)) { - if (handler.HandleCmdMessage(session, inputCmd)) break; + if (handler.HandleCmdMessage(EntityManager, session, inputCmd)) + break; } Predicted = false; } - private void DispatchInputCommand(FullInputCmdMessage message) + private void DispatchInputCommand(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message) { - _stateManager.InputCommandDispatched(message); + _stateManager.InputCommandDispatched(clientMsg, message); EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence); } @@ -152,7 +166,7 @@ namespace Robust.Client.GameObjects var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction); var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state, - coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid); + GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid); HandleInputCommand(localPlayer.Session, keyFunction, message); } diff --git a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs index 55ea080d9..fb38f67a2 100644 --- a/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/UserInterfaceSystem.cs @@ -43,7 +43,8 @@ namespace Robust.Client.GameObjects private void MessageReceived(BoundUIWrapMessage ev) { - var uid = ev.Entity; + var uid = GetEntity(ev.Entity); + if (!TryComp(uid, out var cmp)) return; @@ -53,7 +54,7 @@ namespace Robust.Client.GameObjects if(_playerManager.LocalPlayer != null) message.Session = _playerManager.LocalPlayer.Session; - message.Entity = uid; + message.Entity = GetNetEntity(uid); message.UiKey = uiKey; // Raise as object so the correct type is used. @@ -125,7 +126,7 @@ namespace Robust.Client.GameObjects internal void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg) { - RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, msg, bui.UiKey)); + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey)); } } } diff --git a/Robust.Client/GameObjects/IClientEntityManagerInternal.cs b/Robust.Client/GameObjects/IClientEntityManagerInternal.cs index 9d93068f6..e3d3d3fbc 100644 --- a/Robust.Client/GameObjects/IClientEntityManagerInternal.cs +++ b/Robust.Client/GameObjects/IClientEntityManagerInternal.cs @@ -6,7 +6,7 @@ namespace Robust.Client.GameObjects { // These methods are used by the Game State Manager. - EntityUid CreateEntity(string? prototypeName, EntityUid uid = default); + EntityUid CreateEntity(string? prototypeName); void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null); diff --git a/Robust.Client/GameStates/ClientDirtySystem.cs b/Robust.Client/GameStates/ClientDirtySystem.cs index a7bf599e3..723188485 100644 --- a/Robust.Client/GameStates/ClientDirtySystem.cs +++ b/Robust.Client/GameStates/ClientDirtySystem.cs @@ -15,7 +15,7 @@ public sealed class ClientDirtySystem : EntitySystem { [Dependency] private readonly IClientGameTiming _timing = default!; [Dependency] private readonly IComponentFactory _compFact = default!; - + // Entities that have removed networked components // could pool the ushort sets, but predicted component changes are rare... soo... internal readonly Dictionary> RemovedComponents = new(); @@ -40,11 +40,11 @@ public sealed class ClientDirtySystem : EntitySystem private void OnTerminate(ref EntityTerminatingEvent ev) { - if (!_timing.InPrediction || ev.Entity.IsClientSide()) + if (!_timing.InPrediction || IsClientSide(ev.Entity)) return; // Client-side entity deletion is not supported and will cause errors. - Logger.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}"); + Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}"); } private void OnCompRemoved(RemovedComponentEventArgs args) @@ -52,8 +52,9 @@ public sealed class ClientDirtySystem : EntitySystem if (args.Terminating) return; + var uid = args.BaseArgs.Owner; var comp = args.BaseArgs.Component; - if (!_timing.InPrediction || comp.Owner.IsClientSide() || !comp.NetSyncEnabled) + if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid)) return; // Was this component added during prediction? If yes, then there is no need to re-add it when resetting. @@ -62,7 +63,7 @@ public sealed class ClientDirtySystem : EntitySystem var netId = _compFact.GetRegistration(comp).NetID; if (netId != null) - RemovedComponents.GetOrNew(comp.Owner).Add(netId.Value); + RemovedComponents.GetOrNew(uid).Add(netId.Value); } public void Reset() @@ -73,7 +74,7 @@ public sealed class ClientDirtySystem : EntitySystem private void OnEntityDirty(EntityUid e) { - if (_timing.InPrediction && !e.IsClientSide()) + if (_timing.InPrediction && !IsClientSide(e)) DirtyEntities.Add(e); } } diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 0865dff9f..991b8dfeb 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -47,8 +47,10 @@ namespace Robust.Client.GameStates = new(); // Game state dictionaries that get used every tick. - private readonly Dictionary _toApply = new(); - private readonly Dictionary _toCreate = new(); + private readonly Dictionary _toApply = new(); + private readonly Dictionary _toCreate = new(); + private readonly Dictionary _compStateWork = new(); + private readonly Dictionary> _pendingReapplyNetStates = new(); private uint _metaCompNetId; @@ -157,7 +159,7 @@ namespace Robust.Client.GameStates } } - public void InputCommandDispatched(FullInputCmdMessage message) + public void InputCommandDispatched(ClientFullInputCmdMessage clientMessage, FullInputCmdMessage message) { if (!IsPredictionEnabled) { @@ -201,7 +203,7 @@ namespace Robust.Client.GameStates public void UpdateFullRep(GameState state, bool cloneDelta = false) => _processor.UpdateFullRep(state, cloneDelta); - public Dictionary> GetFullRep() + public Dictionary> GetFullRep() => _processor.GetFullRep(); private void HandlePvsLeaveMessage(MsgStateLeavePvs message) @@ -210,7 +212,7 @@ namespace Robust.Client.GameStates PvsLeave?.Invoke(message); } - public void QueuePvsDetach(List entities, GameTick tick) + public void QueuePvsDetach(List entities, GameTick tick) { _processor.AddLeavePvsMessage(entities, tick); if (_replayRecording.IsRecording) @@ -298,7 +300,7 @@ namespace Robust.Client.GameStates _processor.UpdateFullRep(curState); } - IEnumerable createdEntities; + IEnumerable createdEntities; using (_prof.Group("ApplyGameState")) { if (_timing.LastProcessedTick < targetProcessedTick && nextState != null) @@ -323,7 +325,7 @@ namespace Robust.Client.GameStates catch (MissingMetadataException e) { // Something has gone wrong. Probably a missing meta-data component. Perhaps a full server state will fix it. - RequestFullState(e.Uid); + RequestFullState(e.NetEntity); throw; } #endif @@ -392,10 +394,10 @@ namespace Robust.Client.GameStates } } - public void RequestFullState(EntityUid? missingEntity = null) + public void RequestFullState(NetEntity? missingEntity = null) { _sawmill.Info("Requesting full server state"); - _network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? EntityUid.Invalid }); + _network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? NetEntity.Invalid }); _processor.RequestFullState(); } @@ -472,7 +474,7 @@ namespace Robust.Client.GameStates var countReset = 0; var system = _entitySystemManager.GetEntitySystem(); - var query = _entityManager.GetEntityQuery(); + var metaQuery = _entityManager.GetEntityQuery(); RemQueue toRemove = new(); // This is terrible, and I hate it. @@ -486,7 +488,8 @@ namespace Robust.Client.GameStates if (_sawmill.Level <= LogLevel.Debug) _sawmill.Debug($"Entity {entity} was made dirty."); - if (!_processor.TryGetLastServerStates(entity, out var last)) + if (!metaQuery.TryGetComponent(entity, out var meta) || + !_processor.TryGetLastServerStates(meta.NetEntity, out var last)) { // Entity was probably deleted on the server so do nothing. continue; @@ -564,7 +567,6 @@ namespace Robust.Client.GameStates } } - var meta = query.GetComponent(entity); DebugTools.Assert(meta.EntityLastModifiedTick > _timing.LastRealTick); meta.EntityLastModifiedTick = _timing.LastRealTick; } @@ -588,15 +590,16 @@ namespace Robust.Client.GameStates /// initial server state for any newly created entity. It does this by simply using the standard . /// - private void MergeImplicitData(IEnumerable createdEntities) + private void MergeImplicitData(IEnumerable createdEntities) { - var outputData = new Dictionary>(); + var outputData = new Dictionary>(); var bus = _entityManager.EventBus; - foreach (var createdEntity in createdEntities) + foreach (var netEntity in createdEntities) { + var createdEntity = _entityManager.GetEntity(netEntity); var compData = new Dictionary(); - outputData.Add(createdEntity, compData); + outputData.Add(netEntity, compData); foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity)) { @@ -617,7 +620,7 @@ namespace Robust.Client.GameStates _network.ClientSendMessage(new MsgStateAck() { Sequence = sequence }); } - public IEnumerable ApplyGameState(GameState curState, GameState? nextState) + public IEnumerable ApplyGameState(GameState curState, GameState? nextState) { using var _ = _timing.StartStateApplicationArea(); @@ -637,7 +640,7 @@ namespace Robust.Client.GameStates _config.TickProcessMessages(); } - (IEnumerable Created, List Detached) output; + (IEnumerable Created, List Detached) output; using (_prof.Group("Entity")) { output = ApplyEntityStates(curState, nextState); @@ -656,7 +659,7 @@ namespace Robust.Client.GameStates return output.Created; } - private (IEnumerable Created, List Detached) ApplyEntityStates(GameState curState, GameState? nextState) + private (IEnumerable Created, List Detached) ApplyEntityStates(GameState curState, GameState? nextState) { var metas = _entities.GetEntityQuery(); var xforms = _entities.GetEntityQuery(); @@ -665,6 +668,7 @@ namespace Robust.Client.GameStates var enteringPvs = 0; _toApply.Clear(); _toCreate.Clear(); + _pendingReapplyNetStates.Clear(); var curSpan = curState.EntityStates.Span; // Create new entities @@ -675,21 +679,40 @@ namespace Robust.Client.GameStates foreach (var es in curSpan) { - if (metas.HasComponent(es.Uid)) + if (_entityManager.TryGetEntity(es.NetEntity, out var nUid)) + { + DebugTools.Assert(_entityManager.EntityExists(nUid)); continue; + } count++; - var uid = es.Uid; var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State; if (metaState == null) - throw new MissingMetadataException(uid); + throw new MissingMetadataException(es.NetEntity); - _entities.CreateEntity(metaState.PrototypeId, uid); - _toCreate.Add(uid, es); + var uid = _entities.CreateEntity(metaState.PrototypeId); + _toCreate.Add(es.NetEntity, es); _toApply.Add(uid, (false, GameTick.Zero, es, null)); var newMeta = metas.GetComponent(uid); + + // Client creates a client-side net entity for the newly created entity. + // We need to clear this mapping before assigning the real net id. + // TODO NetEntity Jank: prevent the client from creating this in the first place. + _entityManager.ClearNetEntity(newMeta.NetEntity); + + _entityManager.SetNetEntity(uid, es.NetEntity, newMeta); newMeta.LastStateApplied = curState.ToSequence; + + // Check if there's any component states awaiting this entity. + if (_entityManager.PendingNetEntityStates.TryGetValue(es.NetEntity, out var value)) + { + foreach (var (type, owner) in value) + { + var pending = _pendingReapplyNetStates.GetOrNew(owner); + pending.Add(type); + } + } } _prof.WriteValue("Count", ProfData.Int32(count)); @@ -697,7 +720,9 @@ namespace Robust.Client.GameStates foreach (var es in curSpan) { - if (!metas.TryGetComponent(es.Uid, out var meta) || _toCreate.ContainsKey(es.Uid)) + var uid = _entityManager.GetEntity(es.NetEntity); + + if (!metas.TryGetComponent(uid, out var meta) || _toCreate.ContainsKey(es.NetEntity)) { continue; } @@ -714,7 +739,7 @@ namespace Robust.Client.GameStates continue; } - _toApply.Add(es.Uid, (isEnteringPvs, meta.LastStateApplied, es, null)); + _toApply.Add(uid, (isEnteringPvs, meta.LastStateApplied, es, null)); meta.LastStateApplied = curState.ToSequence; } @@ -728,22 +753,31 @@ namespace Robust.Client.GameStates { foreach (var es in nextState.EntityStates.Span) { - var uid = es.Uid; - - if (!metas.TryGetComponent(uid, out var meta)) + if (!_entityManager.TryGetEntity(es.NetEntity, out var uid)) continue; + DebugTools.Assert(metas.HasComponent(uid)); + // Does the next state actually have any future information about this entity that could be used for interpolation? if (es.EntityLastModified != nextState.ToSequence) continue; - if (_toApply.TryGetValue(uid, out var state)) - _toApply[uid] = (state.EnteringPvs, state.LastApplied, state.curState, es); + if (_toApply.TryGetValue(uid.Value, out var state)) + _toApply[uid.Value] = (state.EnteringPvs, state.LastApplied, state.curState, es); else - _toApply[uid] = (false, GameTick.Zero, null, es); + _toApply[uid.Value] = (false, GameTick.Zero, null, es); } } + // Check pending states and see if we need to force any entities to re-run component states. + foreach (var uid in _pendingReapplyNetStates.Keys) + { + if (_toApply.ContainsKey(uid)) + continue; + + _toApply[uid] = (false, GameTick.Zero, null, null); + } + var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs); // Apply entity states. @@ -766,6 +800,7 @@ namespace Robust.Client.GameStates if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs) queuedBroadphaseUpdates.Add((entity, xform)); } + _prof.WriteValue("Count", ProfData.Int32(_toApply.Count)); } @@ -829,37 +864,37 @@ namespace Robust.Client.GameStates // Construct hashset for set.Contains() checks. var entityStates = state.EntityStates.Span; - var stateEnts = new HashSet(entityStates.Length); + var stateEnts = new HashSet(); foreach (var entState in entityStates) { - stateEnts.Add(entState.Uid); + stateEnts.Add(entState.NetEntity); } - var metas = _entities.GetEntityQuery(); var xforms = _entities.GetEntityQuery(); var xformSys = _entitySystemManager.GetEntitySystem(); - var currentEnts = _entities.GetEntities(); var toDelete = new List(Math.Max(64, _entities.EntityCount - stateEnts.Count)); - foreach (var ent in currentEnts) + + // Client side entities won't need the transform, but that should always be a tiny minority of entities + var metaQuery = _entityManager.AllEntityQueryEnumerator(); + + while (metaQuery.MoveNext(out var ent, out var metadata, out var xform)) { - if (ent.IsClientSide()) + var netEnt = metadata.NetEntity; + if (metadata.NetEntity.IsClientSide()) { if (deleteClientEntities) toDelete.Add(ent); continue; } - if (stateEnts.Contains(ent) && metas.TryGetComponent(ent, out var meta)) + if (stateEnts.Contains(netEnt)) { - if (resetAllEntities || meta.LastStateApplied > state.ToSequence) - meta.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it? + if (resetAllEntities || metadata.LastStateApplied > state.ToSequence) + metadata.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it? continue; } - if (!xforms.TryGetComponent(ent, out var xform)) - continue; - // This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to // null. First we will detach the parent in order to reduce the number of broadphase/lookup updates. xformSys.DetachParentToNull(ent, xform); @@ -872,7 +907,7 @@ namespace Robust.Client.GameStates if (deleteClientChildren && !deleteClientEntities // don't add duplicates - && child.Value.IsClientSide()) + && _entities.IsClientSide(child.Value)) { toDelete.Add(child.Value); } @@ -887,7 +922,7 @@ namespace Robust.Client.GameStates } private void ProcessDeletions( - ReadOnlySpan delSpan, + ReadOnlySpan delSpan, EntityQuery xforms, EntityQuery metas, SharedTransformSystem xformSys) @@ -904,13 +939,19 @@ namespace Robust.Client.GameStates using var _ = _prof.Group("Deletion"); - foreach (var id in delSpan) + foreach (var netEntity in delSpan) { + // Don't worry about this for later. + _entityManager.PendingNetEntityStates.Remove(netEntity); + + if (!_entityManager.TryGetEntity(netEntity, out var id)) + continue; + if (!xforms.TryGetComponent(id, out var xform)) continue; // Already deleted? or never sent to us? // First, a single recursive map change - xformSys.DetachParentToNull(id, xform); + xformSys.DetachParentToNull(id.Value, xform); // Then detach all children. var childEnumerator = xform.ChildEnumerator; @@ -920,12 +961,12 @@ namespace Robust.Client.GameStates } // Finally, delete the entity. - _entities.DeleteEntity(id); + _entities.DeleteEntity(id.Value); } _prof.WriteValue("Count", ProfData.Int32(delSpan.Length)); } - public void DetachImmediate(List entities) + public void DetachImmediate(List entities) { var metas = _entities.GetEntityQuery(); var xforms = _entities.GetEntityQuery(); @@ -935,7 +976,7 @@ namespace Robust.Client.GameStates Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys); } - private List ProcessPvsDeparture( + private List ProcessPvsDeparture( GameTick toTick, EntityQuery metas, EntityQuery xforms, @@ -944,7 +985,7 @@ namespace Robust.Client.GameStates EntityLookupSystem lookupSys) { var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget); - var detached = new List(); + var detached = new List(); if (toDetach.Count == 0) return detached; @@ -966,16 +1007,18 @@ namespace Robust.Client.GameStates private void Detach(GameTick maxTick, GameTick? lastStateApplied, - List entities, + List entities, EntityQuery metas, EntityQuery xforms, SharedTransformSystem xformSys, ContainerSystem containerSys, EntityLookupSystem lookupSys, - List? detached = null) + List? detached = null) { - foreach (var ent in entities) + foreach (var netEntity in entities) { + var ent = _entityManager.GetEntity(netEntity); + if (!metas.TryGetComponent(ent, out var meta)) continue; @@ -1000,7 +1043,7 @@ namespace Robust.Client.GameStates // In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS. // In those situations, we need to add the entity back to the list of expected entities after detaching. - IContainer? container = null; + BaseContainer? container = null; if ((meta.Flags & MetaDataFlags.InContainer) != 0 && metas.TryGetComponent(xform.ParentUid, out var containerMeta) && (containerMeta.Flags & MetaDataFlags.Detached) == 0 && @@ -1014,35 +1057,38 @@ namespace Robust.Client.GameStates DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0); if (container != null) - containerSys.AddExpectedEntity(ent, container); + containerSys.AddExpectedEntity(netEntity, container); } - detached?.Add(ent); + detached?.Add(netEntity); } } - private void InitializeAndStart(Dictionary toCreate) + private void InitializeAndStart(Dictionary toCreate) { + var metaQuery = _entityManager.GetEntityQuery(); + #if EXCEPTION_TOLERANCE - HashSet brokenEnts = new HashSet(); + var brokenEnts = new List(); #endif using (_prof.Group("Initialize Entity")) { - foreach (var entity in toCreate.Keys) + foreach (var netEntity in toCreate.Keys) { + var entity = _entityManager.GetEntity(netEntity); #if EXCEPTION_TOLERANCE try { #endif - _entities.InitializeEntity(entity); + _entities.InitializeEntity(entity, metaQuery.GetComponent(entity)); #if EXCEPTION_TOLERANCE } catch (Exception e) { - _sawmill.Error($"Server entity threw in Init: ent={_entityManager.ToPrettyString(entity)}"); + _sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}"); _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}"); brokenEnts.Add(entity); - toCreate.Remove(entity); + toCreate.Remove(netEntity); } #endif } @@ -1050,8 +1096,9 @@ namespace Robust.Client.GameStates using (_prof.Group("Start Entity")) { - foreach (var entity in toCreate.Keys) + foreach (var netEntity in toCreate.Keys) { + var entity = _entityManager.GetEntity(netEntity); #if EXCEPTION_TOLERANCE try { @@ -1064,7 +1111,7 @@ namespace Robust.Client.GameStates _sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}"); _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}"); brokenEnts.Add(entity); - toCreate.Remove(entity); + toCreate.Remove(netEntity); } #endif } @@ -1081,8 +1128,9 @@ namespace Robust.Client.GameStates private void HandleEntityState(EntityUid uid, IEventBus bus, EntityState? curState, EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs) { - var size = (curState?.ComponentChanges.Span.Length ?? 0) + (nextState?.ComponentChanges.Span.Length ?? 0); - var compStateWork = new Dictionary(size); + _compStateWork.Clear(); + var meta = _entityManager.GetComponent(uid); + var netEntity = meta.NetEntity; // First remove any deleted components if (curState?.NetComponents != null) @@ -1107,7 +1155,7 @@ namespace Robust.Client.GameStates // // as to why we need to reset: because in the process of detaching to null-space, we will have dirtied // the entity. most notably, all entities will have been ejected from their containers. - foreach (var (id, state) in _processor.GetLastServerStates(uid)) + foreach (var (id, state) in _processor.GetLastServerStates(netEntity)) { if (!_entityManager.TryGetComponent(uid, id, out var comp)) { @@ -1117,7 +1165,7 @@ namespace Robust.Client.GameStates _entityManager.AddComponent(uid, newComp, true); } - compStateWork[id] = (comp, state, null); + _compStateWork[id] = (comp, state, null); } } else if (curState != null) @@ -1134,7 +1182,7 @@ namespace Robust.Client.GameStates else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero) continue; - compStateWork[compChange.NetID] = (comp, compChange.State, null); + _compStateWork[compChange.NetID] = (comp, compChange.State, null); } } @@ -1152,14 +1200,38 @@ namespace Robust.Client.GameStates continue; } - if (compStateWork.TryGetValue(compState.NetID, out var state)) - compStateWork[compState.NetID] = (comp, state.curState, compState.State); + if (_compStateWork.TryGetValue(compState.NetID, out var state)) + _compStateWork[compState.NetID] = (comp, state.curState, compState.State); else - compStateWork[compState.NetID] = (comp, null, compState.State); + _compStateWork[compState.NetID] = (comp, null, compState.State); } } - foreach (var (comp, cur, next) in compStateWork.Values) + // If we have a NetEntity we reference come in then apply their state. + if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes)) + { + var lastState = _processor.GetLastServerStates(netEntity); + + foreach (var type in reapplyTypes) + { + var compRef = _compFactory.GetRegistration(type); + var netId = compRef.NetID; + + if (netId == null) + continue; + + if (_compStateWork.ContainsKey(netId.Value) || + !_entityManager.TryGetComponent(uid, type, out var comp) || + !lastState.TryGetValue(netId.Value, out var lastCompState)) + { + continue; + } + + _compStateWork[netId.Value] = (comp, lastCompState, null); + } + } + + foreach (var (comp, cur, next) in _compStateWork.Values) { try { @@ -1180,6 +1252,7 @@ namespace Robust.Client.GameStates } #region Debug Commands + private bool TryParseUid(IConsoleShell shell, string[] args, out EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? meta) { if (args.Length != 1) @@ -1238,7 +1311,7 @@ namespace Robust.Client.GameStates var xform = _entities.GetComponent(uid); if (xform.ParentUid.IsValid()) { - IContainer? container = null; + BaseContainer? container = null; if ((meta.Flags & MetaDataFlags.InContainer) != 0 && _entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) && (containerMeta.Flags & MetaDataFlags.Detached) == 0) @@ -1249,7 +1322,7 @@ namespace Robust.Client.GameStates _entities.EntitySysManager.GetEntitySystem().DetachParentToNull(uid, xform); if (container != null) - containerSys.AddExpectedEntity(uid, container); + containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container); } } @@ -1266,18 +1339,21 @@ namespace Robust.Client.GameStates // If this is not a client-side entity, it also needs to be removed from the full-server state dictionary to // avoid errors. This has to be done recursively for all children. - void _recursiveRemoveState(TransformComponent xform, EntityQuery query) + void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery metaQuery, EntityQuery xformQuery) { - _processor._lastStateFullRep.Remove(xform.Owner); + _processor._lastStateFullRep.Remove(netEntity); foreach (var child in xform.ChildEntities) { - if (query.TryGetComponent(child, out var childXform)) - _recursiveRemoveState(childXform, query); + if (xformQuery.TryGetComponent(child, out var childXform) && + metaQuery.TryGetComponent(child, out var childMeta)) + { + _recursiveRemoveState(childMeta.NetEntity, childXform, metaQuery, xformQuery); + } } } - if (!uid.IsClientSide() && _entities.TryGetComponent(uid, out TransformComponent? xform)) - _recursiveRemoveState(xform, _entities.GetEntityQuery()); + if (!_entities.IsClientSide(uid) && _entities.TryGetComponent(uid, out TransformComponent? xform)) + _recursiveRemoveState(meta.NetEntity, xform, _entities.GetEntityQuery(), _entities.GetEntityQuery()); // Set ApplyingState to true to avoid logging errors about predicting the deletion of networked entities. using (_timing.StartStateApplicationArea()) @@ -1311,7 +1387,7 @@ namespace Robust.Client.GameStates meta.Flags &= ~MetaDataFlags.Detached; - if (!_processor.TryGetLastServerStates(uid, out var lastState)) + if (!_processor.TryGetLastServerStates(meta.NetEntity, out var lastState)) return; foreach (var (id, state) in lastState) @@ -1352,9 +1428,9 @@ namespace Robust.Client.GameStates public sealed class GameStateAppliedArgs : EventArgs { public GameState AppliedState { get; } - public readonly List Detached; + public readonly List Detached; - public GameStateAppliedArgs(GameState appliedState, List detached) + public GameStateAppliedArgs(GameState appliedState, List detached) { AppliedState = appliedState; Detached = detached; @@ -1363,12 +1439,12 @@ namespace Robust.Client.GameStates public sealed class MissingMetadataException : Exception { - public readonly EntityUid Uid; + public readonly NetEntity NetEntity; - public MissingMetadataException(EntityUid uid) - : base($"Server state is missing the metadata component for a new entity: {uid}.") + public MissingMetadataException(NetEntity netEntity) + : base($"Server state is missing the metadata component for a new entity: {netEntity}.") { - Uid = uid; + NetEntity = netEntity; } } } diff --git a/Robust.Client/GameStates/GameStateProcessor.cs b/Robust.Client/GameStates/GameStateProcessor.cs index a929bf9d2..694e99aaa 100644 --- a/Robust.Client/GameStates/GameStateProcessor.cs +++ b/Robust.Client/GameStates/GameStateProcessor.cs @@ -20,7 +20,7 @@ namespace Robust.Client.GameStates private readonly List _stateBuffer = new(); - private readonly Dictionary> _pvsDetachMessages = new(); + private readonly Dictionary> _pvsDetachMessages = new(); private ISawmill _logger = default!; private ISawmill _stateLogger = default!; @@ -44,7 +44,7 @@ namespace Robust.Client.GameStates /// /// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset. /// - internal readonly Dictionary> _lastStateFullRep + internal readonly Dictionary> _lastStateFullRep = new(); /// @@ -178,10 +178,10 @@ namespace Robust.Client.GameStates foreach (var entityState in state.EntityStates.Span) { - if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData)) + if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData)) { compData = new Dictionary(); - _lastStateFullRep.Add(entityState.Uid, compData); + _lastStateFullRep.Add(entityState.NetEntity, compData); } foreach (var change in entityState.ComponentChanges.Span) @@ -263,7 +263,7 @@ namespace Robust.Client.GameStates return false; } - internal void AddLeavePvsMessage(List entities, GameTick tick) + internal void AddLeavePvsMessage(List entities, GameTick tick) { // Late message may still need to be processed, DebugTools.Assert(entities.Count > 0); @@ -272,9 +272,9 @@ namespace Robust.Client.GameStates public void ClearDetachQueue() => _pvsDetachMessages.Clear(); - public List<(GameTick Tick, List Entities)> GetEntitiesToDetach(GameTick toTick, int budget) + public List<(GameTick Tick, List Entities)> GetEntitiesToDetach(GameTick toTick, int budget) { - var result = new List<(GameTick Tick, List Entities)>(); + var result = new List<(GameTick Tick, List Entities)>(); foreach (var (tick, entities) in _pvsDetachMessages) { if (tick > toTick) @@ -353,11 +353,11 @@ namespace Robust.Client.GameStates LastFullStateRequested = _timing.LastRealTick; } - public void MergeImplicitData(Dictionary> implicitData) + public void MergeImplicitData(Dictionary> implicitData) { - foreach (var (uid, implicitEntState) in implicitData) + foreach (var (netEntity, implicitEntState) in implicitData) { - var fullRep = _lastStateFullRep[uid]; + var fullRep = _lastStateFullRep[netEntity]; foreach (var (netId, implicitCompState) in implicitEntState) { @@ -374,7 +374,7 @@ namespace Robust.Client.GameStates // state from the entity prototype. if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState) { - _logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {uid}"); + _logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}"); continue; } @@ -385,17 +385,17 @@ namespace Robust.Client.GameStates } } - public Dictionary GetLastServerStates(EntityUid entity) + public Dictionary GetLastServerStates(NetEntity netEntity) { - return _lastStateFullRep[entity]; + return _lastStateFullRep[netEntity]; } - public Dictionary> GetFullRep() + public Dictionary> GetFullRep() { return _lastStateFullRep; } - public bool TryGetLastServerStates(EntityUid entity, + public bool TryGetLastServerStates(NetEntity entity, [NotNullWhen(true)] out Dictionary? dictionary) { return _lastStateFullRep.TryGetValue(entity, out dictionary); diff --git a/Robust.Client/GameStates/IClientGameStateManager.cs b/Robust.Client/GameStates/IClientGameStateManager.cs index 4e63d9f7f..36feb53d5 100644 --- a/Robust.Client/GameStates/IClientGameStateManager.cs +++ b/Robust.Client/GameStates/IClientGameStateManager.cs @@ -75,7 +75,7 @@ namespace Robust.Client.GameStates /// /// Applies a given set of game states. /// - IEnumerable ApplyGameState(GameState curState, GameState? nextState); + IEnumerable ApplyGameState(GameState curState, GameState? nextState); /// /// Resets any entities that have changed while predicting future ticks. @@ -86,12 +86,12 @@ namespace Robust.Client.GameStates /// An input command has been dispatched. /// /// Message being dispatched. - void InputCommandDispatched(FullInputCmdMessage message); + void InputCommandDispatched(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message); /// /// Requests a full state from the server. This should override even implicit entity data. /// - void RequestFullState(EntityUid? missingEntity = null); + void RequestFullState(NetEntity? missingEntity = null); uint SystemMessageDispatched(T message) where T : EntityEventArgs; @@ -105,7 +105,7 @@ namespace Robust.Client.GameStates /// /// Returns the full collection of cached game states that are used to reset predicted entities. /// - Dictionary> GetFullRep(); + Dictionary> GetFullRep(); /// /// This will perform some setup in order to reset the game to an earlier state. To fully reset the state @@ -144,12 +144,12 @@ namespace Robust.Client.GameStates /// Queue a collection of entities that are to be detached to null-space & marked as PVS-detached. /// This store and modify the list given to it. /// - void QueuePvsDetach(List entities, GameTick tick); + void QueuePvsDetach(List entities, GameTick tick); /// /// Immediately detach several entities. /// - void DetachImmediate(List entities); + void DetachImmediate(List entities); /// /// Clears the PVS detach queue. diff --git a/Robust.Client/GameStates/IGameStateProcessor.cs b/Robust.Client/GameStates/IGameStateProcessor.cs index ebf519c8e..fd366ceb2 100644 --- a/Robust.Client/GameStates/IGameStateProcessor.cs +++ b/Robust.Client/GameStates/IGameStateProcessor.cs @@ -83,13 +83,13 @@ namespace Robust.Client.GameStates /// The data to merge. /// It's a dictionary of entity ID -> (component net ID -> ComponentState) /// - void MergeImplicitData(Dictionary> data); + void MergeImplicitData(Dictionary> data); /// /// Get the last state data from the server for an entity. /// /// Dictionary (net ID -> ComponentState) - Dictionary GetLastServerStates(EntityUid entity); + Dictionary GetLastServerStates(NetEntity entity); /// /// Calculate the number of applicable states in the game state buffer from a given tick. @@ -98,7 +98,7 @@ namespace Robust.Client.GameStates /// The tick to calculate from. int CalculateBufferSize(GameTick fromTick); - bool TryGetLastServerStates(EntityUid entity, + bool TryGetLastServerStates(NetEntity entity, [NotNullWhen(true)] out Dictionary? dictionary); } } diff --git a/Robust.Client/GameStates/NetEntityOverlay.cs b/Robust.Client/GameStates/NetEntityOverlay.cs index f5638b9f1..e63c3e33a 100644 --- a/Robust.Client/GameStates/NetEntityOverlay.cs +++ b/Robust.Client/GameStates/NetEntityOverlay.cs @@ -35,7 +35,7 @@ namespace Robust.Client.GameStates private readonly Font _font; private readonly int _lineHeight; - private readonly Dictionary _netEnts = new(); + private readonly Dictionary _netEnts = new(); public NetEntityOverlay() { @@ -77,12 +77,12 @@ namespace Robust.Client.GameStates foreach (var entityState in gameState.EntityStates.Span) { - if (!_netEnts.TryGetValue(entityState.Uid, out var netEnt)) + if (!_netEnts.TryGetValue(entityState.NetEntity, out var netEnt)) { if (_netEnts.Count >= _maxEnts) continue; - _netEnts[entityState.Uid] = netEnt = new(); + _netEnts[entityState.NetEntity] = netEnt = new(); } if (!netEnt.InPVS && netEnt.LastUpdate < gameState.ToSequence) @@ -119,11 +119,13 @@ namespace Robust.Client.GameStates var screenHandle = args.ScreenHandle; int i = 0; - foreach (var (uid, netEnt) in _netEnts) + foreach (var (nent, netEnt) in _netEnts) { + var uid = _entityManager.GetEntity(nent); + if (!_entityManager.EntityExists(uid)) { - _netEnts.Remove(uid); + _netEnts.Remove(nent); continue; } diff --git a/Robust.Client/GameStates/NetGraphOverlay.cs b/Robust.Client/GameStates/NetGraphOverlay.cs index 9b02d1db2..a045da24d 100644 --- a/Robust.Client/GameStates/NetGraphOverlay.cs +++ b/Robust.Client/GameStates/NetGraphOverlay.cs @@ -26,6 +26,7 @@ namespace Robust.Client.GameStates [Dependency] private readonly IClientNetManager _netManager = default!; [Dependency] private readonly IClientGameStateManager _gameStateManager = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly IEntityManager _entManager = default!; private const int HistorySize = 60 * 5; // number of ticks to keep in history. private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more? @@ -73,7 +74,7 @@ namespace Robust.Client.GameStates _history.Add((toSeq, sz, lag, buffer)); // not watching an ent - if(!WatchEntId.IsValid() || WatchEntId.IsClientSide()) + if(!WatchEntId.IsValid() || _entManager.IsClientSide(WatchEntId)) return; string? entStateString = null; @@ -86,7 +87,9 @@ namespace Robust.Client.GameStates var sb = new StringBuilder(); foreach (var entState in entStates.Span) { - if (entState.Uid != WatchEntId) + var uid = _entManager.GetEntity(entState.NetEntity); + + if (uid != WatchEntId) continue; if (!entState.ComponentChanges.HasContents) @@ -115,7 +118,9 @@ namespace Robust.Client.GameStates foreach (var ent in args.Detached) { - if (ent != WatchEntId) + var uid = _entManager.GetEntity(ent); + + if (uid != WatchEntId) continue; conShell.WriteLine($"watchEnt: Left PVS at tick {args.AppliedState.ToSequence}, eid={WatchEntId}" + "\n"); @@ -126,7 +131,9 @@ namespace Robust.Client.GameStates { foreach (var entDelete in entDeletes.Span) { - if (entDelete == WatchEntId) + var uid = _entManager.GetEntity(entDelete); + + if (uid == WatchEntId) entDelString = "\n Deleted"; } } @@ -294,30 +301,33 @@ namespace Robust.Client.GameStates private sealed class NetWatchEntCommand : LocalizedCommands { + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IOverlayManager _overlayManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + public override string Command => "net_watchent"; public override void Execute(IConsoleShell shell, string argStr, string[] args) { - EntityUid eValue; + EntityUid? entity; + if (args.Length == 0) { - eValue = IoCManager.Resolve().LocalPlayer?.ControlledEntity ?? EntityUid.Invalid; + entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid; } - else if (!EntityUid.TryParse(args[0], out eValue)) + else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity)) { shell.WriteError("Invalid argument: Needs to be 0 or an entityId."); return; } - var overlayMan = IoCManager.Resolve(); - - if (!overlayMan.TryGetOverlay(out NetGraphOverlay? overlay)) + if (!_overlayManager.TryGetOverlay(out NetGraphOverlay? overlay)) { - overlay = new(); - overlayMan.AddOverlay(overlay); + overlay = new NetGraphOverlay(); + _overlayManager.AddOverlay(overlay); } - overlay.WatchEntId = eValue; + overlay.WatchEntId = entity.Value; } } } diff --git a/Robust.Client/Physics/GridFixtureSystem.cs b/Robust.Client/Physics/GridFixtureSystem.cs index 0b4669b64..d0d6eeb38 100644 --- a/Robust.Client/Physics/GridFixtureSystem.cs +++ b/Robust.Client/Physics/GridFixtureSystem.cs @@ -62,8 +62,9 @@ namespace Robust.Client.Physics Log.Info($"Received grid fixture debug data"); if (!_enableDebug) return; - _nodes[ev.Grid] = ev.Nodes; - _connections[ev.Grid] = ev.Connections; + var grid = GetEntity(ev.Grid); + _nodes[grid] = ev.Nodes; + _connections[grid] = ev.Connections; } private sealed class GridSplitNodeOverlay : Overlay diff --git a/Robust.Client/Physics/JointSystem.cs b/Robust.Client/Physics/JointSystem.cs index d17ff7175..3ee18c133 100644 --- a/Robust.Client/Physics/JointSystem.cs +++ b/Robust.Client/Physics/JointSystem.cs @@ -20,7 +20,7 @@ namespace Robust.Client.Physics { if (args.Current is not JointComponentState jointState) return; - component.Relay = jointState.Relay; + component.Relay = EnsureEntity(jointState.Relay, uid); // Initial state gets applied before the entity (& entity's transform) have been initialized. // So just let joint init code handle that. @@ -29,7 +29,7 @@ namespace Robust.Client.Physics component.Joints.Clear(); foreach (var (id, state) in jointState.Joints) { - component.Joints[id] = state.GetJoint(); + component.Joints[id] = state.GetJoint(EntityManager, uid); } return; } @@ -62,8 +62,8 @@ namespace Robust.Client.Physics continue; } - var other = state.UidA == uid ? state.UidB : state.UidA; - + var uidA = GetEntity(state.UidA); + var other = uidA == uid ? GetEntity(state.UidB) : uidA; // Add new joint (if possible). // Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies! @@ -82,11 +82,11 @@ namespace Robust.Client.Physics // TODO: component state handling ordering. if (Transform(uid).MapID == MapId.Nullspace) { - AddedJoints.Add(state.GetJoint()); + AddedJoints.Add(state.GetJoint(EntityManager, uid)); continue; } - AddJoint(state.GetJoint()); + AddJoint(state.GetJoint(EntityManager, uid)); } } } diff --git a/Robust.Client/Placement/PlacementManager.cs b/Robust.Client/Placement/PlacementManager.cs index 55d4a1daf..0de25afa7 100644 --- a/Robust.Client/Placement/PlacementManager.cs +++ b/Robust.Client/Placement/PlacementManager.cs @@ -225,7 +225,7 @@ namespace Robust.Client.Placement } })) .Bind(EngineKeyFunctions.EditorPlaceObject, new PointerStateInputCmdHandler( - (session, coords, uid) => + (session, netCoords, nent) => { if (!IsActive) return false; @@ -239,15 +239,15 @@ namespace Robust.Client.Placement if (Eraser) { - if (HandleDeletion(coords)) + if (HandleDeletion(netCoords)) return true; - if (uid == EntityUid.Invalid) + if (nent == EntityUid.Invalid) { return false; } - HandleDeletion(uid); + HandleDeletion(nent); } else { @@ -428,7 +428,7 @@ namespace Robust.Client.Placement var msg = new MsgPlacement(); msg.PlaceType = PlacementManagerMessage.RequestEntRemove; - msg.EntityUid = entity; + msg.EntityUid = EntityManager.GetNetEntity(entity); _networkManager.ClientSendMessage(msg); } @@ -436,7 +436,7 @@ namespace Robust.Client.Placement { var msg = new MsgPlacement(); msg.PlaceType = PlacementManagerMessage.RequestRectRemove; - msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft); + msg.NetCoordinates = new NetCoordinates(EntityManager.GetNetEntity(StartPoint.EntityId), rect.BottomLeft); msg.RectSize = rect.Size; _networkManager.ClientSendMessage(msg); } @@ -790,7 +790,7 @@ namespace Robust.Client.Placement message.EntityTemplateName = CurrentPermission.EntityType; // world x and y - message.EntityCoordinates = coordinates; + message.NetCoordinates = EntityManager.GetNetCoordinates(coordinates); message.DirRcv = Direction; diff --git a/Robust.Client/Player/PlayerManager.cs b/Robust.Client/Player/PlayerManager.cs index 77c668f7a..42c7e0522 100644 --- a/Robust.Client/Player/PlayerManager.cs +++ b/Robust.Client/Player/PlayerManager.cs @@ -6,6 +6,7 @@ using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Players; @@ -24,6 +25,7 @@ namespace Robust.Client.Player [Dependency] private readonly IClientNetManager _network = default!; [Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly ILogManager _logMan = default!; /// /// Active sessions of connected clients to the server. @@ -65,6 +67,8 @@ namespace Robust.Client.Player } } private LocalPlayer? _localPlayer; + private ISawmill _sawmill = default!; + public event Action? LocalPlayerChanged; /// @@ -82,6 +86,7 @@ namespace Robust.Client.Player { _client.RunLevelChanged += OnRunLevelChanged; + _sawmill = _logMan.GetSawmill("player"); _network.RegisterNetMessage(); _network.RegisterNetMessage(HandlePlayerList); } @@ -122,7 +127,13 @@ namespace Robust.Client.Player if (myState != null) { - UpdateAttachedEntity(myState.ControlledEntity); + var uid = _entManager.GetEntity(myState.ControlledEntity); + if (myState.ControlledEntity is {Valid: true} && !_entManager.EntityExists(uid)) + { + _sawmill.Error($"Received player state for local player with an unknown net entity!"); + } + + UpdateAttachedEntity(uid); UpdateSessionStatus(myState.Status); } @@ -181,11 +192,13 @@ namespace Robust.Client.Player if (_sessions.TryGetValue(state.UserId, out var session)) { var local = (PlayerSession) session; + var controlled = _entManager.GetEntity(state.ControlledEntity); + // Exists, update data. if (local.Name == state.Name && local.Status == state.Status && local.Ping == state.Ping - && local.AttachedEntity == state.ControlledEntity) + && local.AttachedEntity == controlled) { continue; } @@ -194,7 +207,7 @@ namespace Robust.Client.Player local.Name = state.Name; local.Status = state.Status; local.Ping = state.Ping; - local.AttachedEntity = state.ControlledEntity; + local.AttachedEntity = controlled; } else { @@ -206,7 +219,7 @@ namespace Robust.Client.Player Name = state.Name, Status = state.Status, Ping = state.Ping, - AttachedEntity = state.ControlledEntity, + AttachedEntity = _entManager.GetEntity(state.ControlledEntity), }; _sessions.Add(state.UserId, newSession); if (state.UserId == LocalPlayer!.UserId) diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs index cc420e9eb..b426b7b46 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs @@ -83,8 +83,8 @@ public sealed partial class ReplayLoadManager } HashSet uploadedFiles = new(); - var detached = new HashSet(); - var detachQueue = new Dictionary>(); + var detached = new HashSet(); + var detachQueue = new Dictionary>(); if (initMessages != null) UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true); @@ -92,11 +92,11 @@ public sealed partial class ReplayLoadManager ProcessQueue(GameTick.MaxValue, detachQueue, detached); var entSpan = state0.EntityStates.Value; - Dictionary entStates = new(entSpan.Count); + Dictionary entStates = new(entSpan.Count); foreach (var entState in entSpan) { var modifiedState = AddImplicitData(entState); - entStates.Add(entState.Uid, modifiedState); + entStates.Add(entState.NetEntity, modifiedState); } await callback(0, states.Count, LoadingState.ProcessingFiles, true); @@ -112,11 +112,11 @@ public sealed partial class ReplayLoadManager default, entStates.Values.ToArray(), playerStates.Values.ToArray(), - Array.Empty()); + Array.Empty()); checkPoints.Add(new CheckpointState(state0, timeBase, cvars, 0, detached)); DebugTools.Assert(state0.EntityDeletions.Value.Count == 0); - var empty = Array.Empty(); + var empty = Array.Empty(); TimeSpan GetTime(GameTick tick) { @@ -176,8 +176,8 @@ public sealed partial class ReplayLoadManager private void ProcessQueue( GameTick curTick, - Dictionary> detachQueue, - HashSet detached) + Dictionary> detachQueue, + HashSet detached) { foreach (var (tick, ents) in detachQueue) { @@ -192,7 +192,7 @@ public sealed partial class ReplayLoadManager HashSet uploadedFiles, Dictionary> prototypes, Dictionary cvars, - Dictionary> detachQueue, + Dictionary> detachQueue, ref (TimeSpan, GameTick) timeBase, bool ignoreDuplicates = false) { @@ -301,8 +301,8 @@ public sealed partial class ReplayLoadManager _locMan.ReloadLocalizations(); } - private void UpdateDeletions(NetListAsArray entityDeletions, - Dictionary entStates, HashSet detached) + private void UpdateDeletions(NetListAsArray entityDeletions, + Dictionary entStates, HashSet detached) { foreach (var ent in entityDeletions.Span) { @@ -311,16 +311,16 @@ public sealed partial class ReplayLoadManager } } - private void UpdateEntityStates(ReadOnlySpan span, Dictionary entStates, - ref int spawnedTracker, ref int stateTracker, HashSet detached) + private void UpdateEntityStates(ReadOnlySpan span, Dictionary entStates, + ref int spawnedTracker, ref int stateTracker, HashSet detached) { foreach (var entState in span) { - detached.Remove(entState.Uid); - if (!entStates.TryGetValue(entState.Uid, out var oldEntState)) + detached.Remove(entState.NetEntity); + if (!entStates.TryGetValue(entState.NetEntity, out var oldEntState)) { var modifiedState = AddImplicitData(entState); - entStates[entState.Uid] = modifiedState; + entStates[entState.NetEntity] = modifiedState; spawnedTracker++; #if DEBUG @@ -333,11 +333,11 @@ public sealed partial class ReplayLoadManager } stateTracker++; - DebugTools.Assert(oldEntState.Uid == entState.Uid); - entStates[entState.Uid] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents); + DebugTools.Assert(oldEntState.NetEntity == entState.NetEntity); + entStates[entState.NetEntity] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents); #if DEBUG - foreach (var state in entStates[entState.Uid].ComponentChanges.Span) + foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span) { DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState); } @@ -388,7 +388,7 @@ public sealed partial class ReplayLoadManager } DebugTools.Assert(newState.NetComponents == null || newState.NetComponents.Count == combined.Count); - return new EntityState(newState.Uid, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps); + return new EntityState(newState.NetEntity, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps); } private void UpdatePlayerStates(ReadOnlySpan span, Dictionary playerStates) diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Implicit.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Implicit.cs index d1a30103e..dafae76c1 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Implicit.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Implicit.cs @@ -70,14 +70,14 @@ public sealed partial class ReplayLoadManager { // This shouldn't be possible, yet it has happened? // TODO this should probably also throw an exception. - _sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue."); + _sawmill.Error($"Encountered blank entity state? Entity: {entState.NetEntity}. Last modified: {entState.EntityLastModified}. Attempting to continue."); return null; } if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors)) - throw new MissingMetadataException(entState.Uid); + throw new MissingMetadataException(entState.NetEntity); - _sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}."); + _sawmill.Error($"Missing metadata component. Entity: {entState.NetEntity}. Last modified: {entState.EntityLastModified}."); return null; } } diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Start.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Start.cs index 0a19f0336..42728535f 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.Start.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Start.cs @@ -68,10 +68,18 @@ public sealed partial class ReplayLoadManager var metaState = (MetaDataComponentState?)ent.ComponentChanges.Value? .FirstOrDefault(c => c.NetID == _metaId).State; if (metaState == null) - throw new MissingMetadataException(ent.Uid); + throw new MissingMetadataException(ent.NetEntity); - _entMan.CreateEntityUninitialized(metaState.PrototypeId, ent.Uid); - entities.Add(ent.Uid); + var uid = _entMan.CreateEntityUninitialized(metaState.PrototypeId); + entities.Add(uid); + var metaComp = _entMan.GetComponent(uid); + + // Client creates a client-side net entity for the newly created entity. + // We need to clear this mapping before assigning the real net id. + // TODO NetEntity Jank: prevent the client from creating this in the first place. + _entMan.ClearNetEntity(metaComp.NetEntity); + + _entMan.SetNetEntity(uid, ent.NetEntity, metaComp); if (i++ % 50 == 0) { diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.cs index 1e4973bb0..84dbd1871 100644 --- a/Robust.Client/Replays/Loading/ReplayLoadManager.cs +++ b/Robust.Client/Replays/Loading/ReplayLoadManager.cs @@ -19,7 +19,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager { [Dependency] private readonly ILogManager _logMan = default!; [Dependency] private readonly IBaseClient _client = default!; - [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly EntityManager _entMan = default!; [Dependency] private readonly IClientGameTiming _timing = default!; [Dependency] private readonly IClientNetManager _netMan = default!; [Dependency] private readonly IComponentFactory _factory = default!; diff --git a/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs b/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs index fc6b05892..cfd9f77e0 100644 --- a/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs +++ b/Robust.Client/Replays/Playback/ReplayPlaybackManager.Checkpoint.cs @@ -82,19 +82,28 @@ internal sealed partial class ReplayPlaybackManager var metas = _entMan.GetEntityQuery(); foreach (var es in checkpoint.DetachedStates) { - if (metas.TryGetComponent(es.Uid, out var meta) && !meta.EntityDeleted) + var uid = _entMan.GetEntity(es.NetEntity); + if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted) continue; - ; + var metaState = (MetaDataComponentState?)es.ComponentChanges.Value? .FirstOrDefault(c => c.NetID == _metaId).State; if (metaState == null) - throw new MissingMetadataException(es.Uid); + throw new MissingMetadataException(es.NetEntity); - _entMan.CreateEntityUninitialized(metaState.PrototypeId, es.Uid); - meta = metas.GetComponent(es.Uid); - _entMan.InitializeEntity(es.Uid, meta); - _entMan.StartEntity(es.Uid); + _entMan.CreateEntityUninitialized(metaState.PrototypeId, uid); + meta = metas.GetComponent(uid); + + // Client creates a client-side net entity for the newly created entity. + // We need to clear this mapping before assigning the real net id. + // TODO NetEntity Jank: prevent the client from creating this in the first place. + _entMan.ClearNetEntity(meta.NetEntity); + + _entMan.SetNetEntity(uid, es.NetEntity, meta); + + _entMan.InitializeEntity(uid, meta); + _entMan.StartEntity(uid); meta.LastStateApplied = checkpoint.Tick; } } diff --git a/Robust.Client/Replays/Playback/ReplayPlaybackManager.Update.cs b/Robust.Client/Replays/Playback/ReplayPlaybackManager.Update.cs index 623edf2d9..b14a36b36 100644 --- a/Robust.Client/Replays/Playback/ReplayPlaybackManager.Update.cs +++ b/Robust.Client/Replays/Playback/ReplayPlaybackManager.Update.cs @@ -50,7 +50,7 @@ internal sealed partial class ReplayPlaybackManager } _timing.CurTick += 1; - _entMan.TickUpdate(args.DeltaSeconds, noPredictions: true); + _cEntManager.TickUpdate(args.DeltaSeconds, noPredictions: true); if (!Playing || AutoPauseCountdown == null) return; @@ -82,7 +82,7 @@ internal sealed partial class ReplayPlaybackManager // Maybe track our own detach queue and use _gameState.DetachImmediate()? // That way we don't have to clone this. Downside would be that all entities will be immediately // detached. I.e., the detach budget cvar will simply be ignored. - var clone = new List(leavePvs.Entities); + var clone = new List(leavePvs.Entities); _gameState.QueuePvsDetach(clone, leavePvs.Tick); continue; diff --git a/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs b/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs index 653e740e7..22f9f1a24 100644 --- a/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs +++ b/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs @@ -33,7 +33,8 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly IGameController _controller = default!; - [Dependency] private readonly IClientEntityManager _entMan = default!; + [Dependency] private readonly IClientEntityManager _cEntManager = default!; + [Dependency] private readonly ClientEntityManager _entMan = default!; [Dependency] private readonly IConfigurationManager _confMan = default!; [Dependency] private readonly NetworkResourceManager _netResMan = default!; [Dependency] private readonly IClientGameStateManager _gameState = default!; diff --git a/Robust.Client/Replays/ReplayRecordingManager.cs b/Robust.Client/Replays/ReplayRecordingManager.cs index 026103d53..e9dd7ff2a 100644 --- a/Robust.Client/Replays/ReplayRecordingManager.cs +++ b/Robust.Client/Replays/ReplayRecordingManager.cs @@ -95,12 +95,12 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager { var tick = _timing.LastRealTick; var players = _player.Sessions.Select(GetPlayerState).ToArray(); - var deletions = Array.Empty(); + var deletions = Array.Empty(); var fullRep = _state.GetFullRep(); var entStates = new EntityState[fullRep.Count]; var i = 0; - foreach (var (uid, dict) in fullRep) + foreach (var (netEntity, dict) in fullRep) { var compData = new ComponentChange[dict.Count]; var netComps = new HashSet(dict.Keys); @@ -110,7 +110,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager compData[j++] = new ComponentChange(id, compState, tick); } - entStates[i++] = new EntityState(uid, compData, tick, netComps); + entStates[i++] = new EntityState(netEntity, compData, tick, netComps); } var state = new GameState( @@ -121,16 +121,17 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager players, deletions); - var detached = new List(); + var detached = new List(); var query = _entMan.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { - if (uid.IsClientSide()) + if (_entMan.IsClientSide(uid)) continue; - DebugTools.Assert(fullRep.ContainsKey(uid)); + var nent = comp.NetEntity; + DebugTools.Assert(fullRep.ContainsKey(nent)); if ((comp.Flags & MetaDataFlags.Detached) != 0) - detached.Add(uid); + detached.Add(nent); } var detachMsg = detached.Count > 0 ? new ReplayMessage.LeavePvs(detached, tick) : null; @@ -144,7 +145,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager UserId = session.UserId, Status = session.Status, Name = session.Name, - ControlledEntity = session.AttachedEntity, + ControlledEntity = _entMan.GetNetEntity(session.AttachedEntity), }; } } diff --git a/Robust.Client/ViewVariables/ClientViewVariablesManager.cs b/Robust.Client/ViewVariables/ClientViewVariablesManager.cs index 935ff4611..ea7ee680b 100644 --- a/Robust.Client/ViewVariables/ClientViewVariablesManager.cs +++ b/Robust.Client/ViewVariables/ClientViewVariablesManager.cs @@ -218,7 +218,7 @@ namespace Robust.Client.ViewVariables { // TODO: more flexibility in allowing custom instances here. ViewVariablesInstance instance; - if (obj is EntityUid entity && _entityManager.EntityExists(entity)) + if (obj is NetEntity netEntity && _entityManager.GetEntity(netEntity).IsValid()) { instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer, Sawmill); } @@ -269,7 +269,7 @@ namespace Robust.Client.ViewVariables var type = Type.GetType(blob.ObjectType); // TODO: more flexibility in allowing custom instances here. ViewVariablesInstance instance; - if (type != null && typeof(EntityUid).IsAssignableFrom(type)) + if (type != null && typeof(NetEntity).IsAssignableFrom(type)) { instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer, Sawmill); } diff --git a/Robust.Client/ViewVariables/Instances/ViewVariablesInstanceEntity.cs b/Robust.Client/ViewVariables/Instances/ViewVariablesInstanceEntity.cs index d44f53e88..aa1149ef8 100644 --- a/Robust.Client/ViewVariables/Instances/ViewVariablesInstanceEntity.cs +++ b/Robust.Client/ViewVariables/Instances/ViewVariablesInstanceEntity.cs @@ -41,7 +41,8 @@ namespace Robust.Client.ViewVariables.Instances private const int TabServerComponents = 3; private TabContainer _tabs = default!; - private EntityUid _entity = default!; + private EntityUid _entity; + private NetEntity _netEntity = default!; private ViewVariablesAddWindow? _addComponentWindow; private bool _addComponentServer; @@ -71,7 +72,8 @@ namespace Robust.Client.ViewVariables.Instances public override void Initialize(DefaultWindow window, object obj) { - _entity = (EntityUid) obj; + _netEntity = (NetEntity) obj; + _entity = _entityManager.GetEntity(_netEntity); var scrollContainer = new ScrollContainer(); //scrollContainer.SetAnchorPreset(Control.LayoutPreset.Wide, true); @@ -163,7 +165,7 @@ namespace Robust.Client.ViewVariables.Instances PopulateClientComponents(); - if (!_entity.IsClientSide()) + if (!_entityManager.IsClientSide(_entity)) { _serverVariables = new BoxContainer { @@ -268,12 +270,12 @@ namespace Robust.Client.ViewVariables.Instances button.OnPressed += _ => { ViewVariablesManager.OpenVV( - new ViewVariablesComponentSelector(_entity, componentType.FullName)); + new ViewVariablesComponentSelector(_entityManager.GetNetEntity(_entity), componentType.FullName)); }; removeButton.OnPressed += _ => { // We send a command to remove the component. - IoCManager.Resolve().RemoteExecuteCommand(null, $"rmcomp {_entity} {componentType.ComponentName}"); + IoCManager.Resolve().RemoteExecuteCommand(null, $"rmcomp {_netEntity} {componentType.ComponentName}"); PopulateServerComponents(); }; button.AddChild(removeButton); @@ -417,7 +419,7 @@ namespace Robust.Client.ViewVariables.Instances if (_addComponentServer) { // Attempted to add a component to the server entity... We send a command. - IoCManager.Resolve().RemoteExecuteCommand(null, $"addcomp {_entity} {eventArgs.Entry}"); + IoCManager.Resolve().RemoteExecuteCommand(null, $"addcomp {_netEntity} {eventArgs.Entry}"); PopulateServerComponents(); _addComponentWindow?.Populate(await GetValidServerComponentsForAdding()); return; @@ -504,7 +506,7 @@ namespace Robust.Client.ViewVariables.Instances try { _entitySession = - await ViewVariablesManager.RequestSession(new ViewVariablesEntitySelector(_entity)); + await ViewVariablesManager.RequestSession(new ViewVariablesEntitySelector(_netEntity)); } catch (SessionDenyException e) { diff --git a/Robust.Client/ViewVariables/ViewVariablesCommand.cs b/Robust.Client/ViewVariables/ViewVariablesCommand.cs index b24b6b684..c9567f3b2 100644 --- a/Robust.Client/ViewVariables/ViewVariablesCommand.cs +++ b/Robust.Client/ViewVariables/ViewVariablesCommand.cs @@ -63,20 +63,22 @@ namespace Robust.Client.ViewVariables } // Entity. - if (!EntityUid.TryParse(args[0], out var entity)) + if (!NetEntity.TryParse(args[0], out var netEntity)) { shell.WriteLine("Invalid specifier format."); return; } + var entity = _entities.GetEntity(netEntity); + if (!_entities.EntityExists(entity)) { shell.WriteLine("That entity does not exist locally. Attempting to open remote view..."); - _cvvm.OpenVV(new ViewVariablesEntitySelector(entity)); + _cvvm.OpenVV(new ViewVariablesEntitySelector(netEntity)); return; } - _cvvm.OpenVV(entity); + _cvvm.OpenVV(netEntity); } } } diff --git a/Robust.Server/Console/Commands/AddComponentCommand.cs b/Robust.Server/Console/Commands/AddComponentCommand.cs index 15006569f..18f2639ba 100644 --- a/Robust.Server/Console/Commands/AddComponentCommand.cs +++ b/Robust.Server/Console/Commands/AddComponentCommand.cs @@ -22,13 +22,13 @@ namespace Robust.Server.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var uid)) + if (!NetEntity.TryParse(args[0], out var uidNet)) { - shell.WriteLine($"{uid} is not a valid entity uid."); + shell.WriteLine($"{args[0]} is not a valid entity."); return; } - if (!_entityManager.EntityExists(uid)) + if (!_entityManager.TryGetEntity(uidNet, out var uid) || !_entityManager.EntityExists(uid)) { shell.WriteLine($"No entity found with id {uid}."); return; @@ -44,17 +44,17 @@ namespace Robust.Server.Console.Commands if (_entityManager.HasComponent(uid, registration.Type)) { - shell.WriteLine($"Entity {_entityManager.GetComponent(uid).EntityName} already has a {componentName} component."); + shell.WriteLine($"Entity {_entityManager.GetComponent(uid.Value).EntityName} already has a {componentName} component."); } var component = (Component) _componentFactory.GetComponent(registration.Type); #pragma warning disable CS0618 - component.Owner = uid; + component.Owner = uid.Value; #pragma warning restore CS0618 - _entityManager.AddComponent(uid, component); + _entityManager.AddComponent(uid.Value, component); - shell.WriteLine($"Added {componentName} component to entity {_entityManager.GetComponent(uid).EntityName}."); + shell.WriteLine($"Added {componentName} component to entity {_entityManager.GetComponent(uid.Value).EntityName}."); } } } diff --git a/Robust.Server/Console/Commands/DeleteCommand.cs b/Robust.Server/Console/Commands/DeleteCommand.cs index b3a9931b2..5482447ec 100644 --- a/Robust.Server/Console/Commands/DeleteCommand.cs +++ b/Robust.Server/Console/Commands/DeleteCommand.cs @@ -19,19 +19,19 @@ namespace Robust.Server.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var entity)) + if (!NetEntity.TryParse(args[0], out var entityNet)) { shell.WriteLine("Invalid entity UID."); return; } - if (!_entityManager.EntityExists(entity)) + if (!_entityManager.TryGetEntity(entityNet, out var entity) || !_entityManager.EntityExists(entity)) { shell.WriteLine("That entity does not exist."); return; } - _entityManager.DeleteEntity(entity); + _entityManager.DeleteEntity(entity.Value); } } } diff --git a/Robust.Server/Console/Commands/MapCommands.cs b/Robust.Server/Console/Commands/MapCommands.cs index 4733e5145..83a1bcf47 100644 --- a/Robust.Server/Console/Commands/MapCommands.cs +++ b/Robust.Server/Console/Commands/MapCommands.cs @@ -27,12 +27,14 @@ namespace Robust.Server.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var uid)) + if (!NetEntity.TryParse(args[0], out var uidNet)) { shell.WriteError("Not a valid entity ID."); return; } + var uid = _ent.GetEntity(uidNet); + // no saving default grid if (!_ent.EntityExists(uid)) { diff --git a/Robust.Server/Console/Commands/RemoveComponentCommand.cs b/Robust.Server/Console/Commands/RemoveComponentCommand.cs index 35e2e4cee..79522d00e 100644 --- a/Robust.Server/Console/Commands/RemoveComponentCommand.cs +++ b/Robust.Server/Console/Commands/RemoveComponentCommand.cs @@ -22,12 +22,14 @@ namespace Robust.Server.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var uid)) + if (!NetEntity.TryParse(args[0], out var netEntity)) { - shell.WriteLine($"{uid} is not a valid entity uid."); + shell.WriteLine($"{netEntity} is not a valid entity uid."); return; } + var uid = _entityManager.GetEntity(netEntity); + if (!_entityManager.EntityExists(uid)) { shell.WriteLine($"No entity found with id {uid}."); diff --git a/Robust.Server/Console/Commands/ScaleCommand.cs b/Robust.Server/Console/Commands/ScaleCommand.cs index c3a0ee73d..601e775c3 100644 --- a/Robust.Server/Console/Commands/ScaleCommand.cs +++ b/Robust.Server/Console/Commands/ScaleCommand.cs @@ -24,7 +24,7 @@ public sealed class ScaleCommand : LocalizedCommands return; } - if (!EntityUid.TryParse(args[0], out var uid)) + if (!NetEntity.TryParse(args[0], out var netEntity)) { shell.WriteError($"Unable to find entity {args[0]}"); return; @@ -47,6 +47,7 @@ public sealed class ScaleCommand : LocalizedCommands var physics = _entityManager.System(); var appearance = _entityManager.System(); + var uid = _entityManager.GetEntity(netEntity); _entityManager.EnsureComponent(uid); var @event = new ScaleEntityEvent(); _entityManager.EventBus.RaiseLocalEvent(uid, ref @event); diff --git a/Robust.Server/Console/Commands/SpinCommand.cs b/Robust.Server/Console/Commands/SpinCommand.cs index fe2038f92..9f48332a9 100644 --- a/Robust.Server/Console/Commands/SpinCommand.cs +++ b/Robust.Server/Console/Commands/SpinCommand.cs @@ -29,10 +29,12 @@ public sealed class SpinCommand : LocalizedCommands } // get the target - EntityUid target; + EntityUid? target; + if (args.Length == 3) { - if (!EntityUid.TryParse(args[2], out target)) + if (!NetEntity.TryParse(args[2], out var targetNet) || + !_entities.TryGetEntity(targetNet, out target)) { shell.WriteError($"Unable to find entity {args[1]}"); return; @@ -65,6 +67,6 @@ public sealed class SpinCommand : LocalizedCommands var physicsSystem = _entities.System(); physicsSystem.SetAngularDamping(physics, drag); - physicsSystem.SetAngularVelocity(target, speed, body: physics); + physicsSystem.SetAngularVelocity(target.Value, speed, body: physics); } } diff --git a/Robust.Server/Console/Commands/ViewSubscriberCommand.cs b/Robust.Server/Console/Commands/ViewSubscriberCommand.cs index 8d9109fcb..963170a83 100644 --- a/Robust.Server/Console/Commands/ViewSubscriberCommand.cs +++ b/Robust.Server/Console/Commands/ViewSubscriberCommand.cs @@ -29,19 +29,19 @@ namespace Robust.Server.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var uid)) + if (!NetEntity.TryParse(args[0], out var uidNet)) { shell.WriteError($"Unable to parse {args[0]} as a {nameof(EntityUid)}"); return; } - if (!_entities.EntityExists(uid)) + if (!_entities.TryGetEntity(uidNet, out var uid) || !_entities.EntityExists(uid)) { shell.WriteError($"Unable to find entity {uid}"); return; } - _entities.EntitySysManager.GetEntitySystem().AddViewSubscriber(uid, playerSession); + _entities.EntitySysManager.GetEntitySystem().AddViewSubscriber(uid.Value, playerSession); } public sealed class RemoveViewSubscriberCommand : LocalizedCommands @@ -66,19 +66,19 @@ namespace Robust.Server.Console.Commands return; } - if (!EntityUid.TryParse(args[0], out var uid)) + if (!NetEntity.TryParse(args[0], out var uidNet)) { - shell.WriteError($"Unable to parse {args[0]} as a {nameof(EntityUid)}"); + shell.WriteError($"Unable to parse {args[0]} as a {nameof(NetEntity)}"); return; } - if (!_entities.EntityExists(uid)) + if (!_entities.TryGetEntity(uidNet, out var uid) || !_entities.EntityExists(uid)) { shell.WriteError($"Unable to find entity {uid}"); return; } - _entities.EntitySysManager.GetEntitySystem().RemoveViewSubscriber(uid, playerSession); + _entities.EntitySysManager.GetEntitySystem().RemoveViewSubscriber(uid.Value, playerSession); } } } diff --git a/Robust.Server/Containers/ContainerSystem.cs b/Robust.Server/Containers/ContainerSystem.cs index 1a632e2a3..d298fc2b4 100644 --- a/Robust.Server/Containers/ContainerSystem.cs +++ b/Robust.Server/Containers/ContainerSystem.cs @@ -6,7 +6,7 @@ namespace Robust.Server.Containers { public sealed class ContainerSystem : SharedContainerSystem { - protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing) + protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing) { Log.Error($"Missing entity for container {ToPrettyString(uid)}. Missing uid: {missing}"); //cont.InternalRemove(ent); diff --git a/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs b/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs index 2c63635bb..fe2fa1280 100644 --- a/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs @@ -86,9 +86,9 @@ public sealed class AudioSystem : SharedAudioSystem var msg = new PlayAudioEntityMessage { FileName = filename, - Coordinates = transform.Coordinates, - FallbackCoordinates = fallbackCoordinates, - EntityUid = uid, + Coordinates = GetNetCoordinates(transform.Coordinates), + FallbackCoordinates = GetNetCoordinates(fallbackCoordinates), + NetEntity = GetNetEntity(uid), AudioParams = audioParams ?? AudioParams.Default, Identifier = id, }; @@ -108,8 +108,8 @@ public sealed class AudioSystem : SharedAudioSystem var msg = new PlayAudioPositionalMessage { FileName = filename, - Coordinates = coordinates, - FallbackCoordinates = fallbackCoordinates, + Coordinates = GetNetCoordinates(coordinates), + FallbackCoordinates = GetNetCoordinates(fallbackCoordinates), AudioParams = audioParams ?? AudioParams.Default, Identifier = id }; diff --git a/Robust.Server/GameObjects/EntitySystems/InputSystem.cs b/Robust.Server/GameObjects/EntitySystems/InputSystem.cs index 87b169b18..e74a0205f 100644 --- a/Robust.Server/GameObjects/EntitySystems/InputSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/InputSystem.cs @@ -61,7 +61,7 @@ namespace Robust.Server.GameObjects //Client Sanitization: unbound command, just ignore foreach (var handler in BindRegistry.GetHandlers(function)) { - if (handler.HandleCmdMessage(session, msg)) return; + if (handler.HandleCmdMessage(EntityManager, session, msg)) return; } } diff --git a/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs b/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs index 169545d0c..513476385 100644 --- a/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/UserInterfaceSystem.cs @@ -83,7 +83,7 @@ namespace Robust.Server.GameObjects /// private void OnMessageReceived(BoundUIWrapMessage msg, EntitySessionEventArgs args) { - var uid = msg.Entity; + var uid = GetEntity(msg.Entity); if (!TryComp(uid, out ServerUserInterfaceComponent? uiComp) || args.SenderSession is not IPlayerSession session) return; @@ -118,7 +118,7 @@ namespace Robust.Server.GameObjects // get the wrapped message and populate it with the sender & UI key information. var message = msg.Message; message.Session = args.SenderSession; - message.Entity = uid; + message.Entity = GetNetEntity(uid); message.UiKey = msg.UiKey; // Raise as object so the correct type is used. @@ -321,9 +321,9 @@ namespace Robust.Server.GameObjects /// The player session to send this new state to. /// Set to null for sending it to every subscribed player session. /// - public static void SetUiState(BoundUserInterface bui, BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true) + public void SetUiState(BoundUserInterface bui, BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true) { - var msg = new BoundUIWrapMessage(bui.Owner, new UpdateBoundStateMessage(state), bui.UiKey); + var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey); if (session == null) { bui.LastStateMsg = msg; @@ -385,7 +385,7 @@ namespace Robust.Server.GameObjects _openInterfaces.GetOrNew(session).Add(bui); RaiseLocalEvent(bui.Owner, new BoundUIOpenedEvent(bui.UiKey, bui.Owner, session)); - RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient); + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient); // Fun fact, clients needs to have BUIs open before they can receive the state..... if (bui.LastStateMsg != null) @@ -414,7 +414,7 @@ namespace Robust.Server.GameObjects if (!bui._subscribedSessions.Remove(session)) return false; - RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient); + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient); CloseShared(bui, session, activeUis); return true; } @@ -492,7 +492,7 @@ namespace Robust.Server.GameObjects /// public void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage message) { - var msg = new BoundUIWrapMessage(bui.Owner, message, bui.UiKey); + var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey); foreach (var session in bui.SubscribedSessions) { RaiseNetworkEvent(msg, session.ConnectedClient); @@ -518,7 +518,7 @@ namespace Robust.Server.GameObjects if (!bui.SubscribedSessions.Contains(session)) return false; - RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, message, bui.UiKey), session.ConnectedClient); + RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.ConnectedClient); return true; } diff --git a/Robust.Server/GameObjects/IServerEntityManagerInternal.cs b/Robust.Server/GameObjects/IServerEntityManagerInternal.cs index ee682d362..1717eb130 100644 --- a/Robust.Server/GameObjects/IServerEntityManagerInternal.cs +++ b/Robust.Server/GameObjects/IServerEntityManagerInternal.cs @@ -8,7 +8,7 @@ namespace Robust.Server.GameObjects // These methods are used by the map loader to do multi-stage entity construction during map load. // I would recommend you refer to the MapLoader for usage. - EntityUid AllocEntity(EntityPrototype? prototype, EntityUid uid = default); + EntityUid AllocEntity(EntityPrototype? prototype); void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null); diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs index a496f4927..918bda7ef 100644 --- a/Robust.Server/GameObjects/ServerEntityManager.cs +++ b/Robust.Server/GameObjects/ServerEntityManager.cs @@ -42,8 +42,6 @@ namespace Robust.Server.GameObjects private ISawmill _netEntSawmill = default!; - protected override int NextEntityUid { get; set; } = (int) EntityUid.FirstUid; - public override void Initialize() { _netEntSawmill = LogManager.GetSawmill("net.ent"); @@ -54,9 +52,9 @@ namespace Robust.Server.GameObjects base.Initialize(); } - EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype, EntityUid uid) + EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype) { - return AllocEntity(prototype, out _, uid); + return AllocEntity(prototype, out _); } void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context) @@ -79,15 +77,15 @@ namespace Robust.Server.GameObjects StartEntity(entity); } - private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null) + private protected override EntityUid CreateEntity(string? prototypeName, IEntityLoadContext? context = null) { if (prototypeName == null) - return base.CreateEntity(prototypeName, uid, context); + return base.CreateEntity(prototypeName, context); if (!PrototypeManager.TryIndex(prototypeName, out var prototype)) throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}"); - var entity = base.CreateEntity(prototype, uid, context); + var entity = base.CreateEntity(prototype, context); // At this point in time, all data configure on the entity *should* be purely from the prototype. // As such, we can reset the modified ticks to Zero, diff --git a/Robust.Server/GameStates/IServerGameStateManager.cs b/Robust.Server/GameStates/IServerGameStateManager.cs index 281d0ab0f..f2757abe0 100644 --- a/Robust.Server/GameStates/IServerGameStateManager.cs +++ b/Robust.Server/GameStates/IServerGameStateManager.cs @@ -24,6 +24,6 @@ namespace Robust.Server.GameStates Action? ClientAck { get; set; } - Action? ClientRequestFull { get; set; } + Action? ClientRequestFull { get; set; } } } diff --git a/Robust.Server/GameStates/PvsOverrideSystem.cs b/Robust.Server/GameStates/PvsOverrideSystem.cs index 20be0bec3..81eff401a 100644 --- a/Robust.Server/GameStates/PvsOverrideSystem.cs +++ b/Robust.Server/GameStates/PvsOverrideSystem.cs @@ -18,7 +18,7 @@ public sealed class PvsOverrideSystem : EntitySystem /// If true, this will also recursively send any children of the given index. public void AddGlobalOverride(EntityUid uid, bool removeExistingOverride = true, bool recursive = false) { - _pvs.EntityPVSCollection.AddGlobalOverride(uid, removeExistingOverride, recursive); + _pvs.EntityPVSCollection.AddGlobalOverride(GetNetEntity(uid), removeExistingOverride, recursive); } /// @@ -28,7 +28,7 @@ public sealed class PvsOverrideSystem : EntitySystem /// Whether or not to supersede existing overrides. public void AddSessionOverride(EntityUid uid, ICommonSession session,bool removeExistingOverride = true) { - _pvs.EntityPVSCollection.UpdateIndex(uid, session, removeExistingOverride); + _pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), session, removeExistingOverride); } /// @@ -39,6 +39,6 @@ public sealed class PvsOverrideSystem : EntitySystem if (!Resolve(uid, ref xform)) return; - _pvs.EntityPVSCollection.UpdateIndex(uid, xform.Coordinates, true); + _pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), xform.Coordinates, true); } } diff --git a/Robust.Server/GameStates/PvsSystem.Ack.cs b/Robust.Server/GameStates/PvsSystem.Ack.cs index 7ef278e2c..ba2680d4a 100644 --- a/Robust.Server/GameStates/PvsSystem.Ack.cs +++ b/Robust.Server/GameStates/PvsSystem.Ack.cs @@ -45,7 +45,7 @@ internal sealed partial class PvsSystem return; var ackedTick = sessionData.LastReceivedAck; - Dictionary? ackedData; + Dictionary? ackedData; if (sessionData.Overflow != null && sessionData.Overflow.Value.Tick <= ackedTick) { diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs index 44c4e2136..6037a25cd 100644 --- a/Robust.Server/GameStates/PvsSystem.cs +++ b/Robust.Server/GameStates/PvsSystem.cs @@ -24,14 +24,13 @@ namespace Robust.Server.GameStates; internal sealed partial class PvsSystem : EntitySystem { + [Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!; [Shared.IoC.Dependency] private readonly IMapManagerInternal _mapManager = default!; [Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!; - [Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!; - [Shared.IoC.Dependency] private readonly SharedTransformSystem _transform = default!; - [Shared.IoC.Dependency] private readonly IServerNetConfigurationManager _netConfigManager = default!; - [Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!; [Shared.IoC.Dependency] private readonly IParallelManager _parallelManager = default!; - [Shared.IoC.Dependency] private readonly IComponentFactory _factory = default!; + [Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!; + [Shared.IoC.Dependency] private readonly IServerNetConfigurationManager _netConfigManager = default!; + [Shared.IoC.Dependency] private readonly SharedTransformSystem _transform = default!; public const float ChunkSize = 8; @@ -63,31 +62,28 @@ internal sealed partial class PvsSystem : EntitySystem internal readonly Dictionary PlayerData = new(); - private PVSCollection _entityPvsCollection = default!; - public PVSCollection EntityPVSCollection => _entityPvsCollection; + private PVSCollection _entityPvsCollection = default!; + public PVSCollection EntityPVSCollection => _entityPvsCollection; private readonly List _pvsCollections = new(); - private readonly ObjectPool> _visSetPool - = new DefaultObjectPool>( - new DictPolicy(), MaxVisPoolSize); + private readonly ObjectPool> _visSetPool + = new DefaultObjectPool>( + new DictPolicy(), MaxVisPoolSize); - private readonly ObjectPool> _stackPool - = new DefaultObjectPool>( - new StackPolicy(), MaxVisPoolSize); + private readonly ObjectPool> _stackPool + = new DefaultObjectPool>( + new StackPolicy(), MaxVisPoolSize); - private readonly ObjectPool> _uidSetPool - = new DefaultObjectPool>(new SetPolicy(), MaxVisPoolSize); - - private readonly ObjectPool> _chunkCachePool = - new DefaultObjectPool>( - new DictPolicy(), MaxVisPoolSize); + private readonly ObjectPool> _chunkCachePool = + new DefaultObjectPool>( + new DictPolicy(), MaxVisPoolSize); private readonly ObjectPool> _playerChunkPool = new DefaultObjectPool>(new SetPolicy(), MaxVisPoolSize); - private readonly ObjectPool> _treePool = - new DefaultObjectPool>(new TreePolicy(), MaxVisPoolSize); + private readonly ObjectPool> _treePool = + new DefaultObjectPool>(new TreePolicy(), MaxVisPoolSize); private readonly ObjectPool> _mapChunkPool = new DefaultObjectPool>( @@ -102,19 +98,25 @@ internal sealed partial class PvsSystem : EntitySystem private readonly List<(uint, IChunkIndexLocation)> _chunkList = new(64); internal readonly HashSet PendingAcks = new(); + private readonly Dictionary<(uint visMask, IChunkIndexLocation location), (Dictionary metadata, + RobustTree tree)?> _previousTrees = new(); + + private readonly HashSet<(uint visMask, IChunkIndexLocation location)> _reusedTrees = new(); + private EntityQuery _eyeQuery; - private EntityQuery _xformQuery; private EntityQuery _metaQuery; + private EntityQuery _xformQuery; public override void Initialize() { base.Initialize(); _eyeQuery = GetEntityQuery(); + _metaQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); _metaQuery = GetEntityQuery(); - _entityPvsCollection = RegisterPVSCollection(); + _entityPvsCollection = RegisterPVSCollection(); SubscribeLocalEvent(ev => { @@ -159,7 +161,7 @@ internal sealed partial class PvsSystem : EntitySystem } // TODO rate limit this? - private void OnClientRequestFull(ICommonSession session, GameTick tick, EntityUid? missingEntity) + private void OnClientRequestFull(ICommonSession session, GameTick tick, NetEntity? missingEntity) { if (!PlayerData.TryGetValue(session, out var sessionData)) return; @@ -173,7 +175,8 @@ internal sealed partial class PvsSystem : EntitySystem if (missingEntity != null) { - sb.Append($" Apparently they received an entity without metadata: {ToPrettyString(missingEntity.Value)}."); + var entity = GetEntity(missingEntity)!; + sb.Append($" Apparently they received an entity without metadata: {ToPrettyString(entity.Value)}."); if (sessionData.LastSeenAt.TryGetValue(missingEntity.Value, out var lastSeenTick)) sb.Append($" Entity last sent: {lastSeenTick.Value}"); @@ -240,17 +243,17 @@ internal sealed partial class PvsSystem : EntitySystem #region PVSCollection Event Updates - private void OnEntityDeleted(EntityUid e) + private void OnEntityDeleted(EntityUid e, MetaDataComponent metadata) { - _entityPvsCollection.RemoveIndex(EntityManager.CurrentTick, e); + _entityPvsCollection.RemoveIndex(EntityManager.CurrentTick, metadata.NetEntity); var previousTick = _gameTiming.CurTick - 1; foreach (var sessionData in PlayerData.Values) { - sessionData.LastSeenAt.Remove(e); + sessionData.LastSeenAt.Remove(metadata.NetEntity); if (sessionData.SentEntities.TryGetValue(previousTick, out var ents)) - ents.Remove(e); + ents.Remove(metadata.NetEntity); } } @@ -278,7 +281,7 @@ internal sealed partial class PvsSystem : EntitySystem DebugTools.Assert(!_mapManager.IsMap(ev.Sender)); var coordinates = _transform.GetMoverCoordinates(ev.Sender, ev.Component); - UpdateEntityRecursive(ev.Sender, ev.Component, coordinates, false, ev.ParentChanged); + UpdateEntityRecursive(ev.Sender, _metaQuery.GetComponent(ev.Sender), ev.Component, coordinates, false, ev.ParentChanged); } private void OnTransformStartup(EntityUid uid, TransformComponent component, ref TransformStartupEvent args) @@ -295,10 +298,10 @@ internal sealed partial class PvsSystem : EntitySystem DebugTools.Assert(!_mapManager.IsMap(uid)); var coordinates = _transform.GetMoverCoordinates(uid, component); - UpdateEntityRecursive(uid, component, coordinates, false, false); + UpdateEntityRecursive(uid, _metaQuery.GetComponent(uid), component, coordinates, false, false); } - private void UpdateEntityRecursive(EntityUid uid, TransformComponent xform, EntityCoordinates coordinates, bool mover, bool forceDirty) + private void UpdateEntityRecursive(EntityUid uid, MetaDataComponent metadata, TransformComponent xform, EntityCoordinates coordinates, bool mover, bool forceDirty) { if (mover && !xform.LocalPosition.Equals(Vector2.Zero)) { @@ -310,9 +313,9 @@ internal sealed partial class PvsSystem : EntitySystem var indices = PVSCollection.GetChunkIndices(coordinates.Position); if (xform.GridUid != null) - _entityPvsCollection.UpdateIndex(uid, xform.GridUid.Value, indices, forceDirty: forceDirty); + _entityPvsCollection.UpdateIndex(metadata.NetEntity, xform.GridUid.Value, indices, forceDirty: forceDirty); else - _entityPvsCollection.UpdateIndex(uid, xform.MapID, indices, forceDirty: forceDirty); + _entityPvsCollection.UpdateIndex(metadata.NetEntity, xform.MapID, indices, forceDirty: forceDirty); var children = xform.ChildEnumerator; @@ -322,7 +325,7 @@ internal sealed partial class PvsSystem : EntitySystem // directly. while (children.MoveNext(out var child)) { - UpdateEntityRecursive(child.Value, _xformQuery.GetComponent(child.Value), coordinates, true, forceDirty); + UpdateEntityRecursive(child.Value, _metaQuery.GetComponent(child.Value), _xformQuery.GetComponent(child.Value), coordinates, true, forceDirty); } } @@ -386,7 +389,7 @@ internal sealed partial class PvsSystem : EntitySystem pvsCollection.AddGrid(gridId); } - _entityPvsCollection.AddGlobalOverride(gridId, true, false); + _entityPvsCollection.AddGlobalOverride(_metaQuery.GetComponent(gridId).NetEntity, true, false); } private void OnMapDestroyed(MapChangedEvent e) @@ -406,7 +409,7 @@ internal sealed partial class PvsSystem : EntitySystem if(e.Map == MapId.Nullspace) return; var uid = _mapManager.GetMapEntityId(e.Map); - _entityPvsCollection.AddGlobalOverride(uid, true, false); + _entityPvsCollection.AddGlobalOverride(_metaQuery.GetComponent(uid).NetEntity, true, false); } #endregion @@ -533,14 +536,9 @@ internal sealed partial class PvsSystem : EntitySystem return (_chunkList, playerChunks, viewerEntities); } - private Dictionary<(uint visMask, IChunkIndexLocation location), (Dictionary metadata, - RobustTree tree)?> _previousTrees = new(); - - private HashSet<(uint visMask, IChunkIndexLocation location)> _reusedTrees = new(); - public void RegisterNewPreviousChunkTrees( List<(uint, IChunkIndexLocation)> chunks, - (Dictionary metadata, RobustTree tree)?[] trees, + (Dictionary metadata, RobustTree tree)?[] trees, bool[] reuse) { // For any chunks able to re-used we'll chuck them in a dictionary for faster lookup. @@ -584,9 +582,7 @@ internal sealed partial class PvsSystem : EntitySystem public bool TryCalculateChunk( IChunkIndexLocation chunkLocation, uint visMask, - EntityQuery transform, - EntityQuery metadata, - out (Dictionary mData, RobustTree tree)? result) + out (Dictionary mData, RobustTree tree)? result) { if (!_entityPvsCollection.IsDirty(chunkLocation) && _previousTrees.TryGetValue((visMask, chunkLocation), out var previousTree)) { @@ -613,11 +609,12 @@ internal sealed partial class PvsSystem : EntitySystem } var chunkSet = _chunkCachePool.Get(); var tree = _treePool.Get(); - foreach (var uid in chunk) + foreach (var netEntity in chunk) { - AddToChunkSetRecursively(in uid, visMask, tree, chunkSet, transform, metadata); + var uid = GetEntity(netEntity); + AddToChunkSetRecursively(in uid, in netEntity, visMask, tree, chunkSet); #if DEBUG - var xform = transform.GetComponent(uid); + var xform = _xformQuery.GetComponent(uid); if (chunkLocation is MapChunkLocation) DebugTools.Assert(xform.GridUid == null || xform.GridUid == uid); else if (chunkLocation is GridChunkLocation) @@ -649,51 +646,55 @@ internal sealed partial class PvsSystem : EntitySystem } } - private bool AddToChunkSetRecursively(in EntityUid uid, uint visMask, RobustTree tree, Dictionary set, EntityQuery transform, - EntityQuery metadata) + private bool AddToChunkSetRecursively(in EntityUid uid, in NetEntity netEntity, uint visMask, RobustTree tree, Dictionary set) { - if (set.ContainsKey(uid)) + if (set.ContainsKey(netEntity)) return true; - var mComp = metadata.GetComponent(uid); + var mComp = _metaQuery.GetComponent(uid); // TODO: Don't need to know about parents so no longer need to use bool for this method. // If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible. if ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask) return false; - var xform = transform.GetComponent(uid); + var xform = _xformQuery.GetComponent(uid); // is this a map or grid? var isRoot = !xform.ParentUid.IsValid() || uid == xform.GridUid; if (isRoot) { DebugTools.Assert(_mapManager.IsGrid(uid) || _mapManager.IsMap(uid)); - tree.Set(uid); - set.Add(uid, mComp); + tree.Set(netEntity); + set.Add(netEntity, mComp); return true; } DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid)); var parent = xform.ParentUid; - if (!set.ContainsKey(parent) && //was the parent not yet added to toSend? - !AddToChunkSetRecursively(in parent, visMask, tree, set, transform, metadata)) //did we just fail to add the parent? + var parentNetEntity = _metaQuery.GetComponent(parent).NetEntity; + + // TODO performance + // AddToChunkSetRecursively will result in a redundant set.ContainsKey() check. + // This can probably be avoided somehow + if (!set.ContainsKey(parentNetEntity) && //was the parent not yet added to toSend? + !AddToChunkSetRecursively(in parent, in parentNetEntity, visMask, tree, set)) //did we just fail to add the parent? + { return false; //we failed? suppose we dont get added either + } //i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles - tree.Set(uid, parent); - set.Add(uid, mComp); + tree.Set(netEntity, parentNetEntity); + set.Add(netEntity, mComp); return true; } - internal (List? updates, List? deletions, List? leftPvs, GameTick fromTick) + internal (List? updates, List? deletions, List? leftPvs, GameTick fromTick) CalculateEntityStates(IPlayerSession session, GameTick fromTick, GameTick toTick, - EntityQuery mQuery, - EntityQuery tQuery, - (Dictionary metadata, RobustTree tree)?[] chunks, + (Dictionary metadata, RobustTree tree)?[] chunks, HashSet visibleChunks, EntityUid[] viewers) { @@ -712,7 +713,6 @@ internal sealed partial class PvsSystem : EntitySystem throw new Exception("Encountered non-empty object inside of _visSetPool. Was the same object returned to the pool more than once?"); var deletions = _entityPvsCollection.GetDeletedIndices(fromTick); - var entStateCount = 0; var stack = _stackPool.Get(); @@ -726,14 +726,15 @@ internal sealed partial class PvsSystem : EntitySystem // Each root nodes should simply be a map or a grid entity. DebugTools.Assert(cache.Value.tree.RootNodes.Count == 1, $"Root node count is {cache.Value.tree.RootNodes.Count} instead of 1. Session: {session}"); - var ent = cache.Value.tree.RootNodes.FirstOrDefault(); + var nent = cache.Value.tree.RootNodes.FirstOrDefault(); + var ent = GetEntity(nent); DebugTools.Assert(Exists(ent), $"Root node does not exist. Node {ent}. Session: {session}"); DebugTools.Assert(HasComp(ent) || HasComp(ent)); #endif foreach (var rootNode in cache.Value.tree.RootNodes) { - RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, cache.Value.metadata, stack, in fromTick, + RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, stack, in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget); } } @@ -742,8 +743,9 @@ internal sealed partial class PvsSystem : EntitySystem var globalEnumerator = _entityPvsCollection.GlobalOverridesEnumerator; while (globalEnumerator.MoveNext()) { - var uid = globalEnumerator.Current; - RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick, + var netEntity = globalEnumerator.Current; + var uid = GetEntity(netEntity); + RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget); } globalEnumerator.Dispose(); @@ -752,8 +754,9 @@ internal sealed partial class PvsSystem : EntitySystem var globalRecursiveEnumerator = _entityPvsCollection.GlobalRecursiveOverridesEnumerator; while (globalRecursiveEnumerator.MoveNext()) { - var uid = globalRecursiveEnumerator.Current; - RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick, + var netEntity = globalRecursiveEnumerator.Current; + var uid = GetEntity(netEntity); + RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true); } globalRecursiveEnumerator.Dispose(); @@ -761,15 +764,16 @@ internal sealed partial class PvsSystem : EntitySystem var localEnumerator = _entityPvsCollection.GetElementsForSession(session); while (localEnumerator.MoveNext()) { - var uid = localEnumerator.Current; - RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick, + var netEntity = localEnumerator.Current; + var uid = GetEntity(netEntity); + RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget); } localEnumerator.Dispose(); foreach (var viewerEntity in viewers) { - RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick, + RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget); } @@ -777,27 +781,32 @@ internal sealed partial class PvsSystem : EntitySystem RaiseLocalEvent(ref expandEvent); foreach (var entityUid in expandEvent.Entities) { - RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick, + RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget); } foreach (var entityUid in expandEvent.RecursiveEntities) { - RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick, + RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick, ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true); } var entityStates = new List(entStateCount); - foreach (var (uid, visiblity) in visibleEnts) + foreach (var (netEntity, visiblity) in visibleEnts) { + var uid = GetEntity(netEntity); + +#if DEBUG // if an entity is visible, its parents should always be visible. - DebugTools.Assert((tQuery.GetComponent(uid).ParentUid is not { Valid: true } parent) || visibleEnts.ContainsKey(parent), + DebugTools.Assert((_xformQuery.GetComponent(uid).ParentUid is not { Valid: true } parent) || + visibleEnts.ContainsKey(_metaQuery.GetComponent(parent).NetEntity), $"Attempted to send an entity without sending it's parents. Entity: {ToPrettyString(uid)}."); +#endif if (sessionData.RequestedFull) { - entityStates.Add(GetFullEntityState(session, uid, mQuery.GetComponent(uid))); + entityStates.Add(GetFullEntityState(session, uid, _metaQuery.GetComponent(uid))); continue; } @@ -805,8 +814,8 @@ internal sealed partial class PvsSystem : EntitySystem continue; var entered = visiblity == PvsEntityVisibility.Entered; - var entFromTick = entered ? lastSeen.GetValueOrDefault(uid) : fromTick; - var state = GetEntityState(session, uid, entFromTick, mQuery.GetComponent(uid)); + var entFromTick = entered ? lastSeen.GetValueOrDefault(netEntity) : fromTick; + var state = GetEntityState(session, uid, entFromTick, _metaQuery.GetComponent(uid)); if (entered || !state.Empty) entityStates.Add(state); @@ -850,31 +859,30 @@ internal sealed partial class PvsSystem : EntitySystem /// Figure out what entities are no longer visible to the client. These entities are sent reliably to the client /// in a separate net message. /// - private List? ProcessLeavePVS( - Dictionary visibleEnts, - Dictionary? lastSent) + private List? ProcessLeavePVS( + Dictionary visibleEnts, + Dictionary? lastSent) { if (lastSent == null) return null; - var leftView = new List(); - foreach (var uid in lastSent.Keys) + var leftView = new List(); + foreach (var netEntity in lastSent.Keys) { - if (!visibleEnts.ContainsKey(uid)) - leftView.Add(uid); + if (!visibleEnts.ContainsKey(netEntity)) + leftView.Add(netEntity); } return leftView.Count > 0 ? leftView : null; } - private void RecursivelyAddTreeNode(in EntityUid nodeIndex, - RobustTree tree, - Dictionary? lastAcked, - Dictionary? lastSent, - Dictionary toSend, - Dictionary lastSeen, - Dictionary metaDataCache, - Stack stack, + private void RecursivelyAddTreeNode(in NetEntity nodeIndex, + RobustTree tree, + Dictionary? lastAcked, + Dictionary? lastSent, + Dictionary toSend, + Dictionary lastSeen, + Stack stack, in GameTick fromTick, ref int newEntityCount, ref int enteredEntityCount, @@ -899,7 +907,8 @@ internal sealed partial class PvsSystem : EntitySystem if (!shouldAdd) continue; - AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], toSend, fromTick, in entered, ref entStateCount); + var entity = GetEntity(currentNodeIndex); + AddToSendSet(in currentNodeIndex, _metaQuery.GetComponent(entity), toSend, fromTick, in entered, ref entStateCount); } var node = tree[currentNodeIndex]; @@ -914,12 +923,10 @@ internal sealed partial class PvsSystem : EntitySystem } public bool RecursivelyAddOverride(in EntityUid uid, - Dictionary? lastAcked, - Dictionary? lastSent, - Dictionary toSend, - Dictionary lastSeen, - in EntityQuery metaQuery, - in EntityQuery transQuery, + Dictionary? lastAcked, + Dictionary? lastSent, + Dictionary toSend, + Dictionary lastSeen, in GameTick fromTick, ref int newEntityCount, ref int enteredEntityCount, @@ -933,25 +940,31 @@ internal sealed partial class PvsSystem : EntitySystem if (!uid.IsValid()) return false; - var xform = transQuery.GetComponent(uid); + var xform = _xformQuery.GetComponent(uid); var parent = xform.ParentUid; - if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in metaQuery, in transQuery, in fromTick, - ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget)) + if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in fromTick, + ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, + in enteredEntityBudget)) + { return false; + } + + var metadata = _metaQuery.GetComponent(uid); + var netEntity = GetNetEntity(uid, metadata); //did we already get added? // Note that we check this AFTER adding parents. This is because while this entity may already have been added // to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported // to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also // send the new parents, which may otherwise be delayed because of the PVS budget.. - if (!toSend.ContainsKey(uid)) + if (!toSend.ContainsKey(netEntity)) { // TODO PERFORMANCE. // ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this // point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this // is just the player's own entity + maybe a singularity. So currently not all that performance intensive. - var (entered, _) = ProcessEntry(in uid, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget); - AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount); + var (entered, _) = ProcessEntry(in netEntity, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget); + AddToSendSet(in netEntity, _metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount); } if (addChildren) @@ -964,10 +977,10 @@ internal sealed partial class PvsSystem : EntitySystem } private void RecursivelyAddChildren(TransformComponent xform, - Dictionary? lastAcked, - Dictionary? lastSent, - Dictionary toSend, - Dictionary lastSeen, + Dictionary? lastAcked, + Dictionary? lastSent, + Dictionary toSend, + Dictionary lastSeen, in GameTick fromTick, ref int newEntityCount, ref int enteredEntityCount, @@ -980,12 +993,15 @@ internal sealed partial class PvsSystem : EntitySystem if (!_xformQuery.TryGetComponent(child, out var childXform)) continue; - if (!toSend.ContainsKey(child)) + var metadata = _metaQuery.GetComponent(child); + var childNetEntity = GetNetEntity(child, metadata); + + if (!toSend.ContainsKey(childNetEntity)) { - var (entered, _) = ProcessEntry(in child, lastAcked, lastSent, lastSeen, ref newEntityCount, + var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget); - AddToSendSet(in child, _metaQuery.GetComponent(child), toSend, fromTick, in entered, ref entStateCount); + AddToSendSet(in childNetEntity, metadata, toSend, fromTick, in entered, ref entStateCount); } RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount, @@ -993,19 +1009,20 @@ internal sealed partial class PvsSystem : EntitySystem } } - private (bool Entered, bool ShouldAdd) ProcessEntry(in EntityUid uid, - Dictionary? lastAcked, - Dictionary? lastSent, - Dictionary lastSeen, + private (bool Entered, bool ShouldAdd) ProcessEntry( + in NetEntity netEntity, + Dictionary? lastAcked, + Dictionary? lastSent, + Dictionary lastSeen, ref int newEntityCount, ref int enteredEntityCount, in int newEntityBudget, in int enteredEntityBudget) { - var enteredSinceLastSent = lastSent == null || !lastSent.ContainsKey(uid); + var enteredSinceLastSent = lastSent == null || !lastSent.ContainsKey(netEntity); var entered = enteredSinceLastSent || // OR, entered since last ack: - lastAcked == null || !lastAcked.ContainsKey(uid); + lastAcked == null || !lastAcked.ContainsKey(netEntity); // If the entity is entering, but we already sent this entering entity in the last message, we won't add it to // the budget. Chances are the packet will arrive in a nice and orderly fashion, and the client will stick to @@ -1020,25 +1037,25 @@ internal sealed partial class PvsSystem : EntitySystem return (entered, false); enteredEntityCount++; - if (!lastSeen.ContainsKey(uid)) + if (!lastSeen.ContainsKey(netEntity)) newEntityCount++; } return (entered, true); } - private void AddToSendSet(in EntityUid uid, MetaDataComponent metaDataComponent, Dictionary toSend, GameTick fromTick, in bool entered, ref int entStateCount) + private void AddToSendSet(in NetEntity netEntity, MetaDataComponent metaDataComponent, Dictionary toSend, GameTick fromTick, in bool entered, ref int entStateCount) { if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating) { - var rep = new EntityStringRepresentation(uid, metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID); + var rep = new EntityStringRepresentation(GetEntity(netEntity), metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID); Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}"); return; } if (entered) { - toSend.Add(uid, PvsEntityVisibility.Entered); + toSend.Add(netEntity, PvsEntityVisibility.Entered); entStateCount++; return; } @@ -1046,22 +1063,22 @@ internal sealed partial class PvsSystem : EntitySystem if (metaDataComponent.EntityLastModifiedTick <= fromTick) { //entity has been sent before and hasnt been updated since - toSend.Add(uid, PvsEntityVisibility.StayedUnchanged); + toSend.Add(netEntity, PvsEntityVisibility.StayedUnchanged); return; } //add us - toSend.Add(uid, PvsEntityVisibility.StayedChanged); + toSend.Add(netEntity, PvsEntityVisibility.StayedChanged); entStateCount++; } /// /// Gets all entity states that have been modified after and including the provided tick. /// - public (List?, List?, GameTick fromTick) GetAllEntityStates(ICommonSession? player, GameTick fromTick, GameTick toTick) + public (List?, List?, GameTick fromTick) GetAllEntityStates(ICommonSession? player, GameTick fromTick, GameTick toTick) { List? stateEntities; - var toSend = _uidSetPool.Get(); + var toSend = new HashSet(); DebugTools.Assert(toSend.Count == 0); bool enumerateAll = false; @@ -1096,7 +1113,7 @@ internal sealed partial class PvsSystem : EntitySystem if (state.Empty) { - Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities. + Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities. Tick: {fromTick}--{_gameTiming.CurTick} Entity: {ToPrettyString(uid)} Last modified: {md.EntityLastModifiedTick} @@ -1163,7 +1180,6 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); } } - _uidSetPool.Return(toSend); var deletions = _entityPvsCollection.GetDeletedIndices(fromTick); if (stateEntities.Count == 0) @@ -1221,7 +1237,8 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); } DebugTools.Assert(meta.EntityLastModifiedTick >= meta.LastComponentRemoved); - var entState = new EntityState(entityUid, changed, meta.EntityLastModifiedTick, netComps); + DebugTools.Assert(GetEntity(meta.NetEntity) == entityUid); + var entState = new EntityState(meta.NetEntity, changed, meta.EntityLastModifiedTick, netComps); return entState; } @@ -1253,7 +1270,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); netComps.Add(netId); } - var entState = new EntityState(entityUid, changed, meta.EntityLastModifiedTick, netComps); + var entState = new EntityState(meta.NetEntity, changed, meta.EntityLastModifiedTick, netComps); return entState; } @@ -1263,14 +1280,13 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); if (session.Status != SessionStatus.InGame) return Array.Empty(); - var viewers = _uidSetPool.Get(); + var viewers = new HashSet(); if (session.AttachedEntity != null) { // Fast path if (session is IPlayerSession { ViewSubscriptionCount: 0 }) { - _uidSetPool.Return(viewers); return new[] { session.AttachedEntity.Value }; } @@ -1288,7 +1304,6 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); var viewerArray = viewers.ToArray(); - _uidSetPool.Return(viewers); return viewerArray; } @@ -1336,23 +1351,23 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); /// /// All s that this session saw during the last ticks. /// - public readonly OverflowDictionary> SentEntities = new(DirtyBufferSize); + public readonly OverflowDictionary> SentEntities = new(DirtyBufferSize); /// /// The most recently acked entities /// - public (GameTick Tick, Dictionary Data)? LastAcked; + public (GameTick Tick, Dictionary Data)? LastAcked; /// /// Stores the last tick at which a given entity was acked by a player. Used to avoid re-sending the whole entity /// state when an item re-enters PVS. /// - public readonly Dictionary LastSeenAt = new(); + public readonly Dictionary LastSeenAt = new(); /// /// overflow in case a player's last ack is more than ticks behind the current tick. /// - public (GameTick Tick, Dictionary SentEnts)? Overflow; + public (GameTick Tick, Dictionary SentEnts)? Overflow; /// /// If true, the client has explicitly requested a full state. Unlike the first state, we will send them diff --git a/Robust.Server/GameStates/ServerGameStateManager.cs b/Robust.Server/GameStates/ServerGameStateManager.cs index 857908504..d6029e5ee 100644 --- a/Robust.Server/GameStates/ServerGameStateManager.cs +++ b/Robust.Server/GameStates/ServerGameStateManager.cs @@ -63,7 +63,7 @@ namespace Robust.Server.GameStates public ushort TransformNetId { get; set; } public Action? ClientAck { get; set; } - public Action? ClientRequestFull { get; set; } + public Action? ClientRequestFull { get; set; } public void PostInject() { @@ -134,7 +134,7 @@ namespace Robust.Server.GameStates if (!_playerManager.TryGetSessionById(msg.MsgChannel.UserId, out var session)) return; - EntityUid? ent = msg.MissingEntity.IsValid() ? msg.MissingEntity : null; + NetEntity? ent = msg.MissingEntity.IsValid() ? msg.MissingEntity : null; ClientRequestFull?.Invoke(session, msg.Tick, ent); } @@ -245,7 +245,7 @@ namespace Robust.Server.GameStates { public HashSet[] PlayerChunks; public EntityUid[][] ViewerEntities; - public (Dictionary metadata, RobustTree tree)?[] ChunkCache; + public (Dictionary metadata, RobustTree tree)?[] ChunkCache; } private PvsData? GetPVSData(IPlayerSession[] players) @@ -255,13 +255,11 @@ namespace Robust.Server.GameStates var chunksCount = chunks.Count; var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize); var chunkCache = - new (Dictionary metadata, RobustTree tree)?[chunksCount]; + new (Dictionary metadata, RobustTree tree)?[chunksCount]; // Update the reused trees sequentially to avoid having to lock the dictionary per chunk. var reuse = ArrayPool.Shared.Rent(chunksCount); - var transformQuery = _entityManager.GetEntityQuery(); - var metadataQuery = _entityManager.GetEntityQuery(); Parallel.For(0, chunkBatches, new ParallelOptions { MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount }, i => @@ -272,8 +270,7 @@ namespace Robust.Server.GameStates for (var j = start; j < end; ++j) { var (visMask, chunkIndexLocation) = chunks[j]; - reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery, - out var chunk); + reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, out var chunk); chunkCache[j] = chunk; #if DEBUG @@ -283,7 +280,8 @@ namespace Robust.Server.GameStates // Each root nodes should simply be a map or a grid entity. DebugTools.Assert(chunk.Value.tree.RootNodes.Count == 1, $"Root node count is {chunk.Value.tree.RootNodes.Count} instead of 1."); - var ent = chunk.Value.tree.RootNodes.FirstOrDefault(); + var nent = chunk.Value.tree.RootNodes.FirstOrDefault(); + var ent = _entityManager.GetEntity(nent); DebugTools.Assert(_entityManager.EntityExists(ent), $"Root node does not exist. Node {ent}."); DebugTools.Assert(_entityManager.HasComponent(ent) || _entityManager.HasComponent(ent)); @@ -313,9 +311,9 @@ namespace Robust.Server.GameStates var channel = session.ConnectedClient; var sessionData = _pvs.PlayerData[session]; var lastAck = sessionData.LastReceivedAck; - List? leftPvs = null; + List? leftPvs = null; List? entStates; - List? deletions; + List? deletions; GameTick fromTick; DebugTools.Assert(_pvs.CullingEnabled == (pvsData != null)); @@ -325,8 +323,6 @@ namespace Robust.Server.GameStates session, lastAck, _gameTiming.CurTick, - mQuery, - tQuery, pvsData.Value.ChunkCache, pvsData.Value.PlayerChunks[i], pvsData.Value.ViewerEntities[i]); diff --git a/Robust.Server/Physics/GridFixtureSystem.cs b/Robust.Server/Physics/GridFixtureSystem.cs index 62436e285..22cf6c2bc 100644 --- a/Robust.Server/Physics/GridFixtureSystem.cs +++ b/Robust.Server/Physics/GridFixtureSystem.cs @@ -130,7 +130,7 @@ namespace Robust.Server.Physics var msg = new ChunkSplitDebugMessage { - Grid = uid, + Grid = GetNetEntity(uid), }; foreach (var (index, group) in _nodes[uid]) diff --git a/Robust.Server/Physics/JointSystem.cs b/Robust.Server/Physics/JointSystem.cs index 7ab7f69ba..ef05188c2 100644 --- a/Robust.Server/Physics/JointSystem.cs +++ b/Robust.Server/Physics/JointSystem.cs @@ -21,9 +21,9 @@ public sealed class JointSystem : SharedJointSystem foreach (var (id, joint) in component.Joints) { - states.Add(id, joint.GetState()); + states.Add(id, joint.GetState(EntityManager)); } - args.State = new JointComponentState(component.Relay, states); + args.State = new JointComponentState(GetNetEntity(component.Relay), states); } } diff --git a/Robust.Server/Placement/PlacementManager.cs b/Robust.Server/Placement/PlacementManager.cs index b2ab64a91..b18970d9a 100644 --- a/Robust.Server/Placement/PlacementManager.cs +++ b/Robust.Server/Placement/PlacementManager.cs @@ -98,8 +98,10 @@ namespace Robust.Server.Placement return; //TODO: Distance check, so you can't place things off of screen. + // I don't think that's this manager's biggest problem - var coordinates = msg.EntityCoordinates; + var netCoordinates = msg.NetCoordinates; + var coordinates = _entityManager.GetCoordinates(netCoordinates); if (!coordinates.IsValid(_entityManager)) { @@ -211,17 +213,19 @@ namespace Robust.Server.Placement private void HandleEntRemoveReq(MsgPlacement msg) { //TODO: Some form of admin check - if (!_entityManager.EntityExists(msg.EntityUid)) + var entity = _entityManager.GetEntity(msg.EntityUid); + + if (!_entityManager.EntityExists(entity)) return; - var placementEraseEvent = new PlacementEntityEvent(msg.EntityUid, _entityManager.GetComponent(msg.EntityUid).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId); + var placementEraseEvent = new PlacementEntityEvent(entity, _entityManager.GetComponent(entity).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId); _entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent); - _entityManager.DeleteEntity(msg.EntityUid); + _entityManager.DeleteEntity(entity); } private void HandleRectRemoveReq(MsgPlacement msg) { - EntityCoordinates start = msg.EntityCoordinates; + EntityCoordinates start = _entityManager.GetCoordinates(msg.NetCoordinates); Vector2 rectSize = msg.RectSize; foreach (EntityUid entity in EntitySystem.Get().GetEntitiesIntersecting(start.GetMapId(_entityManager), new Box2(start.Position, start.Position + rectSize))) diff --git a/Robust.Server/Player/PlayerSession.cs b/Robust.Server/Player/PlayerSession.cs index b5f217ac0..abec3d8c2 100644 --- a/Robust.Server/Player/PlayerSession.cs +++ b/Robust.Server/Player/PlayerSession.cs @@ -210,7 +210,7 @@ namespace Robust.Server.Player { PlayerState.Status = Status; PlayerState.Name = Name; - PlayerState.ControlledEntity = AttachedEntity; + PlayerState.ControlledEntity = IoCManager.Resolve().GetNetEntity(AttachedEntity); _playerManager.Dirty(); } diff --git a/Robust.Server/ViewVariables/ServerViewVariablesManager.cs b/Robust.Server/ViewVariables/ServerViewVariablesManager.cs index 6b2e68ebf..cbf1a5342 100644 --- a/Robust.Server/ViewVariables/ServerViewVariablesManager.cs +++ b/Robust.Server/ViewVariables/ServerViewVariablesManager.cs @@ -125,8 +125,10 @@ namespace Robust.Server.ViewVariables case ViewVariablesComponentSelector componentSelector: { var compType = _reflectionManager.GetType(componentSelector.ComponentType); + var entity = _entityManager.GetEntity(componentSelector.Entity); + if (compType == null || - !_entityManager.TryGetComponent(componentSelector.Entity, compType, out var component)) + !_entityManager.TryGetComponent(entity, compType, out var component)) { Deny(ViewVariablesResponseCode.NoObject); return; @@ -137,7 +139,9 @@ namespace Robust.Server.ViewVariables } case ViewVariablesEntitySelector entitySelector: { - if (!_entityManager.EntityExists(entitySelector.Entity)) + var entity = _entityManager.GetEntity(entitySelector.Entity); + + if (!_entityManager.EntityExists(entity)) { Deny(ViewVariablesResponseCode.NoObject); return; diff --git a/Robust.Server/ViewVariables/Traits/ViewVariablesTraitEntity.cs b/Robust.Server/ViewVariables/Traits/ViewVariablesTraitEntity.cs index 726f10cec..fc52df020 100644 --- a/Robust.Server/ViewVariables/Traits/ViewVariablesTraitEntity.cs +++ b/Robust.Server/ViewVariables/Traits/ViewVariablesTraitEntity.cs @@ -14,7 +14,8 @@ namespace Robust.Server.ViewVariables.Traits public ViewVariablesTraitEntity(IViewVariablesSession session) : base(session) { - _entity = (EntityUid) Session.Object; + var netEntity = (NetEntity) Session.Object; + _entity = IoCManager.Resolve().GetEntity(netEntity); } public override ViewVariablesBlob? DataRequest(ViewVariablesRequest viewVariablesRequest) diff --git a/Robust.Server/ViewVariables/Traits/ViewVariablesTraitMembers.cs b/Robust.Server/ViewVariables/Traits/ViewVariablesTraitMembers.cs index 436b9971d..62255aa8a 100644 --- a/Robust.Server/ViewVariables/Traits/ViewVariablesTraitMembers.cs +++ b/Robust.Server/ViewVariables/Traits/ViewVariablesTraitMembers.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Prototypes; @@ -27,10 +28,11 @@ namespace Robust.Server.ViewVariables.Traits if (messageRequestMeta is ViewVariablesRequestMembers) { var members = new List<(MemberData mData, MemberInfo mInfo)>(); + var obj = Session.Object; + var objType = Session.ObjectType; - foreach (var property in Session.ObjectType.GetAllProperties()) + foreach (var property in objType.GetAllProperties()) { - if (!ViewVariablesUtility.TryGetViewVariablesAccess(property, out var access)) { continue; @@ -53,7 +55,7 @@ namespace Robust.Server.ViewVariables.Traits _members.Add(property); } - foreach (var field in Session.ObjectType.GetAllFields()) + foreach (var field in objType.GetAllFields()) { if (!ViewVariablesUtility.TryGetViewVariablesAccess(field, out var access)) { @@ -66,7 +68,7 @@ namespace Robust.Server.ViewVariables.Traits Name = field.Name, Type = field.FieldType.AssemblyQualifiedName, TypePretty = PrettyPrint.PrintUserFacingTypeShort(field.FieldType, 2), - Value = field.GetValue(Session.Object), + Value = field.GetValue(obj), PropertyIndex = _members.Count }, field)); diff --git a/Robust.Shared.CompNetworkGenerator/ComponentNetworkGenerator.cs b/Robust.Shared.CompNetworkGenerator/ComponentNetworkGenerator.cs index 20e1da16a..892c59f69 100644 --- a/Robust.Shared.CompNetworkGenerator/ComponentNetworkGenerator.cs +++ b/Robust.Shared.CompNetworkGenerator/ComponentNetworkGenerator.cs @@ -14,9 +14,14 @@ namespace Robust.Shared.CompNetworkGenerator { private const string ClassAttributeName = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute"; private const string MemberAttributeName = "Robust.Shared.Analyzers.AutoNetworkedFieldAttribute"; + private const string GlobalEntityUidName = "global::Robust.Shared.GameObjects.EntityUid"; + private const string GlobalNullableEntityUidName = "global::Robust.Shared.GameObjects.EntityUid?"; + private const string GlobalEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates?"; + private const string GlobalNullableEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates"; private static string GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle) { + // Debugger.Launch(); var nameSpace = classSymbol.ContainingNamespace.ToDisplayString(); var componentName = classSymbol.Name; var stateName = $"{componentName}_AutoState"; @@ -115,27 +120,59 @@ namespace Robust.Shared.CompNetworkGenerator foreach (var (type, name, attribute) in fields) { - stateFields.Append($@" - public {type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {name} = default!;"); + var typeDisplayStr = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var nullable = type.NullableAnnotation == NullableAnnotation.Annotated; + var nullableAnnotation = nullable ? "?" : string.Empty; - // get first ctor arg of the field attribute, which determines whether the field should be cloned - // (like if its a dict or list) - if (attribute.ConstructorArguments[0].Value is bool val && val) + switch (typeDisplayStr) { - getStateInit.Append($@" + case GlobalEntityUidName: + case GlobalNullableEntityUidName: + stateFields.Append($@" + public NetEntity{nullableAnnotation} {name} = default!;"); + + getStateInit.Append($@" + {name} = GetNetEntity(component.{name}),"); + handleStateSetters.Append($@" + component.{name} = EnsureEntity<{componentName}>(state.{name}, uid);"); + + break; + case GlobalEntityCoordinatesName: + case GlobalNullableEntityCoordinatesName: + stateFields.Append($@" + public NetCoordinates{nullableAnnotation} {name} = default!;"); + + getStateInit.Append($@" + {name} = GetNetCoordinates(component.{name}),"); + handleStateSetters.Append($@" + component.{name} = EnsureCoordinates<{componentName}>(state.{name}, uid);"); + + break; + default: + stateFields.Append($@" + public {typeDisplayStr} {name} = default!;"); + + if (attribute.ConstructorArguments[0].Value is bool val && val) + { + // get first ctor arg of the field attribute, which determines whether the field should be cloned + // (like if its a dict or list) + getStateInit.Append($@" {name} = component.{name},"); - handleStateSetters.Append($@" + handleStateSetters.Append($@" if (state.{name} != null) component.{name} = new(state.{name});"); - } - else - { - getStateInit.Append($@" + } + else + { + getStateInit.Append($@" {name} = component.{name},"); - handleStateSetters.Append($@" + handleStateSetters.Append($@" component.{name} = state.{name};"); + } + + break; } } @@ -153,6 +190,7 @@ using Robust.Shared.GameStates; using Robust.Shared.GameObjects; using Robust.Shared.Analyzers; using Robust.Shared.Serialization; +using Robust.Shared.Map; namespace {nameSpace}; diff --git a/Robust.Shared/Console/Commands/DumpEventTablesCommand.cs b/Robust.Shared/Console/Commands/DumpEventTablesCommand.cs index 516fb2a0d..aa185506a 100644 --- a/Robust.Shared/Console/Commands/DumpEventTablesCommand.cs +++ b/Robust.Shared/Console/Commands/DumpEventTablesCommand.cs @@ -19,7 +19,9 @@ internal sealed class DumpEventTablesCommand : LocalizedCommands return; } - if (!EntityUid.TryParse(args[0], out var entity) || !_entities.EntityExists(entity)) + if (!NetEntity.TryParse(args[0], out var entityNet) || + !_entities.TryGetEntity(entityNet, out var entity) || + !_entities.EntityExists(entity)) { shell.WriteError(Loc.GetString("cmd-dump_event_tables-error-entity")); return; @@ -27,7 +29,7 @@ internal sealed class DumpEventTablesCommand : LocalizedCommands var eventBus = (EntityEventBus)_entities.EventBus; - var table = eventBus._entEventTables[entity]; + var table = eventBus._entEventTables[entity.Value]; foreach (var (evType, comps) in table.EventIndices) { shell.WriteLine($"{evType}:"); diff --git a/Robust.Shared/Console/Commands/MapCommands.cs b/Robust.Shared/Console/Commands/MapCommands.cs index 225808a4c..17d9a671d 100644 --- a/Robust.Shared/Console/Commands/MapCommands.cs +++ b/Robust.Shared/Console/Commands/MapCommands.cs @@ -68,6 +68,7 @@ sealed class RemoveMapCommand : LocalizedCommands sealed class RemoveGridCommand : LocalizedCommands { + [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IMapManager _map = default!; public override string Command => "rmgrid"; @@ -81,15 +82,15 @@ sealed class RemoveGridCommand : LocalizedCommands return; } - var gridId = EntityUid.Parse(args[0]); + var gridIdNet = NetEntity.Parse(args[0]); - if (!_map.GridExists(gridId)) + if (!_entManager.TryGetEntity(gridIdNet, out var gridId) || !_map.GridExists(gridId)) { shell.WriteError($"Grid {gridId} does not exist."); return; } - _map.DeleteGrid(gridId); + _map.DeleteGrid(gridId.Value); shell.WriteLine($"Grid {gridId} was removed."); } } diff --git a/Robust.Shared/Console/Commands/SpawnCommand.cs b/Robust.Shared/Console/Commands/SpawnCommand.cs index e00a75924..28f82a05e 100644 --- a/Robust.Shared/Console/Commands/SpawnCommand.cs +++ b/Robust.Shared/Console/Commands/SpawnCommand.cs @@ -32,8 +32,8 @@ public sealed class SpawnCommand : LocalizedCommands } else if (args.Length == 2) { - var uid = EntityUid.Parse(args[1]); - var entityCoordinates = _entityManager.GetComponent(uid).Coordinates; + var uidNet = NetEntity.Parse(args[1]); + var entityCoordinates = _entityManager.GetComponent(_entityManager.GetEntity(uidNet)).Coordinates; var createdEntity = _entityManager.SpawnEntity(args[0], entityCoordinates); placementEv = new PlacementEntityEvent(createdEntity, entityCoordinates, PlacementEventAction.Create, shell.Player?.UserId); } diff --git a/Robust.Shared/Console/Commands/TeleportCommands.cs b/Robust.Shared/Console/Commands/TeleportCommands.cs index 4b0e64b61..b9234c6f5 100644 --- a/Robust.Shared/Console/Commands/TeleportCommands.cs +++ b/Robust.Shared/Console/Commands/TeleportCommands.cs @@ -125,7 +125,7 @@ public sealed class TeleportToCommand : LocalizedCommands [NotNullWhen(true)] out EntityUid? victimUid, [NotNullWhen(true)] out TransformComponent? transform) { - if (EntityUid.TryParse(str, out var uid) && _entities.TryGetComponent(uid, out transform)) + if (NetEntity.TryParse(str, out var uidNet) && _entities.TryGetEntity(uidNet, out var uid) && _entities.TryGetComponent(uid, out transform)) { victimUid = uid; return true; @@ -160,10 +160,10 @@ public sealed class TeleportToCommand : LocalizedCommands hint = Loc.GetString(hint); var opts = CompletionResult.FromHintOptions(users, hint); - if (last != string.Empty && !EntityUid.TryParse(last, out _)) + if (last != string.Empty && !NetEntity.TryParse(last, out _)) return opts; - return CompletionResult.FromHintOptions(opts.Options.Concat(CompletionHelper.EntityUids(last, _entities)), hint); + return CompletionResult.FromHintOptions(opts.Options.Concat(CompletionHelper.NetEntities(last, _entities)), hint); } } @@ -201,11 +201,11 @@ sealed class TpGridCommand : LocalizedCommands return; } - var gridId = EntityUid.Parse(args[0]); + var gridIdNet = NetEntity.Parse(args[0]); var xPos = float.Parse(args[1], CultureInfo.InvariantCulture); var yPos = float.Parse(args[2], CultureInfo.InvariantCulture); - if (!_ent.EntityExists(gridId)) + if (!_ent.TryGetEntity(gridIdNet, out var gridId) || !_ent.EntityExists(gridId)) { shell.WriteError($"Entity does not exist: {args[0]}"); return; @@ -217,7 +217,7 @@ sealed class TpGridCommand : LocalizedCommands return; } - var gridXform = _ent.GetComponent(gridId); + var gridXform = _ent.GetComponent(gridId.Value); var mapId = args.Length == 4 ? new MapId(int.Parse(args[3])) : gridXform.MapID; gridXform.Coordinates = new EntityCoordinates(_map.GetMapEntityId(mapId), new Vector2(xPos, yPos)); diff --git a/Robust.Shared/Console/CompletionHelper.cs b/Robust.Shared/Console/CompletionHelper.cs index 625c8ea73..464b7a1d4 100644 --- a/Robust.Shared/Console/CompletionHelper.cs +++ b/Robust.Shared/Console/CompletionHelper.cs @@ -155,18 +155,21 @@ public static class CompletionHelper } } - public static IEnumerable EntityUids(string text, IEntityManager? entManager = null) + public static IEnumerable NetEntities(string text, IEntityManager? entManager = null) { IoCManager.Resolve(ref entManager); foreach (var ent in entManager.GetEntities()) { - var entString = ent.ToString(); - - if (!entString.StartsWith(text)) + if (!entManager.TryGetNetEntity(ent, out var netEntity)) continue; - yield return new CompletionOption(entString); + var netString = netEntity.Value.ToString(); + + if (!netString.StartsWith(text)) + continue; + + yield return new CompletionOption(netString); } } @@ -174,14 +177,19 @@ public static class CompletionHelper { IoCManager.Resolve(ref entManager); - var query = entManager.AllEntityQueryEnumerator(); + var query = entManager.AllEntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _)) + while (query.MoveNext(out var uid, out _, out var metadata)) { - if (!uid.ToString().StartsWith(text)) + if (!entManager.TryGetNetEntity(uid, out var netEntity, metadata: metadata)) continue; - yield return new CompletionOption(uid.ToString()); + var netString = netEntity.Value.ToString(); + + if (!netString.StartsWith(text)) + continue; + + yield return new CompletionOption(netString); } } } diff --git a/Robust.Shared/Containers/BaseContainer.cs b/Robust.Shared/Containers/BaseContainer.cs index 088182304..d4b5f132b 100644 --- a/Robust.Shared/Containers/BaseContainer.cs +++ b/Robust.Shared/Containers/BaseContainer.cs @@ -12,57 +12,85 @@ using Robust.Shared.ViewVariables; using System; using System.Collections.Generic; using System.Numerics; -using Robust.Shared.Map.Components; +using Robust.Shared.Serialization; namespace Robust.Shared.Containers { /// /// Base container class that all container inherit from. /// - public abstract partial class BaseContainer : IContainer + [ImplicitDataDefinitionForInheritors] + public abstract partial class BaseContainer { - /// + /// + /// Readonly collection of all the entities contained within this specific container + /// [ViewVariables] public abstract IReadOnlyList ContainedEntities { get; } - [ViewVariables] - public abstract List ExpectedEntities { get; } + /// + /// Number of contained entities. + /// + public abstract int Count { get; } - /// - public abstract string ContainerType { get; } + [ViewVariables, NonSerialized] + public List ExpectedEntities = new(); - /// - [ViewVariables] - public bool Deleted { get; private set; } + /// + /// The ID of this container. + /// + [ViewVariables, NonSerialized, Access(typeof(SharedContainerSystem), typeof(ContainerManagerComponent))] + public string ID = default!; - /// - [ViewVariables] - public string ID { get; internal set; } = default!; // Make sure you set me in init + [NonSerialized] + internal ContainerManagerComponent Manager = default!; - /// - public ContainerManagerComponent Manager { get; internal set; } = default!; // Make sure you set me in init - - /// + /// + /// Prevents light from escaping the container, from ex. a flashlight. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("occludes")] public bool OccludesLight { get; set; } = true; - /// + /// + /// The entity that owns this container. + /// [ViewVariables] public EntityUid Owner => Manager.Owner; - /// + /// + /// Should the contents of this container be shown? False for closed containers like lockers, true for + /// things like glass display cases. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("showEnts")] public bool ShowContents { get; set; } - /// - /// DO NOT CALL THIS METHOD DIRECTLY! - /// You want instead. - /// - protected BaseContainer() { } + internal void Init(string id, EntityUid owner, ContainerManagerComponent component) + { + DebugTools.AssertNull(ID); + ID = id; + Manager = component; - /// + // TODO fix container init. + // Eventually, we want an owner field, but currently it needs to use component.Owner + // Owner = owner; + } + + /// + /// Attempts to insert the entity into this container. + /// + /// + /// If the insertion is successful, the inserted entity will end up parented to the + /// container entity, and the inserted entity's local position will be set to the zero vector. + /// + /// The entity to insert. + /// + /// False if the entity could not be inserted. + /// + /// Thrown if this container is a child of the entity, + /// which would cause infinite loops. + /// public bool Insert( EntityUid toinsert, IEntityManager? entMan = null, @@ -72,17 +100,12 @@ namespace Robust.Shared.Containers PhysicsComponent? physics = null, bool force = false) { - DebugTools.Assert(!Deleted); + IoCManager.Resolve(ref entMan); DebugTools.Assert(transform == null || transform.Owner == toinsert); DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner); DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner); DebugTools.Assert(physics == null || physics.Owner == toinsert); - DebugTools.Assert(!ExpectedEntities.Contains(toinsert)); - IoCManager.Resolve(ref entMan); - - //Verify we can insert into this container - if (!force && !CanInsert(toinsert, entMan)) - return false; + DebugTools.Assert(!ExpectedEntities.Contains(entMan.GetNetEntity(toinsert))); var physicsQuery = entMan.GetEntityQuery(); var transformQuery = entMan.GetEntityQuery(); @@ -91,6 +114,11 @@ namespace Robust.Shared.Containers // ECS containers when var physicsSys = entMan.EntitySysManager.GetEntitySystem(); var jointSys = entMan.EntitySysManager.GetEntitySystem(); + var containerSys = entMan.EntitySysManager.GetEntitySystem(); + + //Verify we can insert into this container + if (!force && !containerSys.CanInsert(toinsert, this)) + return false; // Please somebody ecs containers var lookupSys = entMan.EntitySysManager.GetEntitySystem(); @@ -214,45 +242,22 @@ namespace Robust.Shared.Containers } } - /// - public virtual bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) - { - DebugTools.Assert(!Deleted); + /// + /// Whether the given entity can be inserted into this container. + /// + /// Whether to assume that the container is currently empty. + protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true; - // cannot insert into itself. - if (Owner == toinsert) - return false; - - IoCManager.Resolve(ref entMan); - - // no, you can't put maps or grids into containers - if (entMan.HasComponent(toinsert) || entMan.HasComponent(toinsert)) - return false; - - var xformSystem = entMan.EntitySysManager.GetEntitySystem(); - var xformQuery = entMan.GetEntityQuery(); - - // Crucial, prevent circular insertion. - if (xformSystem.ContainsEntity(xformQuery.GetComponent(toinsert), Owner, xformQuery)) - return false; - - //Improvement: Traverse the entire tree to make sure we are not creating a loop. - - //raise events - var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(this, toinsert); - entMan.EventBus.RaiseLocalEvent(Owner, insertAttemptEvent, true); - if (insertAttemptEvent.Cancelled) - return false; - - var gettingInsertedAttemptEvent = new ContainerGettingInsertedAttemptEvent(this, toinsert); - entMan.EventBus.RaiseLocalEvent(toinsert, gettingInsertedAttemptEvent, true); - if (gettingInsertedAttemptEvent.Cancelled) - return false; - - return true; - } - - /// + /// + /// Attempts to remove the entity from this container. + /// + /// If false, this operation will not rigger a move or parent change event. Ignored if + /// destination is not null + /// If true, this will not perform can-remove checks. + /// Where to place the entity after removing. Avoids unnecessary broadphase updates. + /// If not specified, and reparent option is true, then the entity will either be inserted into a parent + /// container, the grid, or the map. + /// Optional final local rotation after removal. Avoids redundant move events. public bool Remove( EntityUid toRemove, IEntityManager? entMan = null, @@ -264,7 +269,6 @@ namespace Robust.Shared.Containers Angle? localRotation = null) { IoCManager.Resolve(ref entMan); - DebugTools.Assert(!Deleted); DebugTools.AssertNotNull(Manager); DebugTools.Assert(entMan.EntityExists(toRemove)); DebugTools.Assert(xform == null || xform.Owner == toRemove); @@ -273,7 +277,8 @@ namespace Robust.Shared.Containers xform ??= entMan.GetComponent(toRemove); meta ??= entMan.GetComponent(toRemove); - if (!force && !CanRemove(toRemove, entMan)) + var sys = entMan.EntitySysManager.GetEntitySystem(); + if (!force && !sys.CanRemove(toRemove, this)) return false; if (force && !Contains(toRemove)) @@ -305,7 +310,7 @@ namespace Robust.Shared.Containers else if (reparent) { // Container ECS when. - entMan.EntitySysManager.GetEntitySystem().AttachParentToContainerOrGrid(xform); + sys.AttachParentToContainerOrGrid(xform); if (localRotation != null) entMan.EntitySysManager.GetEntitySystem().SetLocalRotation(xform, localRotation.Value); } @@ -336,40 +341,22 @@ namespace Robust.Shared.Containers public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null) => Remove(toRemove, entMan, meta: meta, reparent: false, force: true); - /// - public virtual bool CanRemove(EntityUid toRemove, IEntityManager? entMan = null) - { - DebugTools.Assert(!Deleted); - - if (!Contains(toRemove)) - return false; - - IoCManager.Resolve(ref entMan); - - //raise events - var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(this, toRemove); - entMan.EventBus.RaiseLocalEvent(Owner, removeAttemptEvent, true); - if (removeAttemptEvent.Cancelled) - return false; - - var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(this, toRemove); - entMan.EventBus.RaiseLocalEvent(toRemove, gettingRemovedAttemptEvent, true); - if (gettingRemovedAttemptEvent.Cancelled) - return false; - - return true; - } - - /// + /// + /// Checks if the entity is contained in this container. + /// This is not recursive, so containers of children are not checked. + /// + /// The entity to check. + /// True if the entity is immediately contained in this container, false otherwise. public abstract bool Contains(EntityUid contained); - /// + /// + /// Clears the container and marks it as deleted. + /// public void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null) { IoCManager.Resolve(ref entMan, ref netMan); InternalShutdown(entMan, netMan.IsClient); Manager.Containers.Remove(ID); - Deleted = true; } /// diff --git a/Robust.Shared/Containers/Container.cs b/Robust.Shared/Containers/Container.cs index 1ed8d5262..091b63141 100644 --- a/Robust.Shared/Containers/Container.cs +++ b/Robust.Shared/Containers/Container.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.Containers @@ -15,27 +17,21 @@ namespace Robust.Shared.Containers /// For example, inventory containers should be modified only through an inventory component. /// [UsedImplicitly] - [SerializedType(ClassName)] + [SerializedType(nameof(Container))] public sealed partial class Container : BaseContainer { - private const string ClassName = "Container"; - /// /// The generic container class uses a list of entities /// [DataField("ents")] + [NonSerialized] private List _containerList = new(); - private readonly List _expectedEntities = new(); + public override int Count => _containerList.Count; /// public override IReadOnlyList ContainedEntities => _containerList; - public override List ExpectedEntities => _expectedEntities; - - /// - public override string ContainerType => ClassName; - /// protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { @@ -56,6 +52,9 @@ namespace Robust.Shared.Containers return false; #if DEBUG + if (IoCManager.Resolve().ApplyingState) + return true; + var entMan = IoCManager.Resolve(); var flags = entMan.GetComponent(contained).Flags; DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}"); diff --git a/Robust.Shared/Containers/ContainerHelpers.cs b/Robust.Shared/Containers/ContainerHelpers.cs index 0aea962b8..b50c7ac9b 100644 --- a/Robust.Shared/Containers/ContainerHelpers.cs +++ b/Robust.Shared/Containers/ContainerHelpers.cs @@ -54,7 +54,7 @@ namespace Robust.Shared.Containers /// The container that this entity is inside of. /// If a container was found. [Obsolete("Use ContainerSystem.TryGetContainingContainer() instead")] - public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out IContainer? container, IEntityManager? entMan = null) + public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out BaseContainer? container, IEntityManager? entMan = null) { IoCManager.Resolve(ref entMan); DebugTools.Assert(entMan.EntityExists(entity)); @@ -100,7 +100,7 @@ namespace Robust.Shared.Containers /// /// [Obsolete("Use SharedContainerSystem.EmptyContainer() instead")] - public static void EmptyContainer(this IContainer container, bool force = false, EntityCoordinates? moveTo = null, + public static void EmptyContainer(this BaseContainer container, bool force = false, EntityCoordinates? moveTo = null, bool attachToGridOrMap = false, IEntityManager? entMan = null) { IoCManager.Resolve().GetEntitySystem() @@ -112,7 +112,7 @@ namespace Robust.Shared.Containers /// /// [Obsolete("Use SharedContainerSystem.CleanContainer() instead")] - public static void CleanContainer(this IContainer container, IEntityManager? entMan = null) + public static void CleanContainer(this BaseContainer container, IEntityManager? entMan = null) { IoCManager.Resolve().GetEntitySystem() .CleanContainer(container); @@ -147,10 +147,10 @@ namespace Robust.Shared.Containers /// /// The new container. /// Thrown if there already is a container with the specified ID. - /// + /// [Obsolete("Use ContainerSystem.MakeContainer() instead")] public static T CreateContainer(this EntityUid entity, string containerId, IEntityManager? entMan = null) - where T : IContainer + where T : BaseContainer { IoCManager.Resolve(ref entMan); var containermanager = entMan.EnsureComponent(entity); @@ -159,7 +159,7 @@ namespace Robust.Shared.Containers [Obsolete("Use ContainerSystem.EnsureContainer() instead")] public static T EnsureContainer(this EntityUid entity, string containerId, IEntityManager? entMan = null) - where T : IContainer + where T : BaseContainer { IoCManager.Resolve(ref entMan); return EnsureContainer(entity, containerId, out _, entMan); @@ -167,7 +167,7 @@ namespace Robust.Shared.Containers [Obsolete("Use ContainerSystem.EnsureContainer() instead")] public static T EnsureContainer(this EntityUid entity, string containerId, out bool alreadyExisted, IEntityManager? entMan = null) - where T : IContainer + where T : BaseContainer { IoCManager.Resolve(ref entMan); var containerManager = entMan.EnsureComponent(entity); diff --git a/Robust.Shared/Containers/ContainerManagerComponent.cs b/Robust.Shared/Containers/ContainerManagerComponent.cs index 3e0785bc2..352e2d6d1 100644 --- a/Robust.Shared/Containers/ContainerManagerComponent.cs +++ b/Robust.Shared/Containers/ContainerManagerComponent.cs @@ -25,18 +25,16 @@ namespace Robust.Shared.Containers [Dependency] private readonly INetManager _netMan = default!; [DataField("containers")] - public Dictionary Containers = new(); + public Dictionary Containers = new(); void ISerializationHooks.AfterDeserialization() { - // TODO remove ISerializationHooks I guess the IDs can be set by a custom serializer for the dictionary? But - // the component??? Maybe other systems need to stop assuming that containers have been initialized during - // their own init. + // TODO custom type serializer + // TODO set owner uid on init. foreach (var (id, container) in Containers) { - var baseContainer = (BaseContainer) container; - baseContainer.Manager = this; - baseContainer.ID = id; + container.Manager = this; + container.ID = id; } } @@ -55,13 +53,20 @@ namespace Robust.Shared.Containers /// public T MakeContainer(string id) - where T : IContainer + where T : BaseContainer { - return (T) MakeContainer(id, typeof(T)); + if (HasContainer(id)) + throw new ArgumentException($"Container with specified ID already exists: '{id}'"); + + var container = _dynFactory.CreateInstanceUnchecked(typeof(T), inject: false); + container.Init(id, Owner, this); + Containers[id] = container; + _entMan.Dirty(this); + return container; } /// - public IContainer GetContainer(string id) + public BaseContainer GetContainer(string id) { return Containers[id]; } @@ -73,7 +78,7 @@ namespace Robust.Shared.Containers } /// - public bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container) + public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? container) { var ret = Containers.TryGetValue(id, out var cont); container = cont!; @@ -81,11 +86,11 @@ namespace Robust.Shared.Containers } /// - public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out IContainer? container) + public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out BaseContainer? container) { foreach (var contain in Containers.Values) { - if (!contain.Deleted && contain.Contains(entity)) + if (contain.Contains(entity)) { container = contain; return true; @@ -101,7 +106,7 @@ namespace Robust.Shared.Containers { foreach (var container in Containers.Values) { - if (!container.Deleted && container.Contains(entity)) return true; + if (container.Contains(entity)) return true; } return false; @@ -125,19 +130,6 @@ namespace Robust.Shared.Containers return true; // If we don't contain the entity, it will always be removed } - private IContainer MakeContainer(string id, Type type) - { - if (HasContainer(id)) throw new ArgumentException($"Container with specified ID already exists: '{id}'"); - - var container = _dynFactory.CreateInstanceUnchecked(type); - container.ID = id; - container.Manager = this; - - Containers[id] = container; - _entMan.Dirty(this); - return container; - } - public AllContainersEnumerable GetAllContainers() { return new(this); @@ -156,25 +148,22 @@ namespace Robust.Shared.Containers [Serializable, NetSerializable] public readonly struct ContainerData { - public readonly string ContainerType; - public readonly string Id; + public readonly string ContainerType; // TODO remove this. We dont have to send a whole string. public readonly bool ShowContents; public readonly bool OccludesLight; - public readonly EntityUid[] ContainedEntities; + public readonly NetEntity[] ContainedEntities; - public ContainerData(string containerType, string id, bool showContents, bool occludesLight, EntityUid[] containedEntities) + public ContainerData(string containerType, bool showContents, bool occludesLight, NetEntity[] containedEntities) { ContainerType = containerType; - Id = id; ShowContents = showContents; OccludesLight = occludesLight; ContainedEntities = containedEntities; } - public void Deconstruct(out string type, out string id, out bool showEnts, out bool occludesLight, out EntityUid[] ents) + public void Deconstruct(out string type, out bool showEnts, out bool occludesLight, out NetEntity[] ents) { type = ContainerType; - id = Id; showEnts = ShowContents; occludesLight = OccludesLight; ents = ContainedEntities; @@ -182,24 +171,7 @@ namespace Robust.Shared.Containers } } - [DataDefinition] - private partial struct ContainerPrototypeData - { - [DataField("entities")] public List Entities = new (); - - [DataField("type")] public string? Type = null; - - // explicit parameterless constructor is required. - public ContainerPrototypeData() { } - - public ContainerPrototypeData(List entities, string type) - { - Entities = entities; - Type = type; - } - } - - public readonly struct AllContainersEnumerable : IEnumerable + public readonly struct AllContainersEnumerable : IEnumerable { private readonly ContainerManagerComponent? _manager; @@ -213,7 +185,7 @@ namespace Robust.Shared.Containers return new(_manager); } - IEnumerator IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } @@ -224,9 +196,9 @@ namespace Robust.Shared.Containers } } - public struct AllContainersEnumerator : IEnumerator + public struct AllContainersEnumerator : IEnumerator { - private Dictionary.ValueCollection.Enumerator _enumerator; + private Dictionary.ValueCollection.Enumerator _enumerator; public AllContainersEnumerator(ContainerManagerComponent? manager) { @@ -238,11 +210,8 @@ namespace Robust.Shared.Containers { while (_enumerator.MoveNext()) { - if (!_enumerator.Current.Deleted) - { - Current = _enumerator.Current; - return true; - } + Current = _enumerator.Current; + return true; } return false; @@ -250,11 +219,11 @@ namespace Robust.Shared.Containers void IEnumerator.Reset() { - ((IEnumerator) _enumerator).Reset(); + ((IEnumerator) _enumerator).Reset(); } [AllowNull] - public IContainer Current { get; private set; } + public BaseContainer Current { get; private set; } object IEnumerator.Current => Current; diff --git a/Robust.Shared/Containers/ContainerSlot.cs b/Robust.Shared/Containers/ContainerSlot.cs index 27785a257..009b2d02e 100644 --- a/Robust.Shared/Containers/ContainerSlot.cs +++ b/Robust.Shared/Containers/ContainerSlot.cs @@ -6,24 +6,27 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.Containers { [UsedImplicitly] - [SerializedType(ClassName)] + [SerializedType(nameof(ContainerSlot))] public sealed partial class ContainerSlot : BaseContainer { - private const string ClassName = "ContainerSlot"; + public override int Count => ContainedEntity == null ? 0 : 1; /// public override IReadOnlyList ContainedEntities { get { - if (ContainedEntity == null) + if (_containedEntity == null) return Array.Empty(); + _containedEntityArray ??= new[] { _containedEntity.Value }; + DebugTools.Assert(_containedEntityArray[0] == _containedEntity); return _containedEntityArray; } } @@ -36,39 +39,19 @@ namespace Robust.Shared.Containers { _containedEntity = value; if (value != null) - _containedEntityArray[0] = value!.Value; + { + _containedEntityArray ??= new EntityUid[1]; + _containedEntityArray[0] = value.Value; + } } } - public override List ExpectedEntities => _expectedEntities; - + [NonSerialized] private EntityUid? _containedEntity; - private readonly List _expectedEntities = new(); + // Used by ContainedEntities to avoid allocating. - private readonly EntityUid[] _containedEntityArray = new EntityUid[1]; - - /// - public override string ContainerType => ClassName; - - /// - public override bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) - { - return (ContainedEntity == null) && CanInsertIfEmpty(toinsert, entMan); - } - - /// - /// Checks if the entity can be inserted into this container, assuming that the container slot is empty. - /// - /// - /// Useful if you need to know whether an item could be inserted into a slot, without having to actually eject - /// the currently contained entity first. - /// - /// The entity to attempt to insert. - /// True if the entity could be inserted into an empty slot, false otherwise. - public bool CanInsertIfEmpty(EntityUid toinsert, IEntityManager? entMan = null) - { - return base.CanInsert(toinsert, entMan); - } + [NonSerialized] + private EntityUid[]? _containedEntityArray; /// public override bool Contains(EntityUid contained) @@ -77,6 +60,9 @@ namespace Robust.Shared.Containers return false; #if DEBUG + if (IoCManager.Resolve().ApplyingState) + return true; + var entMan = IoCManager.Resolve(); var flags = entMan.GetComponent(contained).Flags; DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}"); @@ -84,6 +70,9 @@ namespace Robust.Shared.Containers return true; } + protected internal override bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) + => ContainedEntity == null || assumeEmpty; + /// protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { @@ -91,7 +80,7 @@ namespace Robust.Shared.Containers #if DEBUG // TODO make this a proper debug assert when gun code no longer fudges client-side spawn prediction. - if (toInsert.IsClientSide() && !Owner.IsClientSide() && Manager.NetSyncEnabled) + if (entMan.IsClientSide(toInsert) && !entMan.IsClientSide(Owner) && Manager.NetSyncEnabled) Logger.Warning("Inserting a client-side entity into a networked container slot. This will block the container slot and may cause issues."); #endif ContainedEntity = toInsert; diff --git a/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs b/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs index aa6107702..15b63d780 100644 --- a/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs +++ b/Robust.Shared/Containers/Events/ContainerAttemptEvents.cs @@ -6,10 +6,10 @@ namespace Robust.Shared.Containers; public abstract class ContainerAttemptEventBase : CancellableEntityEventArgs { - public readonly IContainer Container; + public readonly BaseContainer Container; public readonly EntityUid EntityUid; - public ContainerAttemptEventBase(IContainer container, EntityUid entityUid) + public ContainerAttemptEventBase(BaseContainer container, EntityUid entityUid) { Container = container; EntityUid = entityUid; @@ -18,28 +18,44 @@ public abstract class ContainerAttemptEventBase : CancellableEntityEventArgs public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase { - public ContainerIsInsertingAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) + /// + /// If true, this check should assume that the container is currently empty. + /// I.e., could the entity be inserted if the container doesn't contain anything else? + /// + public bool AssumeEmpty { get; set; } + + public ContainerIsInsertingAttemptEvent(BaseContainer container, EntityUid entityUid, bool assumeEmpty) + : base(container, entityUid) { + AssumeEmpty = assumeEmpty; } } public sealed class ContainerGettingInsertedAttemptEvent : ContainerAttemptEventBase { - public ContainerGettingInsertedAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) + /// + /// If true, this check should assume that the container is currently empty. + /// I.e., could the entity be inserted if the container doesn't contain anything else? + /// + public bool AssumeEmpty { get; set; } + + public ContainerGettingInsertedAttemptEvent(BaseContainer container, EntityUid entityUid, bool assumeEmpty) + : base(container, entityUid) { + AssumeEmpty = assumeEmpty; } } public sealed class ContainerIsRemovingAttemptEvent : ContainerAttemptEventBase { - public ContainerIsRemovingAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) + public ContainerIsRemovingAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid) { } } public sealed class ContainerGettingRemovedAttemptEvent : ContainerAttemptEventBase { - public ContainerGettingRemovedAttemptEvent(IContainer container, EntityUid entityUid) : base(container, entityUid) + public ContainerGettingRemovedAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid) { } } diff --git a/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs b/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs index 06e869d03..570e8dcbb 100644 --- a/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs +++ b/Robust.Shared/Containers/Events/ContainerModifiedMessage.cs @@ -12,14 +12,14 @@ namespace Robust.Shared.Containers /// /// The container being acted upon. /// - public IContainer Container { get; } + public BaseContainer Container { get; } /// /// The entity that was removed or inserted from/into the container. /// public EntityUid Entity { get; } - protected ContainerModifiedMessage(EntityUid entity, IContainer container) + protected ContainerModifiedMessage(EntityUid entity, BaseContainer container) { Entity = entity; Container = container; diff --git a/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs b/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs index 78ba4dbad..cd044765c 100644 --- a/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs +++ b/Robust.Shared/Containers/Events/EntGotInsertedIntoContainerMessage.cs @@ -9,5 +9,5 @@ namespace Robust.Shared.Containers; [PublicAPI] public sealed class EntGotInsertedIntoContainerMessage : ContainerModifiedMessage { - public EntGotInsertedIntoContainerMessage(EntityUid entity, IContainer container) : base(entity, container) { } + public EntGotInsertedIntoContainerMessage(EntityUid entity, BaseContainer container) : base(entity, container) { } } diff --git a/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs b/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs index 38622c21e..dfcae817c 100644 --- a/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs +++ b/Robust.Shared/Containers/Events/EntInsertedIntoContainerMessage.cs @@ -11,7 +11,7 @@ namespace Robust.Shared.Containers { public readonly EntityUid OldParent; - public EntInsertedIntoContainerMessage(EntityUid entity, EntityUid oldParent, IContainer container) : base(entity, container) + public EntInsertedIntoContainerMessage(EntityUid entity, EntityUid oldParent, BaseContainer container) : base(entity, container) { OldParent = oldParent; } diff --git a/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs b/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs index 82b3bd5e3..2f75510da 100644 --- a/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs +++ b/Robust.Shared/Containers/Events/EntRemovedFromContainerMessage.cs @@ -9,7 +9,7 @@ namespace Robust.Shared.Containers [PublicAPI] public sealed class EntRemovedFromContainerMessage : ContainerModifiedMessage { - public EntRemovedFromContainerMessage(EntityUid entity, IContainer container) : base(entity, container) { } + public EntRemovedFromContainerMessage(EntityUid entity, BaseContainer container) : base(entity, container) { } } /// @@ -18,6 +18,6 @@ namespace Robust.Shared.Containers [PublicAPI] public sealed class EntGotRemovedFromContainerMessage : ContainerModifiedMessage { - public EntGotRemovedFromContainerMessage(EntityUid entity, IContainer container) : base(entity, container) { } + public EntGotRemovedFromContainerMessage(EntityUid entity, BaseContainer container) : base(entity, container) { } } } diff --git a/Robust.Shared/Containers/IContainer.cs b/Robust.Shared/Containers/IContainer.cs deleted file mode 100644 index 134d41213..000000000 --- a/Robust.Shared/Containers/IContainer.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Network; -using Robust.Shared.Physics.Components; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Robust.Shared.Containers -{ - /// - /// A container is a way to "contain" entities inside other entities, in a logical way. - /// This is alike BYOND's contents system, except more advanced. - /// - /// - ///

- /// Containers are logical separations of entities contained inside another entity. - /// for example, a crate with two separated compartments would have two separate containers. - /// If an entity inside compartment A drops something, - /// the dropped entity would be placed in compartment A too, - /// and compartment B would be completely untouched. - ///

- ///

- /// Containers are managed by an entity's , - /// and have an ID to be referenced by. - ///

- ///
- /// - [PublicAPI] - [ImplicitDataDefinitionForInheritors] - public partial interface IContainer - { - /// - /// Readonly collection of all the entities contained within this specific container - /// - IReadOnlyList ContainedEntities { get; } - - List ExpectedEntities { get; } - - /// - /// The type of this container. - /// - string ContainerType { get; } - - /// - /// True if the container has been shut down via - /// - bool Deleted { get; } - - /// - /// The ID of this container. - /// - string ID { get; } - - /// - /// Prevents light from escaping the container, from ex. a flashlight. - /// - bool OccludesLight { get; set; } - - /// - /// The entity owning this container. - /// - EntityUid Owner { get; } - - /// - /// Should the contents of this container be shown? False for closed containers like lockers, true for - /// things like glass display cases. - /// - bool ShowContents { get; set; } - - /// - /// Checks if the entity can be inserted into this container. - /// - /// The entity to attempt to insert. - /// - /// True if the entity can be inserted, false otherwise. - bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null); - - /// - /// Attempts to insert the entity into this container. - /// - /// - /// If the insertion is successful, the inserted entity will end up parented to the - /// container entity, and the inserted entity's local position will be set to the zero vector. - /// - /// The entity to insert. - /// - /// False if the entity could not be inserted. - /// - /// Thrown if this container is a child of the entity, - /// which would cause infinite loops. - /// - bool Insert(EntityUid toinsert, - IEntityManager? entMan = null, - TransformComponent? transform = null, - TransformComponent? ownerTransform = null, - MetaDataComponent? meta = null, - PhysicsComponent? physics = null, - bool force = false); - - /// - /// Checks if the entity can be removed from this container. - /// - /// The entity to check. - /// - /// True if the entity can be removed, false otherwise. - bool CanRemove(EntityUid toremove, IEntityManager? entMan = null); - - /// - /// Attempts to remove the entity from this container. - /// - /// If false, this operation will not rigger a move or parent change event. Ignored if - /// destination is not null - /// If true, this will not perform can-remove checks. - /// Where to place the entity after removing. Avoids unnecessary broadphase updates. - /// If not specified, and reparent option is true, then the entity will either be inserted into a parent - /// container, the grid, or the map. - /// Optional final local rotation after removal. Avoids redundant move events. - bool Remove( - EntityUid toremove, - IEntityManager? entMan = null, - TransformComponent? xform = null, - MetaDataComponent? meta = null, - bool reparent = true, - bool force = false, - EntityCoordinates? destination = null, - Angle? localRotation = null); - - [Obsolete("use force option in Remove()")] - void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null); - - /// - /// Checks if the entity is contained in this container. - /// This is not recursive, so containers of children are not checked. - /// - /// The entity to check. - /// True if the entity is immediately contained in this container, false otherwise. - bool Contains(EntityUid contained); - - /// - /// Clears the container and marks it as deleted. - /// - void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null); - } -} diff --git a/Robust.Shared/Containers/SharedContainerSystem.Insert.cs b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs new file mode 100644 index 000000000..681696fb6 --- /dev/null +++ b/Robust.Shared/Containers/SharedContainerSystem.Insert.cs @@ -0,0 +1,49 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Utility; + +namespace Robust.Shared.Containers; + +public abstract partial class SharedContainerSystem +{ + /// + /// Checks if the entity can be inserted into the given container. + /// + /// If true, this will check whether the entity could be inserted if the container were + /// empty. + public bool CanInsert( + EntityUid toInsert, + BaseContainer container, + TransformComponent? toInsertXform = null, + bool assumeEmpty = false) + { + if (container.Owner == toInsert) + return false; + + if (!assumeEmpty && container.Contains(toInsert)) + return false; + + if (!container.CanInsert(toInsert, assumeEmpty, EntityManager)) + return false; + + if (!TransformQuery.Resolve(toInsert, ref toInsertXform)) + return false; + + // no, you can't put maps or grids into containers + if (_mapQuery.HasComponent(toInsert) || _gridQuery.HasComponent(toInsert)) + return false; + + // Prevent circular insertion. + if (_transform.ContainsEntity(toInsertXform, container.Owner)) + return false; + + var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(container, toInsert, assumeEmpty); + RaiseLocalEvent(container.Owner, insertAttemptEvent, true); + if (insertAttemptEvent.Cancelled) + return false; + + var gettingInsertedAttemptEvent = new ContainerGettingInsertedAttemptEvent(container, toInsert, assumeEmpty); + RaiseLocalEvent(toInsert, gettingInsertedAttemptEvent, true); + + return !gettingInsertedAttemptEvent.Cancelled; + } +} diff --git a/Robust.Shared/Containers/SharedContainerSystem.Remove.cs b/Robust.Shared/Containers/SharedContainerSystem.Remove.cs new file mode 100644 index 000000000..7662ae7b5 --- /dev/null +++ b/Robust.Shared/Containers/SharedContainerSystem.Remove.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Containers; + +public abstract partial class SharedContainerSystem +{ + /// + /// Checks if the entity can be removed from this container. + /// + /// True if the entity can be removed, false otherwise. + public bool CanRemove(EntityUid toRemove, BaseContainer container) + { + if (!container.Contains(toRemove)) + return false; + + //raise events + var removeAttemptEvent = new ContainerIsRemovingAttemptEvent(container, toRemove); + RaiseLocalEvent(container.Owner, removeAttemptEvent, true); + if (removeAttemptEvent.Cancelled) + return false; + + var gettingRemovedAttemptEvent = new ContainerGettingRemovedAttemptEvent(container, toRemove); + RaiseLocalEvent(toRemove, gettingRemovedAttemptEvent, true); + return !gettingRemovedAttemptEvent.Cancelled; + } +} diff --git a/Robust.Shared/Containers/SharedContainerSystem.Validation.cs b/Robust.Shared/Containers/SharedContainerSystem.Validation.cs index 3ea0a6f23..27b8d6d67 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.Validation.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.Validation.cs @@ -13,22 +13,18 @@ public abstract partial class SharedContainerSystem : EntitySystem { private void OnStartupValidation(EntityUid uid, ContainerManagerComponent component, ComponentStartup args) { - var metaQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var physicsQuery = GetEntityQuery(); - var jointQuery = GetEntityQuery(); foreach (var cont in component.Containers.Values) { foreach (var ent in cont.ContainedEntities) { - if (!metaQuery.TryGetComponent(ent, out var meta)) + if (!MetaQuery.TryGetComponent(ent, out var meta)) { ValidateMissingEntity(uid, cont, ent); continue; } - var xform = xformQuery.GetComponent(ent); - physicsQuery.TryGetComponent(ent, out var physics); + var xform = TransformQuery.GetComponent(ent); + PhysicsQuery.TryGetComponent(ent, out var physics); DebugTools.Assert(xform.ParentUid == uid, $"Entity not parented to its container. Entity: {ToPrettyString(ent)}, parent: {ToPrettyString(uid)}"); @@ -46,15 +42,15 @@ public abstract partial class SharedContainerSystem : EntitySystem // entities in containers without having to "re-insert" them. meta.Flags |= MetaDataFlags.InContainer; _lookup.RemoveFromEntityTree(ent, xform); - ((BaseContainer)cont).RecursivelyUpdatePhysics(ent, xform, physics, _physics, physicsQuery, xformQuery); + cont.RecursivelyUpdatePhysics(ent, xform, physics, _physics, PhysicsQuery, TransformQuery); // assert children have correct properties - ValidateChildren(xform, xformQuery, physicsQuery); + ValidateChildren(xform, TransformQuery, PhysicsQuery); } } } - protected abstract void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing); + protected abstract void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing); private void ValidateChildren(TransformComponent xform, EntityQuery xformQuery, EntityQuery physicsQuery) { diff --git a/Robust.Shared/Containers/SharedContainerSystem.cs b/Robust.Shared/Containers/SharedContainerSystem.cs index f9bf03096..bde4ddfb7 100644 --- a/Robust.Shared/Containers/SharedContainerSystem.cs +++ b/Robust.Shared/Containers/SharedContainerSystem.cs @@ -6,7 +6,9 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Maths; +using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; @@ -16,9 +18,13 @@ namespace Robust.Shared.Containers { [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; - private EntityQuery _metas; - private EntityQuery _xforms; + private EntityQuery _gridQuery; + private EntityQuery _mapQuery; + protected EntityQuery MetaQuery; + protected EntityQuery PhysicsQuery; + protected EntityQuery TransformQuery; /// public override void Initialize() @@ -29,25 +35,27 @@ namespace Robust.Shared.Containers SubscribeLocalEvent(OnStartupValidation); SubscribeLocalEvent(OnContainerGetState); - _metas = EntityManager.GetEntityQuery(); - _xforms = EntityManager.GetEntityQuery(); + _gridQuery = GetEntityQuery(); + _mapQuery = GetEntityQuery(); + MetaQuery = GetEntityQuery(); + PhysicsQuery = GetEntityQuery(); + TransformQuery = GetEntityQuery(); } private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args) { - // naive implementation that just sends the full state of the component Dictionary containerSet = new(component.Containers.Count); foreach (var container in component.Containers.Values) { - var uidArr = new EntityUid[container.ContainedEntities.Count]; + var uidArr = new NetEntity[container.ContainedEntities.Count]; for (var index = 0; index < container.ContainedEntities.Count; index++) { - uidArr[index] = container.ContainedEntities[index]; + uidArr[index] = GetNetEntity(container.ContainedEntities[index]); } - var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr); + var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name, container.ShowContents, container.OccludesLight, uidArr); containerSet.Add(container.ID, sContainer); } @@ -59,7 +67,7 @@ namespace Robust.Shared.Containers #region Proxy Methods public T MakeContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) - where T : IContainer + where T : BaseContainer { if (!Resolve(uid, ref containerManager, false)) containerManager = EntityManager.AddComponent(uid); // Happy Vera. @@ -68,7 +76,7 @@ namespace Robust.Shared.Containers } public T EnsureContainer(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null) - where T : IContainer + where T : BaseContainer { if (!Resolve(uid, ref containerManager, false)) containerManager = EntityManager.AddComponent(uid); @@ -88,12 +96,12 @@ namespace Robust.Shared.Containers } public T EnsureContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) - where T : IContainer + where T : BaseContainer { return EnsureContainer(uid, id, out _, containerManager); } - public IContainer GetContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) + public BaseContainer GetContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager = null) { if (!Resolve(uid, ref containerManager)) throw new ArgumentException("Entity does not have a ContainerManagerComponent!", nameof(uid)); @@ -109,7 +117,7 @@ namespace Robust.Shared.Containers return containerManager.HasContainer(id); } - public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null) + public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null) { if (Resolve(uid, ref containerManager, false)) return containerManager.TryGetContainer(id, out container); @@ -118,7 +126,7 @@ namespace Robust.Shared.Containers return false; } - public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out IContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false) + public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false) { if (Resolve(uid, ref containerManager, false) && (skipExistCheck || EntityManager.EntityExists(containedUid))) return containerManager.TryGetContainer(containedUid, out container); @@ -164,7 +172,7 @@ namespace Robust.Shared.Containers #region Container Helpers - public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out IContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null) + public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out BaseContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null) { container = null; @@ -227,7 +235,7 @@ namespace Robust.Shared.Containers } /// - /// Finds the first instance of a component on the recursive parented containers that hold an entity + /// Finds the first instance of a component on the recursive parented containers that hold an entity /// public bool TryFindComponentOnEntityContainerOrParent( EntityUid uid, @@ -236,13 +244,13 @@ namespace Robust.Shared.Containers MetaDataComponent? meta = null, TransformComponent? xform = null) where T : Component { - if (!_metas.Resolve(uid, ref meta)) + if (!MetaQuery.Resolve(uid, ref meta)) return false; if ((meta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer) return false; - if (!_xforms.Resolve(uid, ref xform)) + if (!TransformQuery.Resolve(uid, ref xform)) return false; if (!xform.ParentUid.Valid) @@ -255,7 +263,7 @@ namespace Robust.Shared.Containers } /// - /// Finds all instances of a component on the recursive parented containers that hold an entity + /// Finds all instances of a component on the recursive parented containers that hold an entity /// public bool TryFindComponentsOnEntityContainerOrParent( EntityUid uid, @@ -264,13 +272,13 @@ namespace Robust.Shared.Containers MetaDataComponent? meta = null, TransformComponent? xform = null) where T : Component { - if (!_metas.Resolve(uid, ref meta)) + if (!MetaQuery.Resolve(uid, ref meta)) return foundComponents.Any(); if ((meta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer) return foundComponents.Any(); - if (!_xforms.Resolve(uid, ref xform)) + if (!TransformQuery.Resolve(uid, ref xform)) return foundComponents.Any(); if (!xform.ParentUid.Valid) @@ -335,8 +343,8 @@ namespace Robust.Shared.Containers public bool IsInSameOrTransparentContainer( EntityUid user, EntityUid other, - IContainer? userContainer = null, - IContainer? otherContainer = null, + BaseContainer? userContainer = null, + BaseContainer? otherContainer = null, bool userSeeInsideSelf = false) { if (userContainer == null) @@ -371,14 +379,14 @@ namespace Robust.Shared.Containers /// /// Gets the top-most container in the hierarchy for this entity, if it exists. /// - public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out IContainer? container) + public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out BaseContainer? container) { var xformQuery = EntityManager.GetEntityQuery(); return TryGetOuterContainer(uid, xform, out container, xformQuery); } public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, - [NotNullWhen(true)] out IContainer? container, EntityQuery xformQuery) + [NotNullWhen(true)] out BaseContainer? container, EntityQuery xformQuery) { container = null; @@ -448,7 +456,7 @@ namespace Robust.Shared.Containers /// Attempts to remove all entities in a container. Returns removed entities. ///
public List EmptyContainer( - IContainer container, + BaseContainer container, bool force = false, EntityCoordinates? destination = null, bool reparent = true) @@ -470,7 +478,7 @@ namespace Robust.Shared.Containers /// /// Attempts to remove and delete all entities in a container. /// - public void CleanContainer(IContainer container) + public void CleanContainer(BaseContainer container) { foreach (var ent in container.ContainedEntities.ToArray()) { @@ -491,7 +499,7 @@ namespace Robust.Shared.Containers transform.AttachToGridOrMap(); } - private bool TryInsertIntoContainer(TransformComponent transform, IContainer container) + private bool TryInsertIntoContainer(TransformComponent transform, BaseContainer container) { if (container.Insert(transform.Owner)) return true; diff --git a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs index 445fefec5..fc3162ccc 100644 --- a/Robust.Shared/GameObjects/Components/MetaDataComponent.cs +++ b/Robust.Shared/GameObjects/Components/MetaDataComponent.cs @@ -63,6 +63,13 @@ namespace Robust.Shared.GameObjects [DataField("desc")] internal string? _entityDescription; internal EntityPrototype? _entityPrototype; + /// + /// Network identifier for this entity. + /// + [ViewVariables] + [Access(typeof(EntityManager), Other = AccessPermissions.ReadExecute)] + public NetEntity NetEntity { get; internal set; } = NetEntity.Invalid; + /// /// When this entity was paused, if applicable. Note that this is the actual time, not the duration which gets /// returned by . diff --git a/Robust.Shared/GameObjects/Components/UserInterface/SharedUserInterfaceComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/SharedUserInterfaceComponent.cs index b389ce8a4..7ce766783 100644 --- a/Robust.Shared/GameObjects/Components/UserInterface/SharedUserInterfaceComponent.cs +++ b/Robust.Shared/GameObjects/Components/UserInterface/SharedUserInterfaceComponent.cs @@ -64,21 +64,29 @@ namespace Robust.Shared.GameObjects { } + /// + /// Abstract class for local BUI events. + /// + public abstract class BaseLocalBoundUserInterfaceEvent : BaseBoundUserInterfaceEvent + { + /// + /// The Entity receiving the message. + /// Only set when the message is raised as a directed event. + /// + public EntityUid Entity = EntityUid.Invalid; + } - [NetSerializable, Serializable] - public abstract class BoundUserInterfaceMessage : EntityEventArgs + /// + /// Abstract class for all BUI events. + /// + [Serializable, NetSerializable] + public abstract class BaseBoundUserInterfaceEvent : EntityEventArgs { /// /// The UI of this message. /// Only set when the message is raised as a directed event. /// - public Enum UiKey { get; set; } = default!; - - /// - /// The Entity receiving the message. - /// Only set when the message is raised as a directed event. - /// - public EntityUid Entity { get; set; } = EntityUid.Invalid; + public Enum UiKey = default!; /// /// The session sending or receiving this message. @@ -88,6 +96,19 @@ namespace Robust.Shared.GameObjects public ICommonSession Session = default!; } + /// + /// Abstract class for networked BUI events. + /// + [NetSerializable, Serializable] + public abstract class BoundUserInterfaceMessage : BaseBoundUserInterfaceEvent + { + /// + /// The Entity receiving the message. + /// Only set when the message is raised as a directed event. + /// + public NetEntity Entity { get; set; } = NetEntity.Invalid; + } + [NetSerializable, Serializable] internal sealed class UpdateBoundStateMessage : BoundUserInterfaceMessage { @@ -112,11 +133,11 @@ namespace Robust.Shared.GameObjects [Serializable, NetSerializable] internal sealed class BoundUIWrapMessage : EntityEventArgs { - public readonly EntityUid Entity; + public readonly NetEntity Entity; public readonly BoundUserInterfaceMessage Message; public readonly Enum UiKey; - public BoundUIWrapMessage(EntityUid entity, BoundUserInterfaceMessage message, Enum uiKey) + public BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) { Message = message; UiKey = uiKey; @@ -129,7 +150,7 @@ namespace Robust.Shared.GameObjects } } - public sealed class BoundUIOpenedEvent : BoundUserInterfaceMessage + public sealed class BoundUIOpenedEvent : BaseLocalBoundUserInterfaceEvent { public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, ICommonSession session) { @@ -139,7 +160,7 @@ namespace Robust.Shared.GameObjects } } - public sealed class BoundUIClosedEvent : BoundUserInterfaceMessage + public sealed class BoundUIClosedEvent : BaseLocalBoundUserInterfaceEvent { public BoundUIClosedEvent(Enum uiKey, EntityUid uid, ICommonSession session) { diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index f7a714e12..da7ad3a44 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1065,7 +1065,7 @@ namespace Robust.Shared.GameObjects where TComp1 : Component { var trait1 = _entTraitArray[CompIdx.ArrayIndex()]; - return new EntityQueryEnumerator(trait1, _metaQuery); + return new EntityQueryEnumerator(trait1, MetaQuery); } public EntityQueryEnumerator EntityQueryEnumerator() @@ -1074,7 +1074,7 @@ namespace Robust.Shared.GameObjects { var trait1 = _entTraitArray[CompIdx.ArrayIndex()]; var trait2 = _entTraitArray[CompIdx.ArrayIndex()]; - return new EntityQueryEnumerator(trait1, trait2, _metaQuery); + return new EntityQueryEnumerator(trait1, trait2, MetaQuery); } public EntityQueryEnumerator EntityQueryEnumerator() @@ -1085,7 +1085,7 @@ namespace Robust.Shared.GameObjects var trait1 = _entTraitArray[CompIdx.ArrayIndex()]; var trait2 = _entTraitArray[CompIdx.ArrayIndex()]; var trait3 = _entTraitArray[CompIdx.ArrayIndex()]; - return new EntityQueryEnumerator(trait1, trait2, trait3, _metaQuery); + return new EntityQueryEnumerator(trait1, trait2, trait3, MetaQuery); } public EntityQueryEnumerator EntityQueryEnumerator() @@ -1099,7 +1099,7 @@ namespace Robust.Shared.GameObjects var trait3 = _entTraitArray[CompIdx.ArrayIndex()]; var trait4 = _entTraitArray[CompIdx.ArrayIndex()]; - return new EntityQueryEnumerator(trait1, trait2, trait3, trait4, _metaQuery); + return new EntityQueryEnumerator(trait1, trait2, trait3, trait4, MetaQuery); } /// @@ -1121,7 +1121,7 @@ namespace Robust.Shared.GameObjects { foreach (var t1Comp in comps.Values) { - if (t1Comp.Deleted || !_metaQuery.TryGetComponentInternal(t1Comp.Owner, out var metaComp)) continue; + if (t1Comp.Deleted || !MetaQuery.TryGetComponentInternal(t1Comp.Owner, out var metaComp)) continue; if (metaComp.EntityPaused) continue; @@ -1312,7 +1312,7 @@ namespace Robust.Shared.GameObjects { foreach (var (uid, comp) in comps) { - if (comp.Deleted || !_metaQuery.TryGetComponent(uid, out var meta) || meta.EntityPaused) continue; + if (comp.Deleted || !MetaQuery.TryGetComponent(uid, out var meta) || meta.EntityPaused) continue; yield return (uid, comp); } diff --git a/Robust.Shared/GameObjects/EntityManager.Network.cs b/Robust.Shared/GameObjects/EntityManager.Network.cs new file mode 100644 index 000000000..52e7fcb17 --- /dev/null +++ b/Robust.Shared/GameObjects/EntityManager.Network.cs @@ -0,0 +1,585 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +public partial class EntityManager +{ + // TODO POOLING + // Just add overrides that take in an existing collection. + + /// + /// Inverse lookup for net entities. + /// Regular lookup uses MetadataComponent. + /// + protected readonly Dictionary NetEntityLookup = new(EntityCapacity); + + /// + /// Clears an old inverse lookup for a particular entityuid. + /// Do not call this unless you are sure of what you're doing. + /// + internal void ClearNetEntity(NetEntity netEntity) + { + NetEntityLookup.Remove(netEntity); + } + + /// + /// Set the inverse lookup for a particular entityuid. + /// Do not call this unless you are sure of what you're doing. + /// + internal void SetNetEntity(EntityUid uid, NetEntity netEntity, MetaDataComponent component) + { + DebugTools.Assert(!NetEntityLookup.ContainsKey(netEntity)); + NetEntityLookup[netEntity] = uid; + component.NetEntity = netEntity; + } + + /// + public virtual bool IsClientSide(EntityUid uid, MetaDataComponent? metadata = null) + { + return false; + } + + #region NetEntity + + /// + public bool TryParseNetEntity(string arg, [NotNullWhen(true)] out EntityUid? entity) + { + if (!NetEntity.TryParse(arg, out var netEntity) || + !TryGetEntity(netEntity, out entity)) + { + entity = null; + return false; + } + + return true; + } + + /// + public bool TryGetEntity(NetEntity nEntity, [NotNullWhen(true)] out EntityUid? entity) + { + if (NetEntityLookup.TryGetValue(nEntity, out var went)) + { + entity = went; + return true; + } + + entity = EntityUid.Invalid; + return false; + } + + /// + public bool TryGetEntity(NetEntity? nEntity, [NotNullWhen(true)] out EntityUid? entity) + { + if (nEntity == null) + { + entity = EntityUid.Invalid; + return false; + } + + return TryGetEntity(nEntity.Value, out entity); + } + + /// + public bool TryGetNetEntity(EntityUid uid, [NotNullWhen(true)] out NetEntity? netEntity, MetaDataComponent? metadata = null) + { + if (uid == EntityUid.Invalid) + { + netEntity = NetEntity.Invalid; + return false; + } + + // TODO NetEntity figure out why this happens + // I wanted this to logMissing but it seems to break a loootttt of dodgy stuff on content. + if (MetaQuery.Resolve(uid, ref metadata, false)) + { + netEntity = metadata.NetEntity; + return true; + } + + netEntity = NetEntity.Invalid; + return false; + } + + /// + public bool TryGetNetEntity(EntityUid? uid, [NotNullWhen(true)] out NetEntity? netEntity, MetaDataComponent? metadata = null) + { + if (uid == null) + { + netEntity = NetEntity.Invalid; + return false; + } + + return TryGetNetEntity(uid.Value, out netEntity, metadata); + } + + /// + public virtual EntityUid EnsureEntity(NetEntity nEntity, EntityUid callerEntity) + { + // On server we don't want to ensure any reserved entities for later or flag for comp state handling + // so this is just GetEntity. Client-side code overrides this method. + return GetEntity(nEntity); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityUid? EnsureEntity(NetEntity? nEntity, EntityUid callerEntity) + { + if (nEntity == null) + return null; + + return EnsureEntity(nEntity.Value, callerEntity); + } + + /// + public EntityUid GetEntity(NetEntity nEntity) + { + if (nEntity == NetEntity.Invalid) + return EntityUid.Invalid; + + return NetEntityLookup.GetValueOrDefault(nEntity); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityUid? GetEntity(NetEntity? nEntity) + { + if (nEntity == null) + return null; + + return GetEntity(nEntity.Value); + } + + /// + public NetEntity GetNetEntity(EntityUid uid, MetaDataComponent? metadata = null) + { + if (uid == EntityUid.Invalid) + return NetEntity.Invalid; + + if (!MetaQuery.Resolve(uid, ref metadata)) + return NetEntity.Invalid; + + return metadata.NetEntity; + } + + /// + public NetEntity? GetNetEntity(EntityUid? uid, MetaDataComponent? metadata = null) + { + if (uid == null) + return null; + + return GetNetEntity(uid.Value, metadata); + } + + #endregion + + #region NetCoordinates + + /// + public NetCoordinates GetNetCoordinates(EntityCoordinates coordinates, MetaDataComponent? metadata = null) + { + return new NetCoordinates(GetNetEntity(coordinates.EntityId, metadata), coordinates.Position); + } + + /// + public NetCoordinates? GetNetCoordinates(EntityCoordinates? coordinates, MetaDataComponent? metadata = null) + { + if (coordinates == null) + return null; + + return new NetCoordinates(GetNetEntity(coordinates.Value.EntityId, metadata), coordinates.Value.Position); + } + + /// + public EntityCoordinates GetCoordinates(NetCoordinates coordinates) + { + return new EntityCoordinates(GetEntity(coordinates.NetEntity), coordinates.Position); + } + + /// + public EntityCoordinates? GetCoordinates(NetCoordinates? coordinates) + { + if (coordinates == null) + return null; + + return new EntityCoordinates(GetEntity(coordinates.Value.NetEntity), coordinates.Value.Position); + } + + /// + public virtual EntityCoordinates EnsureCoordinates(NetCoordinates netCoordinates, EntityUid callerEntity) + { + // See EnsureEntity + return GetCoordinates(netCoordinates); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityCoordinates? EnsureCoordinates(NetCoordinates? netCoordinates, EntityUid callerEntity) + { + if (netCoordinates == null) + return null; + + return EnsureCoordinates(netCoordinates.Value, callerEntity); + } + + #endregion + + #region Collection helpers + + /// + public HashSet GetEntitySet(HashSet netEntities) + { + var entities = new HashSet(); + entities.EnsureCapacity(netEntities.Count); + + foreach (var netEntity in netEntities) + { + entities.Add(GetEntity(netEntity)); + } + + return entities; + } + + /// + public List GetEntityList(List netEntities) + { + var entities = new List(netEntities.Count); + + foreach (var netEntity in netEntities) + { + entities.Add(GetEntity(netEntity)); + } + + return entities; + } + + public HashSet EnsureEntitySet(HashSet netEntities, EntityUid callerEntity) + { + var entities = new HashSet(netEntities.Count); + + foreach (var netEntity in netEntities) + { + entities.Add(EnsureEntity(netEntity, callerEntity)); + } + + return entities; + } + + /// + public List EnsureEntityList(List netEntities, EntityUid callerEntity) + { + var entities = new List(netEntities.Count); + + foreach (var netEntity in netEntities) + { + entities.Add(EnsureEntity(netEntity, callerEntity)); + } + + return entities; + } + + /// + public List GetEntityList(ICollection netEntities) + { + var entities = new List(netEntities.Count); + foreach (var netEntity in netEntities) + { + entities.Add(GetEntity(netEntity)); + } + + return entities; + } + + /// + public List GetEntityList(List netEntities) + { + var entities = new List(netEntities.Count); + + foreach (var netEntity in netEntities) + { + entities.Add(GetEntity(netEntity)); + } + + return entities; + } + + /// + public EntityUid[] GetEntityArray(NetEntity[] netEntities) + { + var entities = new EntityUid[netEntities.Length]; + + for (var i = 0; i < netEntities.Length; i++) + { + entities[i] = GetEntity(netEntities[i]); + } + + return entities; + } + + /// + public EntityUid?[] GetEntityArray(NetEntity?[] netEntities) + { + var entities = new EntityUid?[netEntities.Length]; + + for (var i = 0; i < netEntities.Length; i++) + { + entities[i] = GetEntity(netEntities[i]); + } + + return entities; + } + + /// + public HashSet GetNetEntitySet(HashSet entities) + { + var newSet = new HashSet(entities.Count); + + foreach (var ent in entities) + { + MetaQuery.TryGetComponent(ent, out var metadata); + newSet.Add(GetNetEntity(ent, metadata)); + } + + return newSet; + } + + /// + public List GetNetEntityList(List entities) + { + var netEntities = new List(entities.Count); + + foreach (var netEntity in entities) + { + netEntities.Add(GetNetEntity(netEntity)); + } + + return netEntities; + } + + /// + public List GetNetEntityList(IReadOnlyList entities) + { + var netEntities = new List(entities.Count); + + foreach (var netEntity in entities) + { + netEntities.Add(GetNetEntity(netEntity)); + } + + return netEntities; + } + + /// + public List GetNetEntityList(ICollection entities) + { + var netEntities = new List(entities.Count); + + foreach (var netEntity in entities) + { + netEntities.Add(GetNetEntity(netEntity)); + } + + return netEntities; + } + + /// + public List GetNetEntityList(List entities) + { + var netEntities = new List(entities.Count); + + foreach (var netEntity in entities) + { + netEntities.Add(GetNetEntity(netEntity)); + } + + return netEntities; + } + + /// + public NetEntity[] GetNetEntityArray(EntityUid[] entities) + { + var netEntities = new NetEntity[entities.Length]; + + for (var i = 0; i < entities.Length; i++) + { + netEntities[i] = GetNetEntity(entities[i]); + } + + return netEntities; + } + + /// + public NetEntity?[] GetNetEntityArray(EntityUid?[] entities) + { + var netEntities = new NetEntity?[entities.Length]; + + for (var i = 0; i < entities.Length; i++) + { + netEntities[i] = GetNetEntity(entities[i]); + } + + return netEntities; + } + + /// + public HashSet GetEntitySet(HashSet netEntities) + { + var entities = new HashSet(netEntities.Count); + + foreach (var netCoordinates in netEntities) + { + entities.Add(GetCoordinates(netCoordinates)); + } + + return entities; + } + + /// + public List GetEntityList(List netEntities) + { + var entities = new List(netEntities.Count); + + foreach (var netCoordinates in netEntities) + { + entities.Add(GetCoordinates(netCoordinates)); + } + + return entities; + } + + /// + public List GetEntityList(ICollection netEntities) + { + var entities = new List(netEntities.Count); + + foreach (var netCoordinates in netEntities) + { + entities.Add(GetCoordinates(netCoordinates)); + } + + return entities; + } + + /// + public List GetEntityList(List netEntities) + { + var entities = new List(netEntities.Count); + + foreach (var netCoordinates in netEntities) + { + entities.Add(GetCoordinates(netCoordinates)); + } + + return entities; + } + + /// + public EntityCoordinates[] GetEntityArray(NetCoordinates[] netEntities) + { + var entities = new EntityCoordinates[netEntities.Length]; + + for (var i = 0; i < netEntities.Length; i++) + { + entities[i] = GetCoordinates(netEntities[i]); + } + + return entities; + } + + /// + public EntityCoordinates?[] GetEntityArray(NetCoordinates?[] netEntities) + { + var entities = new EntityCoordinates?[netEntities.Length]; + + for (var i = 0; i < netEntities.Length; i++) + { + entities[i] = GetCoordinates(netEntities[i]); + } + + return entities; + } + + /// + public HashSet GetNetCoordinatesSet(HashSet entities) + { + var newSet = new HashSet(entities.Count); + + foreach (var coordinates in entities) + { + newSet.Add(GetNetCoordinates(coordinates)); + } + + return newSet; + } + + /// + public List GetNetCoordinatesList(List entities) + { + var netEntities = new List(entities.Count); + + foreach (var netCoordinates in entities) + { + netEntities.Add(GetNetCoordinates(netCoordinates)); + } + + return netEntities; + } + + /// + public List GetNetCoordinatesList(ICollection entities) + { + var netEntities = new List(entities.Count); + + foreach (var netCoordinates in entities) + { + netEntities.Add(GetNetCoordinates(netCoordinates)); + } + + return netEntities; + } + + /// + public List GetNetCoordinatesList(List entities) + { + var netEntities = new List(entities.Count); + + foreach (var netCoordinates in entities) + { + netEntities.Add(GetNetCoordinates(netCoordinates)); + } + + return netEntities; + } + + /// + public NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities) + { + var netEntities = new NetCoordinates[entities.Length]; + + for (var i = 0; i < entities.Length; i++) + { + netEntities[i] = GetNetCoordinates(entities[i]); + } + + return netEntities; + } + + /// + public NetCoordinates?[] GetNetCoordinatesArray(EntityCoordinates?[] entities) + { + var netEntities = new NetCoordinates?[entities.Length]; + + for (var i = 0; i < entities.Length; i++) + { + netEntities[i] = GetNetCoordinates(entities[i]); + } + + return netEntities; + } + + #endregion +} diff --git a/Robust.Shared/GameObjects/EntityManager.Spawn.cs b/Robust.Shared/GameObjects/EntityManager.Spawn.cs index 1026afa68..9e8a3b40a 100644 --- a/Robust.Shared/GameObjects/EntityManager.Spawn.cs +++ b/Robust.Shared/GameObjects/EntityManager.Spawn.cs @@ -13,11 +13,11 @@ public partial class EntityManager // This method will soon be marked as obsolete. public EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null) => SpawnAttachedTo(protoName, coordinates, overrides); - + // This method will soon be marked as obsolete. public EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null) => Spawn(protoName, coordinates, overrides); - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames) { @@ -100,8 +100,8 @@ public partial class EntityManager if (!xform.ParentUid.IsValid()) return false; - - if (!_metaQuery.TryGetComponent(target, out var meta)) + + if (!MetaQuery.TryGetComponent(target, out var meta)) return false; if ((meta.Flags & MetaDataFlags.InContainer) == 0) @@ -135,7 +135,7 @@ public partial class EntityManager EntityUid containerUid, string containerId, [NotNullWhen(true)] out EntityUid? uid, - ContainerManagerComponent? containerComp = null, + ContainerManagerComponent? containerComp = null, ComponentRegistry? overrides = null) { uid = null; @@ -160,7 +160,7 @@ public partial class EntityManager xform ??= _xformQuery.GetComponent(target); if (!xform.ParentUid.IsValid()) return Spawn(protoName); - + var uid = Spawn(protoName, overrides); _xforms.PlaceNextToOrDrop(uid, target); return uid; @@ -180,7 +180,7 @@ public partial class EntityManager || !containerComp.Containers.TryGetValue(containerId, out var container) || !container.Insert(uid, this)) { - + xform ??= _xformQuery.GetComponent(containerUid); if (xform.ParentUid.IsValid()) _xforms.PlaceNextToOrDrop(uid, containerUid, targetXform: xform); diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs index 01dd04263..9f41bc266 100644 --- a/Robust.Shared/GameObjects/EntityManager.cs +++ b/Robust.Shared/GameObjects/EntityManager.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; -using Robust.Shared.Physics; using Robust.Shared.Serialization.Markdown.Mapping; namespace Robust.Shared.GameObjects @@ -38,7 +37,7 @@ namespace Robust.Shared.GameObjects // positions on spawn.... private SharedTransformSystem _xforms = default!; - private EntityQuery _metaQuery; + protected EntityQuery MetaQuery; private EntityQuery _xformQuery; #endregion Dependencies @@ -68,14 +67,16 @@ namespace Robust.Shared.GameObjects private EntityEventBus _eventBus = null!; - protected virtual int NextEntityUid { get; set; } = (int) EntityUid.FirstUid; + protected int NextEntityUid = (int) EntityUid.FirstUid; + + protected int NextNetworkId = (int) NetEntity.First; /// public IEventBus EventBus => _eventBus; public event Action? EntityAdded; public event Action? EntityInitialized; - public event Action? EntityDeleted; + public event Action? EntityDeleted; /// /// Raised when an entity is queued for deletion. Not raised if an entity is deleted. @@ -123,7 +124,7 @@ namespace Robust.Shared.GameObjects /// public bool IsDefault(EntityUid uid) { - if (!_metaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null) + if (!MetaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null) return false; var prototype = metadata.EntityPrototype; @@ -232,7 +233,7 @@ namespace Robust.Shared.GameObjects _eventBus.CalcOrdering(); _mapSystem = System(); _xforms = System(); - _metaQuery = GetEntityQuery(); + MetaQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); } @@ -304,19 +305,19 @@ namespace Robust.Shared.GameObjects public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null) { - return CreateEntity(prototypeName, euid, overrides); + return CreateEntity(prototypeName, overrides); } /// public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null) { - return CreateEntity(prototypeName, default, overrides); + return CreateEntity(prototypeName, overrides); } /// public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null) { - var newEntity = CreateEntity(prototypeName, default, overrides); + var newEntity = CreateEntity(prototypeName, overrides); _xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false); return newEntity; } @@ -324,7 +325,7 @@ namespace Robust.Shared.GameObjects /// public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null) { - var newEntity = CreateEntity(prototypeName, default, overrides); + var newEntity = CreateEntity(prototypeName, overrides); var transform = _xformQuery.GetComponent(newEntity); if (coordinates.MapId == MapId.Nullspace) @@ -415,14 +416,10 @@ namespace Robust.Shared.GameObjects if (!Started) return; - var metaQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var xformSys = EntitySysManager.GetEntitySystem(); - // Networking blindly spams entities at this function, they can already be // deleted from being a child of a previously deleted entity // TODO: Why does networking need to send deletes for child entities? - if (!metaQuery.TryGetComponent(e, out var meta) || meta.EntityDeleted) + if (!MetaQuery.TryGetComponent(e, out var meta) || meta.EntityDeleted) return; if (meta.EntityLifeStage == EntityLifeStage.Terminating) @@ -436,19 +433,17 @@ namespace Robust.Shared.GameObjects } // Notify all entities they are being terminated prior to detaching & deleting - RecursiveFlagEntityTermination(e, meta, metaQuery, xformQuery); + RecursiveFlagEntityTermination(e, meta); // Then actually delete them - RecursiveDeleteEntity(e, meta, metaQuery, xformQuery, xformSys); + RecursiveDeleteEntity(e, meta); } private void RecursiveFlagEntityTermination( EntityUid uid, - MetaDataComponent metadata, - EntityQuery metaQuery, - EntityQuery xformQuery) + MetaDataComponent metadata) { - var transform = xformQuery.GetComponent(uid); + var transform = _xformQuery.GetComponent(uid); metadata.EntityLifeStage = EntityLifeStage.Terminating; try @@ -463,27 +458,24 @@ namespace Robust.Shared.GameObjects foreach (var child in transform._children) { - if (!metaQuery.TryGetComponent(child, out var childMeta) || childMeta.EntityDeleted) + if (!MetaQuery.TryGetComponent(child, out var childMeta) || childMeta.EntityDeleted) { _sawmill.Error($"A deleted entity was still the transform child of another entity. Parent: {ToPrettyString(uid, metadata)}."); transform._children.Remove(child); continue; } - RecursiveFlagEntityTermination(child, childMeta, metaQuery, xformQuery); + RecursiveFlagEntityTermination(child, childMeta); } } private void RecursiveDeleteEntity( EntityUid uid, - MetaDataComponent metadata, - EntityQuery metaQuery, - EntityQuery xformQuery, - SharedTransformSystem xformSys) + MetaDataComponent metadata) { // Note about this method: #if EXCEPTION_TOLERANCE is not used here because we're gonna it in the future... - - var transform = xformQuery.GetComponent(uid); + var netEntity = GetNetEntity(uid, metadata); + var transform = _xformQuery.GetComponent(uid); // Detach the base entity to null before iterating over children // This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy). @@ -491,7 +483,7 @@ namespace Robust.Shared.GameObjects { try { - xformSys.DetachParentToNull(uid, transform); + _xforms.DetachParentToNull(uid, transform); } catch (Exception e) { @@ -503,7 +495,7 @@ namespace Robust.Shared.GameObjects { try { - RecursiveDeleteEntity(child, metaQuery.GetComponent(child), metaQuery, xformQuery, xformSys); + RecursiveDeleteEntity(child, MetaQuery.GetComponent(child)); } catch(Exception e) { @@ -536,7 +528,7 @@ namespace Robust.Shared.GameObjects try { - EntityDeleted?.Invoke(uid); + EntityDeleted?.Invoke(uid, metadata); } catch (Exception e) { @@ -545,6 +537,8 @@ namespace Robust.Shared.GameObjects _eventBus.OnEntityDeleted(uid); Entities.Remove(uid); + // Need to get the ID above before MetadataComponent shutdown but only remove it after everything else is done. + NetEntityLookup.Remove(netEntity); } public virtual void QueueDeleteEntity(EntityUid uid) @@ -574,7 +568,7 @@ namespace Robust.Shared.GameObjects if (uid == null) return false; - return _metaQuery.Resolve(uid.Value, ref metadata) && metadata.EntityPaused; + return MetaQuery.Resolve(uid.Value, ref metadata) && metadata.EntityPaused; } public bool Deleted(EntityUid uid) @@ -608,10 +602,9 @@ namespace Robust.Shared.GameObjects /// private protected EntityUid AllocEntity( EntityPrototype? prototype, - out MetaDataComponent metadata, - EntityUid uid = default) + out MetaDataComponent metadata) { - var entity = AllocEntity(out metadata, uid); + var entity = AllocEntity(out metadata); metadata._entityPrototype = prototype; Dirty(entity, metadata, metadata); return entity; @@ -620,25 +613,30 @@ namespace Robust.Shared.GameObjects /// /// Allocates an entity and stores it but does not load components or do initialization. /// - private EntityUid AllocEntity(out MetaDataComponent metadata, EntityUid uid = default) + private EntityUid AllocEntity(out MetaDataComponent metadata) { - if (uid == default) - { - uid = GenerateEntityUid(); - } + var uid = GenerateEntityUid(); +#if DEBUG if (EntityExists(uid)) { throw new InvalidOperationException($"UID already taken: {uid}"); } +#endif // we want this called before adding components EntityAdded?.Invoke(uid); _eventBus.OnEntityAdded(uid); + var netEntity = GenerateNetEntity(); + metadata = new MetaDataComponent + { #pragma warning disable CS0618 - metadata = new MetaDataComponent { Owner = uid }; + Owner = uid, #pragma warning restore CS0618 + }; + + SetNetEntity(uid, netEntity, metadata); Entities.Add(uid); // add the required MetaDataComponent directly. @@ -653,23 +651,23 @@ namespace Robust.Shared.GameObjects /// /// Allocates an entity and loads components but does not do initialization. /// - private protected virtual EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null) + private protected virtual EntityUid CreateEntity(string? prototypeName, IEntityLoadContext? context = null) { if (prototypeName == null) - return AllocEntity(out _, uid); + return AllocEntity(out _); if (!PrototypeManager.TryIndex(prototypeName, out var prototype)) throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}"); - return CreateEntity(prototype, uid, context); + return CreateEntity(prototype, context); } /// /// Allocates an entity and loads components but does not do initialization. /// - private protected EntityUid CreateEntity(EntityPrototype prototype, EntityUid uid = default, IEntityLoadContext? context = null) + private protected EntityUid CreateEntity(EntityPrototype prototype, IEntityLoadContext? context = null) { - var entity = AllocEntity(prototype, out var metadata, uid); + var entity = AllocEntity(prototype, out var metadata); try { EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, ComponentFactory, this, _serManager, context); @@ -686,7 +684,7 @@ namespace Robust.Shared.GameObjects private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context) { - EntityPrototype.LoadEntity(_metaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context); + EntityPrototype.LoadEntity(MetaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context); } private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype) @@ -698,7 +696,7 @@ namespace Robust.Shared.GameObjects { try { - var meta = _metaQuery.GetComponent(entity); + var meta = MetaQuery.GetComponent(entity); InitializeEntity(entity, meta); StartEntity(entity); @@ -747,6 +745,12 @@ namespace Robust.Shared.GameObjects return ToPrettyString(uid, metadata); } + /// + public EntityStringRepresentation ToPrettyString(NetEntity netEntity) + { + return ToPrettyString(GetEntity(netEntity)); + } + private EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent metadata) { return new EntityStringRepresentation(uid, metadata.EntityDeleted, metadata.EntityName, metadata.EntityPrototype?.ID); @@ -764,12 +768,16 @@ namespace Robust.Shared.GameObjects /// /// Factory for generating a new EntityUid for an entity currently being created. /// - /// - protected virtual EntityUid GenerateEntityUid() + internal EntityUid GenerateEntityUid() { - return new(NextEntityUid++); + return new EntityUid(NextEntityUid++); } + /// + /// Generates a unique network id and increments + /// + protected virtual NetEntity GenerateNetEntity() => new(NextNetworkId++); + private sealed class EntityDiffContext : ISerializationContext { public SerializationManager.SerializerProvider SerializerProvider { get; } diff --git a/Robust.Shared/GameObjects/EntityState.cs b/Robust.Shared/GameObjects/EntityState.cs index 604e19341..baaa9b2f5 100644 --- a/Robust.Shared/GameObjects/EntityState.cs +++ b/Robust.Shared/GameObjects/EntityState.cs @@ -9,7 +9,10 @@ namespace Robust.Shared.GameObjects [Serializable, NetSerializable] public sealed class EntityState { - public EntityUid Uid { get; } + /// + /// Network identifier for the entity. + /// + public NetEntity NetEntity; public NetListAsArray ComponentChanges { get; } @@ -23,9 +26,9 @@ namespace Robust.Shared.GameObjects /// public HashSet? NetComponents; - public EntityState(EntityUid uid, NetListAsArray changedComponents, GameTick lastModified, HashSet? netComps = null) + public EntityState(NetEntity netEntity, NetListAsArray changedComponents, GameTick lastModified, HashSet? netComps = null) { - Uid = uid; + NetEntity = netEntity; ComponentChanges = changedComponents; EntityLastModified = lastModified; NetComponents = netComps; diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index 924790975..beb76a3ba 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -403,6 +403,13 @@ public partial class EntitySystem return EntityManager.ToPrettyString(uid); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityStringRepresentation ToPrettyString(NetEntity netEntity) + { + return EntityManager.ToPrettyString(netEntity); + } + #endregion #region Component Get @@ -886,4 +893,344 @@ public partial class EntitySystem } #endregion + + #region NetEntities + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsClientSide(EntityUid entity) + { + return EntityManager.IsClientSide(entity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(NetEntity nEntity, [NotNullWhen(true)] out EntityUid? entity) + { + return EntityManager.TryGetEntity(nEntity, out entity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetEntity(NetEntity? nEntity, [NotNullWhen(true)] out EntityUid? entity) + { + return EntityManager.TryGetEntity(nEntity, out entity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetNetEntity(EntityUid uid, [NotNullWhen(true)] out NetEntity? netEntity, MetaDataComponent? metadata = null) + { + return EntityManager.TryGetNetEntity(uid, out netEntity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetNetEntity(EntityUid? uid, [NotNullWhen(true)] out NetEntity? netEntity, MetaDataComponent? metadata = null) + { + return EntityManager.TryGetNetEntity(uid, out netEntity); + } + + /// + /// Returns the of an entity. Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected NetEntity GetNetEntity(EntityUid uid, MetaDataComponent? metadata = null) + { + return EntityManager.GetNetEntity(uid, metadata); + } + + /// + /// Returns the of an entity. Logs an error if the entity does not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected NetEntity? GetNetEntity(EntityUid? uid, MetaDataComponent? metadata = null) + { + return EntityManager.GetNetEntity(uid, metadata); + } + + /// + /// Returns the of an entity or creates a new entity if none exists. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid EnsureEntity(NetEntity netEntity, EntityUid callerEntity) + { + return EntityManager.EnsureEntity(netEntity, callerEntity); + } + + /// + /// Returns the of an entity or creates a new one if not null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid? EnsureEntity(NetEntity? netEntity, EntityUid callerEntity) + { + return EntityManager.EnsureEntity(netEntity, callerEntity); + } + + /// + /// Returns the of an entity or creates a new entity if none exists. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityCoordinates EnsureCoordinates(NetCoordinates netCoordinates, EntityUid callerEntity) + { + return EntityManager.EnsureCoordinates(netCoordinates, callerEntity); + } + + /// + /// Returns the of an entity or creates a new one if not null. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityCoordinates? EnsureCoordinates(NetCoordinates? netCoordinates, EntityUid callerEntity) + { + return EntityManager.EnsureCoordinates(netCoordinates, callerEntity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashSet EnsureEntitySet(HashSet netEntities, EntityUid callerEntity) + { + return EntityManager.EnsureEntitySet(netEntities, callerEntity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List EnsureEntityList(List netEntities, EntityUid callerEntity) + { + return EntityManager.EnsureEntityList(netEntities, callerEntity); + } + + /// + /// Returns the of a . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid GetEntity(NetEntity netEntity) + { + return EntityManager.GetEntity(netEntity); + } + + /// + /// Returns the of a . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid? GetEntity(NetEntity? netEntity) + { + return EntityManager.GetEntity(netEntity); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected HashSet GetNetEntitySet(HashSet uids) + { + return EntityManager.GetNetEntitySet(uids); + } + + /// + /// Returns the versions of the supplied . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected HashSet GetEntitySet(HashSet netEntities) + { + return EntityManager.GetEntitySet(netEntities); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetNetEntityList(ICollection uids) + { + return EntityManager.GetNetEntityList(uids); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetNetEntityList(IReadOnlyList uids) + { + return EntityManager.GetNetEntityList(uids); + } + + /// + /// Returns the versions of the supplied . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetEntityList(ICollection netEntities) + { + return EntityManager.GetEntityList(netEntities); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetNetEntityList(List uids) + { + return EntityManager.GetNetEntityList(uids); + } + + /// + /// Returns the versions of the supplied . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetEntityList(List netEntities) + { + return EntityManager.GetEntityList(netEntities); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetNetEntityList(List uids) + { + return EntityManager.GetNetEntityList(uids); + } + + /// + /// Returns the versions of the supplied . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected List GetEntityList(List netEntities) + { + return EntityManager.GetEntityList(netEntities); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected NetEntity[] GetNetEntityArray(EntityUid[] uids) + { + return EntityManager.GetNetEntityArray(uids); + } + + /// + /// Returns the versions of the supplied . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid[] GetEntityArray(NetEntity[] netEntities) + { + return EntityManager.GetEntityArray(netEntities); + } + + /// + /// Returns the versions of the supplied entities. Logs an error if the entities do not exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected NetEntity?[] GetNetEntityArray(EntityUid?[] uids) + { + return EntityManager.GetNetEntityArray(uids); + } + + /// + /// Returns the versions of the supplied . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityUid?[] GetEntityArray(NetEntity?[] netEntities) + { + return EntityManager.GetEntityArray(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected NetCoordinates GetNetCoordinates(EntityCoordinates coordinates, MetaDataComponent? metadata = null) + { + return EntityManager.GetNetCoordinates(coordinates, metadata); + } + + /// + /// Returns the of an entity. Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected NetCoordinates? GetNetCoordinates(EntityCoordinates? coordinates, MetaDataComponent? metadata = null) + { + return EntityManager.GetNetCoordinates(coordinates, metadata); + } + + /// + /// Returns the of a . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityCoordinates GetCoordinates(NetCoordinates netEntity) + { + return EntityManager.GetCoordinates(netEntity); + } + + /// + /// Returns the of a . Returns if it doesn't exist. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected EntityCoordinates? GetCoordinates(NetCoordinates? netEntity) + { + return EntityManager.GetCoordinates(netEntity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashSet GetEntitySet(HashSet netEntities) + { + return EntityManager.GetEntitySet(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List GetEntityList(List netEntities) + { + return EntityManager.GetEntityList(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List GetEntityList(ICollection netEntities) + { + return EntityManager.GetEntityList(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List GetEntityList(List netEntities) + { + return EntityManager.GetEntityList(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityCoordinates[] GetEntityArray(NetCoordinates[] netEntities) + { + return EntityManager.GetEntityArray(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityCoordinates?[] GetEntityArray(NetCoordinates?[] netEntities) + { + return EntityManager.GetEntityArray(netEntities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashSet GetNetCoordinatesSet(HashSet entities) + { + return EntityManager.GetNetCoordinatesSet(entities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List GetNetCoordinatesList(List entities) + { + return EntityManager.GetNetCoordinatesList(entities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List GetNetCoordinatesList(ICollection entities) + { + return EntityManager.GetNetCoordinatesList(entities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List GetNetCoordinatesList(List entities) + { + return EntityManager.GetNetCoordinatesList(entities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities) + { + return EntityManager.GetNetCoordinatesArray(entities); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NetCoordinates?[] GetNetCoordinatesArray(EntityCoordinates?[] entities) + { + return EntityManager.GetNetCoordinatesArray(entities); + } + + #endregion } diff --git a/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs b/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs index 223db7d27..cbc5e56f3 100644 --- a/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs +++ b/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs @@ -31,14 +31,14 @@ public sealed class PlayAudioGlobalMessage : AudioMessage [Serializable, NetSerializable] public sealed class PlayAudioPositionalMessage : AudioMessage { - public EntityCoordinates Coordinates { get; set; } - public EntityCoordinates FallbackCoordinates { get; set; } + public NetCoordinates Coordinates { get; set; } + public NetCoordinates FallbackCoordinates { get; set; } } [Serializable, NetSerializable] public sealed class PlayAudioEntityMessage : AudioMessage { - public EntityUid EntityUid { get; set; } - public EntityCoordinates Coordinates { get; set; } - public EntityCoordinates FallbackCoordinates { get; set; } + public NetEntity NetEntity { get; set; } + public NetCoordinates Coordinates { get; set; } + public NetCoordinates FallbackCoordinates { get; set; } } diff --git a/Robust.Shared/GameObjects/EntityUid.cs b/Robust.Shared/GameObjects/EntityUid.cs index 380680672..88177a7f4 100644 --- a/Robust.Shared/GameObjects/EntityUid.cs +++ b/Robust.Shared/GameObjects/EntityUid.cs @@ -2,7 +2,6 @@ using System; using JetBrains.Annotations; using Robust.Shared.IoC; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -14,15 +13,10 @@ namespace Robust.Shared.GameObjects /// This type contains a network identification number of an entity. /// This can be used by the EntityManager to access an entity /// - [Serializable, NetSerializable, CopyByRef] + [CopyByRef] public readonly struct EntityUid : IEquatable, IComparable, ISpanFormattable { - /// - /// If this bit is set on a UID, it's client sided. - /// Use to check this. - /// - internal const int ClientUid = 2 << 29; - readonly int _uid; + public readonly int Id; /// /// An Invalid entity UID you can compare against. @@ -37,9 +31,9 @@ namespace Robust.Shared.GameObjects /// /// Creates an instance of this structure, with the given network ID. /// - public EntityUid(int uid) + public EntityUid(int id) { - _uid = uid; + Id = id; } public bool Valid => IsValid(); @@ -49,14 +43,7 @@ namespace Robust.Shared.GameObjects /// public static EntityUid Parse(ReadOnlySpan uid) { - if (uid.StartsWith("c")) - { - return new EntityUid(int.Parse(uid[1..]) | ClientUid); - } - else - { - return new EntityUid(int.Parse(uid)); - } + return new EntityUid(int.Parse(uid)); } public static bool TryParse(ReadOnlySpan uid, out EntityUid entityUid) @@ -80,19 +67,13 @@ namespace Robust.Shared.GameObjects [Pure] public bool IsValid() { - return _uid > 0; - } - - [Pure] - public bool IsClientSide() - { - return (_uid & (2 << 29)) != 0; + return Id > 0; } /// public bool Equals(EntityUid other) { - return _uid == other._uid; + return Id == other.Id; } /// @@ -105,7 +86,7 @@ namespace Robust.Shared.GameObjects /// public override int GetHashCode() { - return _uid; + return Id; } /// @@ -113,7 +94,7 @@ namespace Robust.Shared.GameObjects /// public static bool operator ==(EntityUid a, EntityUid b) { - return a._uid == b._uid; + return a.Id == b.Id; } /// @@ -130,17 +111,13 @@ namespace Robust.Shared.GameObjects /// public static explicit operator int(EntityUid self) { - return self._uid; + return self.Id; } /// public override string ToString() { - if (IsClientSide()) - { - return $"c{_uid & ~ClientUid}"; - } - return _uid.ToString(); + return Id.ToString(); } public string ToString(string? format, IFormatProvider? formatProvider) @@ -154,21 +131,13 @@ namespace Robust.Shared.GameObjects ReadOnlySpan format, IFormatProvider? provider) { - if (IsClientSide()) - { - return FormatHelpers.TryFormatInto( - destination, - out charsWritten, - $"c{_uid & ~ClientUid}"); - } - - return _uid.TryFormat(destination, out charsWritten); + return Id.TryFormat(destination, out charsWritten); } /// public int CompareTo(EntityUid other) { - return _uid.CompareTo(other._uid); + return Id.CompareTo(other.Id); } #region ViewVariables diff --git a/Robust.Shared/GameObjects/IEntityManager.Network.cs b/Robust.Shared/GameObjects/IEntityManager.Network.cs new file mode 100644 index 000000000..009ece5aa --- /dev/null +++ b/Robust.Shared/GameObjects/IEntityManager.Network.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Map; + +namespace Robust.Shared.GameObjects; + +public partial interface IEntityManager +{ + /// + /// Tries to parse a string as a NetEntity and return the relevant EntityUid. + /// + public bool TryParseNetEntity(string arg, [NotNullWhen(true)] out EntityUid? entity); + + /// + /// TryGet version of + /// + public bool TryGetEntity(NetEntity nEntity, [NotNullWhen(true)] out EntityUid? entity); + + /// + /// TryGet version of + /// + public bool TryGetEntity(NetEntity? nEntity, [NotNullWhen(true)] out EntityUid? entity); + + /// + /// TryGet version of + /// + public bool TryGetNetEntity(EntityUid uid, [NotNullWhen(true)] out NetEntity? netEntity, MetaDataComponent? metadata = null); + + /// + /// TryGet version of + /// + public bool TryGetNetEntity(EntityUid? uid, [NotNullWhen(true)] out NetEntity? netEntity, + MetaDataComponent? metadata = null); + + /// + /// Returns true if the entity only exists on the client. + /// + public bool IsClientSide(EntityUid uid, MetaDataComponent? metadata = null); + + /// + /// Tries to get a corresponding if it exists, otherwise creates an entity for it. + /// + /// The net entity we're trying to resolve. + /// The type of the component that may need its state handling run later. + /// The entity trying to resolve the net entity. This may be flagged for later component state handling. + public EntityUid EnsureEntity(NetEntity nEntity, EntityUid callerEntity); + + /// + /// Tries to get a corresponding if it exists and nEntity is not null. + /// + public EntityUid? EnsureEntity(NetEntity? nEntity, EntityUid callerEntity); + + /// + /// Returns the corresponding local . + /// + public EntityUid GetEntity(NetEntity nEntity); + + /// + /// Returns the corresponding local . + /// + public EntityUid? GetEntity(NetEntity? nEntity); + + /// + /// Returns the corresponding for the local entity. + /// + public NetEntity GetNetEntity(EntityUid uid, MetaDataComponent? metadata = null); + + /// + /// Returns the corresponding for the local entity. + /// + public NetEntity? GetNetEntity(EntityUid? uid, MetaDataComponent? metadata = null); + + /// + /// HashSet version of + /// + public HashSet GetEntitySet(HashSet netEntities); + + /// + /// List version of + /// + public List GetEntityList(List netEntities); + + /// + /// List version of + /// + public List GetEntityList(ICollection netEntities); + + /// + /// List version of + /// + public List GetEntityList(List netEntities); + + /// + /// List version of + /// + EntityUid[] GetEntityArray(NetEntity[] netEntities); + + /// + /// List version of + /// + EntityUid?[] GetEntityArray(NetEntity?[] netEntities); + + /// + /// HashSet version of + /// + public HashSet GetNetEntitySet(HashSet entities); + + /// + /// List version of + /// + public List GetNetEntityList(List entities); + + /// + /// List version of + /// + public List GetNetEntityList(ICollection entities); + + /// + /// List version of + /// + public List GetNetEntityList(List entities); + + /// + /// List version of + /// + NetEntity[] GetNetEntityArray(EntityUid[] entities); + + /// + /// List version of + /// + NetEntity?[] GetNetEntityArray(EntityUid?[] entities); + + /// + /// Returns the corresponding for the specified local coordinates. + /// + public NetCoordinates GetNetCoordinates(EntityCoordinates coordinates, MetaDataComponent? metadata = null); + + /// + /// Returns the corresponding for the specified local coordinates. + /// + public NetCoordinates? GetNetCoordinates(EntityCoordinates? coordinates, MetaDataComponent? metadata = null); + + /// + /// Returns the corresponding for the specified network coordinates. + /// + public EntityCoordinates GetCoordinates(NetCoordinates coordinates); + + /// + /// Returns the corresponding for the specified network coordinates. + /// + public EntityCoordinates? GetCoordinates(NetCoordinates? coordinates); + + /// + /// Tries to get a corresponding if it exists, otherwise creates an entity for it. + /// + /// The net coordinates we're trying to resolve. + /// The type of the component that may need its state handling run later. + /// The entity trying to resolve the net entity. This may be flagged for later component state handling. + public EntityCoordinates EnsureCoordinates(NetCoordinates netCoordinates, EntityUid callerEntity); + + /// + /// Tries to get a corresponding if it exists and nEntity is not null. + /// + public EntityCoordinates? EnsureCoordinates(NetCoordinates? netCoordinates, EntityUid callerEntity); + + public HashSet GetEntitySet(HashSet netEntities); + + public List GetEntityList(List netEntities); + + public HashSet EnsureEntitySet(HashSet netEntities, EntityUid callerEntity); + + public List EnsureEntityList(List netEntities, EntityUid callerEntity); + + public List GetEntityList(ICollection netEntities); + + public List GetEntityList(List netEntities); + + public EntityCoordinates[] GetEntityArray(NetCoordinates[] netEntities); + + public EntityCoordinates?[] GetEntityArray(NetCoordinates?[] netEntities); + + public HashSet GetNetCoordinatesSet(HashSet entities); + + public List GetNetCoordinatesList(List entities); + + public List GetNetCoordinatesList(ICollection entities); + + public List GetNetCoordinatesList(List entities); + + public NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities); + + public NetCoordinates?[] GetNetCoordinatesArray(EntityCoordinates?[] entities); +} diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs index a4e798619..abcc81e53 100644 --- a/Robust.Shared/GameObjects/IEntityManager.cs +++ b/Robust.Shared/GameObjects/IEntityManager.cs @@ -53,7 +53,7 @@ namespace Robust.Shared.GameObjects event Action? EntityAdded; event Action? EntityInitialized; - event Action? EntityDeleted; + event Action? EntityDeleted; event Action? EntityDirtied; // only raised after initialization EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null); diff --git a/Robust.Shared/GameObjects/NetEntity.cs b/Robust.Shared/GameObjects/NetEntity.cs new file mode 100644 index 000000000..e32fba9d6 --- /dev/null +++ b/Robust.Shared/GameObjects/NetEntity.cs @@ -0,0 +1,226 @@ +using System; +using JetBrains.Annotations; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; +using Robust.Shared.ViewVariables; + +namespace Robust.Shared.GameObjects; + +/// +/// Network identifier for entities; used by client and server to refer to the same entity where their local may differ. +/// +[Serializable, NetSerializable] +public readonly struct NetEntity : IEquatable, IComparable, ISpanFormattable +{ + public readonly int Id; + + public const int ClientEntity = 2 << 29; + + /* + * Differed to EntityUid to be more consistent with Arch. + */ + + /// + /// An Invalid entity UID you can compare against. + /// + public static readonly NetEntity Invalid = new(0); + + /// + /// The first entity UID the entityManager should use when the manager is initialized. + /// + public static readonly NetEntity First = new(1); + + /// + /// Creates an instance of this structure, with the given network ID. + /// + public NetEntity(int id) + { + Id = id; + } + + public bool Valid => IsValid(); + + /// + /// Creates a network entity UID by parsing a string number. + /// + public static NetEntity Parse(ReadOnlySpan uid) + { + return new NetEntity(int.Parse(uid)); + } + + public static bool TryParse(ReadOnlySpan uid, out NetEntity entity) + { + try + { + entity = Parse(uid); + return true; + } + catch (FormatException) + { + entity = Invalid; + return false; + } + } + + /// + /// Checks if the ID value is valid. Does not check if it identifies + /// a valid Entity. + /// + [Pure] + public bool IsValid() + { + return Id > 0; + } + + /// + public bool Equals(NetEntity other) + { + return Id == other.Id; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is EntityUid id && Equals(id); + } + + /// + public override int GetHashCode() + { + return Id; + } + + /// + /// Check for equality by value between two objects. + /// + public static bool operator ==(NetEntity a, NetEntity b) + { + return a.Id == b.Id; + } + + /// + /// Check for inequality by value between two objects. + /// + public static bool operator !=(NetEntity a, NetEntity b) + { + return !(a == b); + } + + /// + /// Explicit conversion of EntityId to int. This should only be used in special + /// cases like serialization. Do NOT use this in content. + /// + public static explicit operator int(NetEntity self) + { + return self.Id; + } + + /// + public override string ToString() + { + return Id.ToString(); + } + + public string ToString(string? format, IFormatProvider? formatProvider) + { + return ToString(); + } + + public bool TryFormat( + Span destination, + out int charsWritten, + ReadOnlySpan format, + IFormatProvider? provider) + { + return Id.TryFormat(destination, out charsWritten); + } + + /// + public int CompareTo(NetEntity other) + { + return Id.CompareTo(other.Id); + } + + public bool IsClientSide() => (Id & ClientEntity) == ClientEntity; + + #region ViewVariables + + + [ViewVariables] + private string Representation + { + get + { + var entManager = IoCManager.Resolve(); + return entManager.ToPrettyString(entManager.GetEntity(this)); + } + } + + [ViewVariables(VVAccess.ReadWrite)] + private string Name + { + get => MetaData?.EntityName ?? string.Empty; + set + { + if (MetaData is {} metaData) + metaData.EntityName = value; + } + } + + [ViewVariables(VVAccess.ReadWrite)] + private string Description + { + get => MetaData?.EntityDescription ?? string.Empty; + set + { + if (MetaData is {} metaData) + metaData.EntityDescription = value; + } + } + + [ViewVariables] + private EntityPrototype? Prototype => MetaData?.EntityPrototype; + + [ViewVariables] + private GameTick LastModifiedTick => MetaData?.EntityLastModifiedTick ?? GameTick.Zero; + + [ViewVariables] + private bool Paused => MetaData?.EntityPaused ?? false; + + [ViewVariables] + private EntityLifeStage LifeStage => MetaData?.EntityLifeStage ?? EntityLifeStage.Deleted; + + [ViewVariables] + private MetaDataComponent? MetaData + { + get + { + var entManager = IoCManager.Resolve(); + return entManager.GetComponentOrNull(entManager.GetEntity(this)); + } + } + + [ViewVariables] + private TransformComponent? Transform + { + get + { + var entManager = IoCManager.Resolve(); + return entManager.GetComponentOrNull(entManager.GetEntity(this)); + } + } + + [ViewVariables] + private EntityUid Uid + { + get + { + return IoCManager.Resolve().GetEntity(this); + } + } + + #endregion +} diff --git a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs index e0f85b07a..490828c86 100644 --- a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs @@ -230,7 +230,7 @@ namespace Robust.Shared.GameObjects [Serializable, NetSerializable] public sealed class ChunkSplitDebugMessage : EntityEventArgs { - public EntityUid Grid; + public NetEntity Grid; public Dictionary>> Nodes = new (); public List<(Vector2 Start, Vector2 End)> Connections = new(); } diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index f2e05235b..4744dcf0b 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -480,7 +480,7 @@ public abstract partial class SharedTransformSystem if (value.EntityId == uid) { DetachParentToNull(uid, xform); - if (_netMan.IsServer || uid.IsClientSide()) + if (_netMan.IsServer || IsClientSide(uid)) QueueDel(uid); throw new InvalidOperationException($"Attempted to parent an entity to itself: {ToPrettyString(uid)}"); } @@ -490,7 +490,7 @@ public abstract partial class SharedTransformSystem if (!_xformQuery.Resolve(value.EntityId, ref newParent, false)) { DetachParentToNull(uid, xform); - if (_netMan.IsServer || uid.IsClientSide()) + if (_netMan.IsServer || IsClientSide(uid)) QueueDel(uid); throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}"); } @@ -498,7 +498,7 @@ public abstract partial class SharedTransformSystem if (newParent.LifeStage > ComponentLifeStage.Running || LifeStage(value.EntityId) > EntityLifeStage.MapInitialized) { DetachParentToNull(uid, xform); - if (_netMan.IsServer || uid.IsClientSide()) + if (_netMan.IsServer || IsClientSide(uid)) QueueDel(uid); throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}"); } @@ -625,7 +625,7 @@ public abstract partial class SharedTransformSystem public TransformComponent? GetParent(TransformComponent xform) { - if (!xform.ParentUid.IsValid()) + if (!xform.ParentUid.IsValid()) return null; return _xformQuery.GetComponent(xform.ParentUid); } @@ -678,10 +678,12 @@ public abstract partial class SharedTransformSystem internal void OnGetState(EntityUid uid, TransformComponent component, ref ComponentGetState args) { DebugTools.Assert(!component.ParentUid.IsValid() || (!Deleted(component.ParentUid) && !EntityManager.IsQueuedForDeletion(component.ParentUid))); + var parent = GetNetEntity(component.ParentUid); + args.State = new TransformComponentState( component.LocalPosition, component.LocalRotation, - component.ParentUid, + parent, component.NoLocalRotation, component.Anchored); } @@ -690,13 +692,16 @@ public abstract partial class SharedTransformSystem { if (args.Current is TransformComponentState newState) { - var newParentId = newState.ParentID; + var parent = GetEntity(newState.ParentID); + if (!parent.IsValid() && newState.ParentID.IsValid()) + Log.Error($"Received transform component state with an unknown parent Id. Entity: {ToPrettyString(uid)}. Net parent: {newState.ParentID}"); + var oldAnchored = xform.Anchored; // update actual position data, if required if (!xform.LocalPosition.EqualsApprox(newState.LocalPosition) || !xform.LocalRotation.EqualsApprox(newState.Rotation) - || xform.ParentUid != newParentId) + || xform.ParentUid != parent) { // remove from any old grid lookups if (xform.Anchored && TryComp(xform.ParentUid, out MapGridComponent? grid)) @@ -709,7 +714,7 @@ public abstract partial class SharedTransformSystem xform._anchored |= newState.Anchored; // Update the action position, rotation, and parent (and hence also map, grid, etc). - SetCoordinates(uid, xform, new EntityCoordinates(newParentId, newState.LocalPosition), newState.Rotation, unanchor: false); + SetCoordinates(uid, xform, new EntityCoordinates(parent, newState.LocalPosition), newState.Rotation, unanchor: false); xform._anchored = newState.Anchored; @@ -744,7 +749,7 @@ public abstract partial class SharedTransformSystem xform.PrevRotation = newState.Rotation; xform._noLocalRotation = newState.NoLocalRotation; - DebugTools.Assert(xform.ParentUid == newState.ParentID, "Transform state failed to set parent"); + DebugTools.Assert(xform.ParentUid == parent, "Transform state failed to set parent"); DebugTools.Assert(xform.Anchored == newState.Anchored, "Transform state failed to set anchored"); } @@ -752,7 +757,7 @@ public abstract partial class SharedTransformSystem { xform.NextPosition = nextTransform.LocalPosition; xform.NextRotation = nextTransform.Rotation; - xform.LerpParent = nextTransform.ParentID; + xform.LerpParent = GetEntity(nextTransform.ParentID); ActivateLerp(xform); } else @@ -1375,12 +1380,12 @@ public abstract partial class SharedTransformSystem /// Attempts to place one entity next to another entity. If the target entity is in a container, this will attempt /// to insert that entity into the same container. /// - public void PlaceNextToOrDrop(EntityUid uid, EntityUid target, + public void PlaceNextToOrDrop(EntityUid uid, EntityUid target, TransformComponent? xform = null, TransformComponent? targetXform = null) { if (!_xformQuery.Resolve(target, ref targetXform)) return; - + if (!_xformQuery.Resolve(uid, ref xform)) return; diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs index 9b497c9f4..8805d901f 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs @@ -281,7 +281,7 @@ namespace Robust.Shared.GameObjects /// /// Current parent entity of this entity. /// - public readonly EntityUid ParentID; + public readonly NetEntity ParentID; /// /// Current position offset of the entity. @@ -310,7 +310,7 @@ namespace Robust.Shared.GameObjects /// Current direction offset of this entity. /// Current parent transform of this entity. /// - public TransformComponentState(Vector2 localPosition, Angle rotation, EntityUid parentId, bool noLocalRotation, bool anchored) + public TransformComponentState(Vector2 localPosition, Angle rotation, NetEntity parentId, bool noLocalRotation, bool anchored) { LocalPosition = localPosition; Rotation = rotation; diff --git a/Robust.Shared/GameStates/GameState.cs b/Robust.Shared/GameStates/GameState.cs index 2be912518..e5b5392e4 100644 --- a/Robust.Shared/GameStates/GameState.cs +++ b/Robust.Shared/GameStates/GameState.cs @@ -26,7 +26,7 @@ namespace Robust.Shared.GameStates uint lastInput, NetListAsArray entities, NetListAsArray players, - NetListAsArray deletions) + NetListAsArray deletions) { FromSequence = fromSequence; ToSequence = toSequence; @@ -43,6 +43,6 @@ namespace Robust.Shared.GameStates public readonly NetListAsArray EntityStates; public readonly NetListAsArray PlayerStates; - public readonly NetListAsArray EntityDeletions; + public readonly NetListAsArray EntityDeletions; } } diff --git a/Robust.Shared/GameStates/PlayerState.cs b/Robust.Shared/GameStates/PlayerState.cs index 28f0d5c53..2e93c3440 100644 --- a/Robust.Shared/GameStates/PlayerState.cs +++ b/Robust.Shared/GameStates/PlayerState.cs @@ -17,7 +17,7 @@ namespace Robust.Shared.GameStates public SessionStatus Status { get; set; } public short Ping { get; set; } - public EntityUid? ControlledEntity { get; set; } + public NetEntity? ControlledEntity { get; set; } public PlayerState Clone() { diff --git a/Robust.Shared/Input/Binding/InputCmdHandler.cs b/Robust.Shared/Input/Binding/InputCmdHandler.cs index 7abeaa2a1..6686ccd7c 100644 --- a/Robust.Shared/Input/Binding/InputCmdHandler.cs +++ b/Robust.Shared/Input/Binding/InputCmdHandler.cs @@ -1,4 +1,5 @@ -using Robust.Shared.GameObjects; +using System; +using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Players; @@ -18,7 +19,7 @@ namespace Robust.Shared.Input.Binding { } - public abstract bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message); + public abstract bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message); /// /// Makes a quick input command from enabled and disabled delegates. @@ -56,12 +57,9 @@ namespace Robust.Shared.Input.Binding DisabledDelegate?.Invoke(session); } - public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message) + public override bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message) { - if (!(message is FullInputCmdMessage msg)) - return false; - - switch (msg.State) + switch (message.State) { case BoundKeyState.Up: Disabled(session); @@ -111,14 +109,28 @@ namespace Robust.Shared.Input.Binding FireOutsidePrediction = outsidePrediction; } - public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message) + public override bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message) { - if (!(message is FullInputCmdMessage msg) || (_ignoreUp && msg.State != BoundKeyState.Down)) + if ((_ignoreUp && message.State != BoundKeyState.Down)) return false; - var handled = _callback?.Invoke(new PointerInputCmdArgs(session, msg.Coordinates, - msg.ScreenCoordinates, msg.Uid, msg.State, msg)); - return handled.HasValue && handled.Value; + switch (message) + { + case ClientFullInputCmdMessage clientInput: + { + var handled = _callback?.Invoke(new PointerInputCmdArgs(session, clientInput.Coordinates, + clientInput.ScreenCoordinates, clientInput.Uid, message.State, message)); + return handled.HasValue && handled.Value; + } + case FullInputCmdMessage fullInput: + { + var handled = _callback?.Invoke(new PointerInputCmdArgs(session, entManager.GetCoordinates(fullInput.Coordinates), + fullInput.ScreenCoordinates, entManager.GetEntity(fullInput.Uid), fullInput.State, message)); + return handled.HasValue && handled.Value; + } + default: + throw new ArgumentOutOfRangeException(); + } } public readonly struct PointerInputCmdArgs @@ -128,11 +140,11 @@ namespace Robust.Shared.Input.Binding public readonly ScreenCoordinates ScreenCoordinates; public readonly EntityUid EntityUid; public readonly BoundKeyState State; - public readonly FullInputCmdMessage OriginalMessage; + public readonly IFullInputCmdMessage OriginalMessage; public PointerInputCmdArgs(ICommonSession? session, EntityCoordinates coordinates, ScreenCoordinates screenCoordinates, EntityUid entityUid, BoundKeyState state, - FullInputCmdMessage originalMessage) + IFullInputCmdMessage originalMessage) { Session = session; Coordinates = coordinates; @@ -158,17 +170,28 @@ namespace Robust.Shared.Input.Binding } /// - public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message) + public override bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message) { - if (!(message is FullInputCmdMessage msg)) - return false; - - switch (msg.State) + switch (message) { - case BoundKeyState.Up: - return _disabled?.Invoke(session, msg.Coordinates, msg.Uid) == true; - case BoundKeyState.Down: - return _enabled?.Invoke(session, msg.Coordinates, msg.Uid) == true; + case ClientFullInputCmdMessage clientInput: + switch (clientInput.State) + { + case BoundKeyState.Up: + return _disabled?.Invoke(session, clientInput.Coordinates, clientInput.Uid) == true; + case BoundKeyState.Down: + return _enabled?.Invoke(session, clientInput.Coordinates, clientInput.Uid) == true; + } + break; + case FullInputCmdMessage fullInput: + switch (fullInput.State) + { + case BoundKeyState.Up: + return _disabled?.Invoke(session, entManager.GetCoordinates(fullInput.Coordinates), entManager.GetEntity(fullInput.Uid)) == true; + case BoundKeyState.Down: + return _enabled?.Invoke(session, entManager.GetCoordinates(fullInput.Coordinates), entManager.GetEntity(fullInput.Uid)) == true; + } + break; } //Client Sanitization: unknown key state, just ignore @@ -183,7 +206,7 @@ namespace Robust.Shared.Input.Binding public sealed class NullInputCmdHandler : InputCmdHandler { /// - public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message) + public override bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message) { return true; } diff --git a/Robust.Shared/Input/InputCmdMessage.cs b/Robust.Shared/Input/InputCmdMessage.cs index 56dd8cc4e..520f3cf2b 100644 --- a/Robust.Shared/Input/InputCmdMessage.cs +++ b/Robust.Shared/Input/InputCmdMessage.cs @@ -113,12 +113,12 @@ namespace Robust.Shared.Input /// /// Local Coordinates of the pointer when the command was created. /// - public EntityCoordinates Coordinates { get; } + public NetCoordinates Coordinates { get; } /// /// Entity that was under the pointer when the command was created (if any). /// - public EntityUid Uid { get; } + public NetEntity Uid { get; } /// /// Creates an instance of . @@ -126,8 +126,8 @@ namespace Robust.Shared.Input /// Client tick this was created. /// Function this command is changing. /// Local Coordinates of the pointer when the command was created. - public PointerInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId, EntityCoordinates coordinates) - : this(tick, subTick, inputFunctionId, coordinates, EntityUid.Invalid) { } + public PointerInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId, NetCoordinates coordinates) + : this(tick, subTick, inputFunctionId, coordinates, NetEntity.Invalid) { } /// /// Creates an instance of with an optional Entity reference. @@ -136,7 +136,7 @@ namespace Robust.Shared.Input /// Function this command is changing. /// Local Coordinates of the pointer when the command was created. /// Entity that was under the pointer when the command was created. - public PointerInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId, EntityCoordinates coordinates, EntityUid uid) + public PointerInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId, NetCoordinates coordinates, NetEntity uid) : base(tick, subTick, inputFunctionId) { Coordinates = coordinates; @@ -144,11 +144,51 @@ namespace Robust.Shared.Input } } + /// + /// Handles inputs clientside. This is used so the client can still interact with client-only entities without relying on + /// . + /// + public sealed class ClientFullInputCmdMessage : InputCmdMessage, IFullInputCmdMessage + { + /// + /// New state of the Input Function. + /// + public BoundKeyState State { get; init; } + + /// + /// Local Coordinates of the pointer when the command was created. + /// + public EntityCoordinates Coordinates { get; init; } + + /// + /// Screen Coordinates of the pointer when the command was created. + /// + public ScreenCoordinates ScreenCoordinates { get; init; } + + /// + /// Entity that was under the pointer when the command was created (if any). + /// + public EntityUid Uid { get; init; } + + public ClientFullInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId) : base(tick, subTick, inputFunctionId) + { + } + } + + public interface IFullInputCmdMessage + { + GameTick Tick { get; } + BoundKeyState State { get; } + KeyFunctionId InputFunctionId { get; } + ushort SubTick { get; } + uint InputSequence { get; set; } + } + /// /// An input command that has both state and pointer info. /// [Serializable, NetSerializable] - public sealed class FullInputCmdMessage : InputCmdMessage + public sealed class FullInputCmdMessage : InputCmdMessage, IFullInputCmdMessage { /// /// New state of the Input Function. @@ -158,7 +198,7 @@ namespace Robust.Shared.Input /// /// Local Coordinates of the pointer when the command was created. /// - public EntityCoordinates Coordinates { get; } + public NetCoordinates Coordinates { get; } /// /// Screen Coordinates of the pointer when the command was created. @@ -168,7 +208,7 @@ namespace Robust.Shared.Input /// /// Entity that was under the pointer when the command was created (if any). /// - public EntityUid Uid { get; } + public NetEntity Uid { get; init; } /// /// Creates an instance of . @@ -179,8 +219,8 @@ namespace Robust.Shared.Input /// New state of the Input Function. /// Local Coordinates of the pointer when the command was created. /// - public FullInputCmdMessage(GameTick tick, ushort subTick, int inputSequence, KeyFunctionId inputFunctionId, BoundKeyState state, EntityCoordinates coordinates, ScreenCoordinates screenCoordinates) - : this(tick, subTick, inputFunctionId, state, coordinates, screenCoordinates, EntityUid.Invalid) { } + public FullInputCmdMessage(GameTick tick, ushort subTick, int inputSequence, KeyFunctionId inputFunctionId, BoundKeyState state, NetCoordinates coordinates, ScreenCoordinates screenCoordinates) + : this(tick, subTick, inputFunctionId, state, coordinates, screenCoordinates, NetEntity.Invalid) { } /// /// Creates an instance of with an optional Entity reference. @@ -191,7 +231,7 @@ namespace Robust.Shared.Input /// Local Coordinates of the pointer when the command was created. /// /// Entity that was under the pointer when the command was created. - public FullInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId, BoundKeyState state, EntityCoordinates coordinates, ScreenCoordinates screenCoordinates, EntityUid uid) + public FullInputCmdMessage(GameTick tick, ushort subTick, KeyFunctionId inputFunctionId, BoundKeyState state, NetCoordinates coordinates, ScreenCoordinates screenCoordinates, NetEntity uid) : base(tick, subTick, inputFunctionId) { State = state; diff --git a/Robust.Shared/Map/EntityCoordinates.cs b/Robust.Shared/Map/EntityCoordinates.cs index e769a5e51..b0b461aa4 100644 --- a/Robust.Shared/Map/EntityCoordinates.cs +++ b/Robust.Shared/Map/EntityCoordinates.cs @@ -13,7 +13,6 @@ namespace Robust.Shared.Map /// A set of coordinates relative to another entity. /// [PublicAPI] - [Serializable, NetSerializable] public readonly struct EntityCoordinates : IEquatable, ISpanFormattable { public static readonly EntityCoordinates Invalid = new(EntityUid.Invalid, Vector2.Zero); diff --git a/Robust.Shared/Map/NetCoordinates.cs b/Robust.Shared/Map/NetCoordinates.cs new file mode 100644 index 000000000..46b25f106 --- /dev/null +++ b/Robust.Shared/Map/NetCoordinates.cs @@ -0,0 +1,102 @@ +using System; +using System.Numerics; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Robust.Shared.Map; + +/// +/// A networked version of +/// +[PublicAPI] +[Serializable, NetSerializable] +public readonly struct NetCoordinates : IEquatable, ISpanFormattable +{ + public static readonly NetCoordinates Invalid = new(NetEntity.Invalid, Vector2.Zero); + + /// + /// Networked ID of the entity that this position is relative to. + /// + public readonly NetEntity NetEntity; + + /// + /// Position in the entity's local space. + /// + public readonly Vector2 Position; + + /// + /// Location of the X axis local to the entity. + /// + public float X => Position.X; + + /// + /// Location of the Y axis local to the entity. + /// + public float Y => Position.Y; + + public NetCoordinates(NetEntity netEntity, Vector2 position) + { + NetEntity = netEntity; + Position = position; + } + + public NetCoordinates(NetEntity netEntity, float x, float y) + { + NetEntity = netEntity; + Position = new Vector2(x, y); + } + #region IEquatable + + /// + public bool Equals(NetCoordinates other) + { + return NetEntity.Equals(other.NetEntity) && Position.Equals(other.Position); + } + + /// + public override bool Equals(object? obj) + { + return obj is NetCoordinates other && Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(NetEntity, Position); + } + + #endregion + + /// + /// Deconstructs the object into it's fields. + /// + /// ID of the entity that this position is relative to. + /// Position in the entity's local space. + public void Deconstruct(out NetEntity entId, out Vector2 localPos) + { + entId = NetEntity; + localPos = Position; + } + + /// + public override string ToString() + { + return $"NetEntity={NetEntity}, X={Position.X:N2}, Y={Position.Y:N2}"; + } + + public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); + + public bool TryFormat( + Span destination, + out int charsWritten, + ReadOnlySpan format, + IFormatProvider? provider) + { + return FormatHelpers.TryFormatInto( + destination, + out charsWritten, + $"NetEntity={NetEntity}, X={Position.X:N2}, Y={Position.Y:N2}"); + } +} diff --git a/Robust.Shared/Network/Messages/MsgPlacement.cs b/Robust.Shared/Network/Messages/MsgPlacement.cs index 21ee1ec56..b66e50b77 100644 --- a/Robust.Shared/Network/Messages/MsgPlacement.cs +++ b/Robust.Shared/Network/Messages/MsgPlacement.cs @@ -25,9 +25,9 @@ namespace Robust.Shared.Network.Messages public bool IsTile { get; set; } public int TileType { get; set; } public string EntityTemplateName { get; set; } - public EntityCoordinates EntityCoordinates { get; set; } + public NetCoordinates NetCoordinates { get; set; } public Direction DirRcv { get; set; } - public EntityUid EntityUid { get; set; } + public NetEntity EntityUid { get; set; } public int Range { get; set; } public string ObjType { get; set; } @@ -47,7 +47,7 @@ namespace Robust.Shared.Network.Messages if (IsTile) TileType = buffer.ReadInt32(); else EntityTemplateName = buffer.ReadString(); - EntityCoordinates = buffer.ReadEntityCoordinates(); + NetCoordinates = buffer.ReadNetCoordinates(); DirRcv = (Direction)buffer.ReadByte(); break; case PlacementManagerMessage.StartPlacement: @@ -60,10 +60,10 @@ namespace Robust.Shared.Network.Messages case PlacementManagerMessage.PlacementFailed: throw new NotImplementedException(); case PlacementManagerMessage.RequestEntRemove: - EntityUid = new EntityUid(buffer.ReadInt32()); + EntityUid = new NetEntity(buffer.ReadInt32()); break; case PlacementManagerMessage.RequestRectRemove: - EntityCoordinates = buffer.ReadEntityCoordinates(); + NetCoordinates = buffer.ReadNetCoordinates(); RectSize = buffer.ReadVector2(); break; } @@ -82,7 +82,7 @@ namespace Robust.Shared.Network.Messages if(IsTile) buffer.Write(TileType); else buffer.Write(EntityTemplateName); - buffer.Write(EntityCoordinates); + buffer.Write(NetCoordinates); buffer.Write((byte)DirRcv); break; case PlacementManagerMessage.StartPlacement: @@ -98,7 +98,7 @@ namespace Robust.Shared.Network.Messages buffer.Write((int)EntityUid); break; case PlacementManagerMessage.RequestRectRemove: - buffer.Write(EntityCoordinates); + buffer.Write(NetCoordinates); buffer.Write(RectSize); break; } diff --git a/Robust.Shared/Network/Messages/MsgStateLeavePvs.cs b/Robust.Shared/Network/Messages/MsgStateLeavePvs.cs index bc0c712d3..d075006e4 100644 --- a/Robust.Shared/Network/Messages/MsgStateLeavePvs.cs +++ b/Robust.Shared/Network/Messages/MsgStateLeavePvs.cs @@ -19,7 +19,7 @@ public sealed class MsgStateLeavePvs : NetMessage { public override MsgGroups MsgGroup => MsgGroups.Entity; - public List Entities; + public List Entities; public GameTick Tick; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) @@ -30,7 +30,7 @@ public sealed class MsgStateLeavePvs : NetMessage for (int i = 0; i < length; i++) { - Entities.Add(buffer.ReadEntityUid()); + Entities.Add(buffer.ReadNetEntity()); } } diff --git a/Robust.Shared/Network/Messages/MsgStateRequestFull.cs b/Robust.Shared/Network/Messages/MsgStateRequestFull.cs index 3830c5dc2..9ec5fed62 100644 --- a/Robust.Shared/Network/Messages/MsgStateRequestFull.cs +++ b/Robust.Shared/Network/Messages/MsgStateRequestFull.cs @@ -13,12 +13,12 @@ public sealed class MsgStateRequestFull : NetMessage public GameTick Tick; - public EntityUid MissingEntity; + public NetEntity MissingEntity; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { Tick = buffer.ReadGameTick(); - MissingEntity = buffer.ReadEntityUid(); + MissingEntity = buffer.ReadNetEntity(); } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) diff --git a/Robust.Shared/Network/NetMessageExt.cs b/Robust.Shared/Network/NetMessageExt.cs index 572b5f38d..4fda623ad 100644 --- a/Robust.Shared/Network/NetMessageExt.cs +++ b/Robust.Shared/Network/NetMessageExt.cs @@ -11,17 +11,17 @@ namespace Robust.Shared.Network { public static class NetMessageExt { - public static EntityCoordinates ReadEntityCoordinates(this NetIncomingMessage message) + public static NetCoordinates ReadNetCoordinates(this NetIncomingMessage message) { - var entityUid = new EntityUid(message.ReadInt32()); + var entity = message.ReadNetEntity(); var vector = message.ReadVector2(); - return new EntityCoordinates(entityUid, vector); + return new NetCoordinates(entity, vector); } - public static void Write(this NetOutgoingMessage message, EntityCoordinates coordinates) + public static void Write(this NetOutgoingMessage message, NetCoordinates coordinates) { - message.Write(coordinates.EntityId); + message.Write(coordinates.NetEntity); message.Write(coordinates.Position); } @@ -39,14 +39,14 @@ namespace Robust.Shared.Network message.Write(vector2.Y); } - public static EntityUid ReadEntityUid(this NetIncomingMessage message) + public static NetEntity ReadNetEntity(this NetIncomingMessage message) { return new(message.ReadInt32()); } - public static void Write(this NetOutgoingMessage message, EntityUid entityUid) + public static void Write(this NetOutgoingMessage message, NetEntity entity) { - message.Write((int)entityUid); + message.Write((int)entity); } public static GameTick ReadGameTick(this NetIncomingMessage message) diff --git a/Robust.Shared/Physics/Dynamics/Joints/DistanceJoint.cs b/Robust.Shared/Physics/Dynamics/Joints/DistanceJoint.cs index 4cd87fd21..9e6e5fb7a 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/DistanceJoint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/DistanceJoint.cs @@ -47,9 +47,9 @@ internal sealed class DistanceJointState : JointState public float Stiffness { get; internal set; } public float Damping { get; internal set; } - public override Joint GetJoint() + public override Joint GetJoint(IEntityManager entManager, EntityUid owner) { - return new DistanceJoint(this); + return new DistanceJoint(this, entManager, owner); } } @@ -123,7 +123,8 @@ public sealed partial class DistanceJoint : Joint, IEquatable LocalAnchorB = anchorB; } - internal DistanceJoint(DistanceJointState state) : base(state) + internal DistanceJoint(DistanceJointState state, IEntityManager entManager, EntityUid owner) + : base(state, entManager, owner) { _damping = state.Damping; _length = state.Length; @@ -242,7 +243,7 @@ public sealed partial class DistanceJoint : Joint, IEquatable return F; } - public override JointState GetState() + public override JointState GetState(IEntityManager entManager) { var distanceState = new DistanceJointState { @@ -255,7 +256,7 @@ public sealed partial class DistanceJoint : Joint, IEquatable LocalAnchorB = LocalAnchorB }; - base.GetState(distanceState); + base.GetState(distanceState, entManager); return distanceState; } diff --git a/Robust.Shared/Physics/Dynamics/Joints/FrictionJoint.cs b/Robust.Shared/Physics/Dynamics/Joints/FrictionJoint.cs index 0063dc562..caa5ae7fc 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/FrictionJoint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/FrictionJoint.cs @@ -51,9 +51,9 @@ public sealed class FrictionJointState : JointState public float MaxForce { get; } public float MaxTorque { get; } - public override Joint GetJoint() + public override Joint GetJoint(IEntityManager entManager, EntityUid owner) { - return new FrictionJoint(this); + return new FrictionJoint(this, entManager, owner); } } @@ -107,17 +107,18 @@ public sealed partial class FrictionJoint : Joint, IEquatable LocalAnchorB = anchorB; } - internal FrictionJoint(FrictionJointState state) : base(state) + internal FrictionJoint(FrictionJointState state, IEntityManager entManager, EntityUid owner) + : base(state, entManager, owner) { MaxForce = state.MaxForce; MaxTorque = state.MaxTorque; } - public override JointState GetState() + public override JointState GetState(IEntityManager entManager) { var frictionState = new FrictionJointState(); - base.GetState(frictionState); + base.GetState(frictionState, entManager); return frictionState; } diff --git a/Robust.Shared/Physics/Dynamics/Joints/Joint.cs b/Robust.Shared/Physics/Dynamics/Joints/Joint.cs index 811b5fd7d..138f5f366 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/Joint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/Joint.cs @@ -66,13 +66,13 @@ public abstract class JointState public string ID { get; internal set; } = default!; public bool Enabled { get; internal set; } public bool CollideConnected { get; internal set; } - public EntityUid UidA { get; internal set; } - public EntityUid UidB { get; internal set; } + public NetEntity UidA { get; internal set; } + public NetEntity UidB { get; internal set; } public Vector2 LocalAnchorA { get; internal set; } public Vector2 LocalAnchorB { get; internal set; } public float Breakpoint { get; internal set; } - public abstract Joint GetJoint(); + public abstract Joint GetJoint(IEntityManager entManager, EntityUid owner); } [ImplicitDataDefinitionForInheritors] @@ -208,11 +208,11 @@ public abstract partial class Joint : IEquatable Debug.Assert(BodyAUid != BodyBUid); } - protected Joint(JointState state) + protected Joint(JointState state, IEntityManager entManager, EntityUid owner) { ID = state.ID; - BodyAUid = state.UidA; - BodyBUid = state.UidB; + BodyAUid = entManager.EnsureEntity(state.UidA, owner); + BodyBUid = entManager.EnsureEntity(state.UidB, owner); Enabled = state.Enabled; _collideConnected = state.CollideConnected; _localAnchorA = state.LocalAnchorA; @@ -224,17 +224,17 @@ public abstract partial class Joint : IEquatable /// Applies our properties to the provided state /// /// - protected void GetState(JointState state) + protected void GetState(JointState state, IEntityManager entManager) { state.ID = ID; state.CollideConnected = _collideConnected; state.Enabled = Enabled; - state.UidA = BodyAUid; - state.UidB = BodyBUid; + state.UidA = entManager.GetNetEntity(BodyAUid); + state.UidB = entManager.GetNetEntity(BodyBUid); state.Breakpoint = _breakpoint; } - public abstract JointState GetState(); + public abstract JointState GetState(IEntityManager entManager); internal virtual void ApplyState(JointState state) { diff --git a/Robust.Shared/Physics/Dynamics/Joints/MouseJoint.cs b/Robust.Shared/Physics/Dynamics/Joints/MouseJoint.cs index 558ff595a..29f00cdf6 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/MouseJoint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/MouseJoint.cs @@ -40,9 +40,9 @@ internal sealed class MouseJointState : JointState public float Stiffness { get; internal set; } public float Damping { get; internal set; } - public override Joint GetJoint() + public override Joint GetJoint(IEntityManager entManager, EntityUid owner) { - return new MouseJoint(this); + return new MouseJoint(this, entManager, owner); } } @@ -126,14 +126,15 @@ public sealed partial class MouseJoint : Joint, IEquatable LocalAnchorB = localAnchorB; } - internal MouseJoint(MouseJointState state) : base(state) + internal MouseJoint(MouseJointState state, IEntityManager entManager, EntityUid owner) + : base(state, entManager, owner) { Damping = state.Damping; Stiffness = state.Stiffness; MaxForce = state.MaxForce; } - public override JointState GetState() + public override JointState GetState(IEntityManager entManager) { var mouseState = new MouseJointState { @@ -144,7 +145,7 @@ public sealed partial class MouseJoint : Joint, IEquatable LocalAnchorB = LocalAnchorB }; - base.GetState(mouseState); + base.GetState(mouseState, entManager); return mouseState; } diff --git a/Robust.Shared/Physics/Dynamics/Joints/PrismaticJoint.cs b/Robust.Shared/Physics/Dynamics/Joints/PrismaticJoint.cs index 6a06ce788..0ac75770a 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/PrismaticJoint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/PrismaticJoint.cs @@ -105,9 +105,9 @@ internal sealed class PrismaticJointState : JointState /// The desired motor speed in radians per second. public float MotorSpeed; - public override Joint GetJoint() + public override Joint GetJoint(IEntityManager entManager, EntityUid owner) { - return new PrismaticJoint(this); + return new PrismaticJoint(this, entManager, owner); } } @@ -219,7 +219,8 @@ public sealed partial class PrismaticJoint : Joint, IEquatable ReferenceAngle = referenceAngle; } - internal PrismaticJoint(PrismaticJointState state) : base(state) + internal PrismaticJoint(PrismaticJointState state, IEntityManager entManager, EntityUid owner) + : base(state, entManager, owner) { LocalAxisA = state.LocalAxisA; ReferenceAngle = state.ReferenceAngle; @@ -233,7 +234,7 @@ public sealed partial class PrismaticJoint : Joint, IEquatable public override JointType JointType => JointType.Prismatic; - public override JointState GetState() + public override JointState GetState(IEntityManager entManager) { var prismaticState = new PrismaticJointState { @@ -241,7 +242,7 @@ public sealed partial class PrismaticJoint : Joint, IEquatable LocalAnchorB = LocalAnchorB }; - base.GetState(prismaticState); + base.GetState(prismaticState, entManager); return prismaticState; } diff --git a/Robust.Shared/Physics/Dynamics/Joints/RevoluteJoint.cs b/Robust.Shared/Physics/Dynamics/Joints/RevoluteJoint.cs index f9765a732..1e27669eb 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/RevoluteJoint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/RevoluteJoint.cs @@ -43,9 +43,9 @@ internal sealed class RevoluteJointState : JointState public float MotorSpeed { get; internal set; } public float MaxMotorTorque { get; internal set; } - public override Joint GetJoint() + public override Joint GetJoint(IEntityManager entManager, EntityUid owner) { - return new RevoluteJoint(this); + return new RevoluteJoint(this, entManager, owner); } } @@ -122,7 +122,8 @@ public sealed partial class RevoluteJoint : Joint, IEquatable public RevoluteJoint(EntityUid bodyAUid, EntityUid bodyBUid) : base(bodyAUid, bodyBUid) {} - internal RevoluteJoint(RevoluteJointState state) : base(state) + internal RevoluteJoint(RevoluteJointState state, IEntityManager entManager, EntityUid owner) + : base(state, entManager, owner) { EnableLimit = state.EnableLimit; EnableMotor = state.EnableMotor; @@ -135,11 +136,11 @@ public sealed partial class RevoluteJoint : Joint, IEquatable public override JointType JointType => JointType.Revolute; - public override JointState GetState() + public override JointState GetState(IEntityManager entManager) { var revoluteState = new RevoluteJointState(); - base.GetState(revoluteState); + base.GetState(revoluteState, entManager); return revoluteState; } diff --git a/Robust.Shared/Physics/Dynamics/Joints/WeldJoint.cs b/Robust.Shared/Physics/Dynamics/Joints/WeldJoint.cs index 18d84f2f5..cdda94af9 100644 --- a/Robust.Shared/Physics/Dynamics/Joints/WeldJoint.cs +++ b/Robust.Shared/Physics/Dynamics/Joints/WeldJoint.cs @@ -17,9 +17,9 @@ internal sealed class WeldJointState : JointState public float Damping { get; internal set; } public float Bias { get; internal set; } - public override Joint GetJoint() + public override Joint GetJoint(IEntityManager entManager, EntityUid owner) { - return new WeldJoint(this); + return new WeldJoint(this, entManager, owner); } } @@ -73,7 +73,8 @@ public sealed partial class WeldJoint : Joint, IEquatable internal WeldJoint(EntityUid bodyAUid, EntityUid bodyBUid) : base(bodyAUid, bodyBUid) {} - internal WeldJoint(WeldJointState state) : base(state) + internal WeldJoint(WeldJointState state, IEntityManager entManager, EntityUid owner) + : base(state, entManager, owner) { Stiffness = state.Stiffness; Damping = state.Damping; @@ -81,11 +82,11 @@ public sealed partial class WeldJoint : Joint, IEquatable } public override JointType JointType => JointType.Weld; - public override JointState GetState() + public override JointState GetState(IEntityManager entManager) { var weldJointState = new WeldJointState(); - base.GetState(weldJointState); + base.GetState(weldJointState, entManager); return weldJointState; } diff --git a/Robust.Shared/Physics/JointComponent.cs b/Robust.Shared/Physics/JointComponent.cs index 42d9a3f06..7e049b44e 100644 --- a/Robust.Shared/Physics/JointComponent.cs +++ b/Robust.Shared/Physics/JointComponent.cs @@ -31,10 +31,10 @@ public sealed partial class JointComponent : Component [Serializable, NetSerializable] public sealed class JointComponentState : ComponentState { - public EntityUid? Relay; + public NetEntity? Relay; public Dictionary Joints; - public JointComponentState(EntityUid? relay, Dictionary joints) + public JointComponentState(NetEntity? relay, Dictionary joints) { Relay = relay; Joints = joints; diff --git a/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs b/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs index 447873c3d..4e36142b1 100644 --- a/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs +++ b/Robust.Shared/Physics/Systems/SharedJointSystem.Relay.cs @@ -19,9 +19,9 @@ public abstract partial class SharedJointSystem [Serializable, NetSerializable] private sealed class JointRelayComponentState : ComponentState { - public HashSet Entities; + public HashSet Entities; - public JointRelayComponentState(HashSet entities) + public JointRelayComponentState(HashSet entities) { Entities = entities; } @@ -36,7 +36,7 @@ public abstract partial class SharedJointSystem private void OnRelayGetState(EntityUid uid, JointRelayTargetComponent component, ref ComponentGetState args) { - args.State = new JointRelayComponentState(component.Relayed); + args.State = new JointRelayComponentState(GetNetEntitySet(component.Relayed)); } private void OnRelayHandleState(EntityUid uid, JointRelayTargetComponent component, ref ComponentHandleState args) @@ -45,7 +45,7 @@ public abstract partial class SharedJointSystem return; component.Relayed.Clear(); - component.Relayed.UnionWith(state.Entities); + component.Relayed.UnionWith(EnsureEntitySet(state.Entities, uid)); } private void OnRelayShutdown(EntityUid uid, JointRelayTargetComponent component, ComponentShutdown args) diff --git a/Robust.Shared/Replays/ReplayData.cs b/Robust.Shared/Replays/ReplayData.cs index 914d9cc21..027915ddb 100644 --- a/Robust.Shared/Replays/ReplayData.cs +++ b/Robust.Shared/Replays/ReplayData.cs @@ -151,14 +151,14 @@ public sealed class CheckpointState : IComparable public readonly (TimeSpan, GameTick) TimeBase; public readonly int Index; public readonly Dictionary Cvars; - public readonly List Detached; + public readonly List Detached; public CheckpointState( GameState state, (TimeSpan, GameTick) time, Dictionary cvars, int index, - HashSet detached) + HashSet detached) { FullState = state; TimeBase = time; @@ -175,7 +175,7 @@ public sealed class CheckpointState : IComparable int i = 0, j = 0; foreach (var entState in state.EntityStates.Span) { - if (detached.Contains(entState.Uid)) + if (detached.Contains(entState.NetEntity)) DetachedStates[i++] = entState; else attachedStates[j++] = entState; @@ -232,10 +232,10 @@ public sealed class ReplayMessage [Serializable, NetSerializable] public sealed class LeavePvs { - public readonly List Entities; + public readonly List Entities; public readonly GameTick Tick; - public LeavePvs(List entities, GameTick tick) + public LeavePvs(List entities, GameTick tick) { Entities = entities; Tick = tick; diff --git a/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs b/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs index c9dab95de..c01027423 100644 --- a/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs @@ -134,7 +134,7 @@ internal record EmplaceContext(IInvocationContext Inner, T Value, IEntityMana { case "ent": { - return session.AttachedEntity!; + return EntityManager.GetNetEntity(session.AttachedEntity!); } case "name": { diff --git a/Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs b/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs similarity index 66% rename from Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs rename to Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs index 535ab33b4..cad7a2f63 100644 --- a/Robust.Shared/Toolshed/TypeParsers/EntityUidTypeParser.cs +++ b/Robust.Shared/Toolshed/TypeParsers/EntityTypeParser.cs @@ -11,14 +11,12 @@ using Robust.Shared.Utility; namespace Robust.Shared.Toolshed.TypeParsers; -internal sealed class EntityUidTypeParser : TypeParser +internal sealed class EntityTypeParser : TypeParser { - [Dependency] private readonly IEntityManager _entity = default!; - - public override bool TryParse(ParserContext parserContext, [NotNullWhen(true)] out object? result, out IConError? error) + public override bool TryParse(ParserContext parser, [NotNullWhen(true)] out object? result, out IConError? error) { - var start = parserContext.Index; - var word = parserContext.GetWord(ParserContext.IsToken); + var start = parser.Index; + var word = parser.GetWord(ParserContext.IsToken); error = null; if (!EntityUid.TryParse(word, out var ent)) @@ -26,11 +24,11 @@ internal sealed class EntityUidTypeParser : TypeParser result = null; if (word is not null) - error = new InvalidEntityUid(word); + error = new InvalidEntity(word); else error = new OutOfInputError(); - error.Contextualize(parserContext.Input, (start, parserContext.Index)); + error.Contextualize(parser.Input, (start, parser.Index)); return false; } @@ -41,15 +39,15 @@ internal sealed class EntityUidTypeParser : TypeParser public override async ValueTask<(CompletionResult? result, IConError? error)> TryAutocomplete(ParserContext parserContext, string? argName) { - return (CompletionResult.FromHint(""), null); + return (CompletionResult.FromHint(""), null); } } -public record InvalidEntityUid(string Value) : IConError +public record InvalidEntity(string Value) : IConError { public FormattedMessage DescribeInner() { - return FormattedMessage.FromMarkup($"Couldn't parse {Value} as an entity ID. Entity IDs are numeric, optionally starting with a c to indicate client-sided-ness."); + return FormattedMessage.FromMarkup($"Couldn't parse {Value} as an Entity."); } public string? Expression { get; set; } diff --git a/Robust.Shared/ViewVariables/ViewVariablesManager.Domains.cs b/Robust.Shared/ViewVariables/ViewVariablesManager.Domains.cs index 4948fb622..d244d9e3e 100644 --- a/Robust.Shared/ViewVariables/ViewVariablesManager.Domains.cs +++ b/Robust.Shared/ViewVariables/ViewVariablesManager.Domains.cs @@ -103,7 +103,8 @@ internal abstract partial class ViewVariablesManager return null; if (segments.Length == 1 - && EntityUid.TryParse(segments[0], out var u) + && NetEntity.TryParse(segments[0], out var uNet) + && _entMan.TryGetEntity(uNet, out var u) && _entMan.EntityExists(u)) { return null; diff --git a/Robust.Shared/ViewVariables/ViewVariablesManager.cs b/Robust.Shared/ViewVariables/ViewVariablesManager.cs index 665f69ff3..df1aa1574 100644 --- a/Robust.Shared/ViewVariables/ViewVariablesManager.cs +++ b/Robust.Shared/ViewVariables/ViewVariablesManager.cs @@ -104,7 +104,7 @@ internal abstract partial class ViewVariablesManager : IViewVariablesManager, IP traits.Add(ViewVariablesTraits.Enumerable); } - if (typeof(EntityUid).IsAssignableFrom(type)) + if (typeof(NetEntity).IsAssignableFrom(type)) { traits.Add(ViewVariablesTraits.Entity); } diff --git a/Robust.Shared/ViewVariables/ViewVariablesObjectSelector.cs b/Robust.Shared/ViewVariables/ViewVariablesObjectSelector.cs index 3e123edf0..e99af9e32 100644 --- a/Robust.Shared/ViewVariables/ViewVariablesObjectSelector.cs +++ b/Robust.Shared/ViewVariables/ViewVariablesObjectSelector.cs @@ -20,12 +20,12 @@ namespace Robust.Shared.ViewVariables [Virtual] public class ViewVariablesEntitySelector : ViewVariablesObjectSelector { - public ViewVariablesEntitySelector(EntityUid entity) + public ViewVariablesEntitySelector(NetEntity entity) { Entity = entity; } - public EntityUid Entity { get; } + public NetEntity Entity { get; } } /// @@ -35,7 +35,7 @@ namespace Robust.Shared.ViewVariables [Serializable, NetSerializable] public sealed class ViewVariablesComponentSelector : ViewVariablesEntitySelector { - public ViewVariablesComponentSelector(EntityUid uid, string componentType) : base(uid) + public ViewVariablesComponentSelector(NetEntity uid, string componentType) : base(uid) { ComponentType = componentType; } diff --git a/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs b/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs index 1ccd3a127..bbfcf91a3 100644 --- a/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs +++ b/Robust.UnitTesting/Client/GameObjects/Components/TransformComponentTests.cs @@ -54,18 +54,18 @@ namespace Robust.UnitTesting.Client.GameObjects.Components var childTrans = entMan.GetComponent(child); ComponentHandleState handleState; - var compState = new TransformComponentState(new Vector2(5, 5), new Angle(0), gridB.Owner, false, false); + var compState = new TransformComponentState(new Vector2(5, 5), new Angle(0), entMan.GetNetEntity(gridB.Owner), false, false); handleState = new ComponentHandleState(compState, null); xformSystem.OnHandleState(parent, parentTrans, ref handleState); - compState = new TransformComponentState(new Vector2(6, 6), new Angle(0), gridB.Owner, false, false); + compState = new TransformComponentState(new Vector2(6, 6), new Angle(0), entMan.GetNetEntity(gridB.Owner), false, false); handleState = new ComponentHandleState(compState, null); xformSystem.OnHandleState(child, childTrans, ref handleState); // World pos should be 6, 6 now. // Act var oldWpos = childTrans.WorldPosition; - compState = new TransformComponentState(new Vector2(1, 1), new Angle(0), parent, false, false); + compState = new TransformComponentState(new Vector2(1, 1), new Angle(0), entMan.GetNetEntity(parent), false, false); handleState = new ComponentHandleState(compState, null); xformSystem.OnHandleState(child, childTrans, ref handleState); var newWpos = childTrans.WorldPosition; @@ -83,7 +83,8 @@ namespace Robust.UnitTesting.Client.GameObjects.Components var (sim, gridIdA, gridIdB) = SimulationFactory(); var entMan = sim.Resolve(); var mapMan = sim.Resolve(); - var xformSystem = sim.Resolve().GetEntitySystem(); + var xformSystem = entMan.System(); + var metaSystem = entMan.System(); var gridA = mapMan.GetGrid(gridIdA); var gridB = mapMan.GetGrid(gridIdB); @@ -94,23 +95,23 @@ namespace Robust.UnitTesting.Client.GameObjects.Components var node2 = entMan.SpawnEntity(null, initalPos); var node3 = entMan.SpawnEntity(null, initalPos); - entMan.GetComponent(node1).EntityName = "node1_dummy"; - entMan.GetComponent(node2).EntityName = "node2_dummy"; - entMan.GetComponent(node3).EntityName = "node3_dummy"; + metaSystem.SetEntityName(node1, "node1_dummy"); + metaSystem.SetEntityName(node2, "node2_dummy"); + metaSystem.SetEntityName(node3, "node3_dummy"); var node1Trans = entMan.GetComponent(node1); var node2Trans = entMan.GetComponent(node2); var node3Trans = entMan.GetComponent(node3); - var compState = new TransformComponentState(new Vector2(6, 6), Angle.FromDegrees(135), gridB.Owner, false, false); + var compState = new TransformComponentState(new Vector2(6, 6), Angle.FromDegrees(135), entMan.GetNetEntity(gridB.Owner), false, false); var handleState = new ComponentHandleState(compState, null); xformSystem.OnHandleState(node1, node1Trans, ref handleState); - compState = new TransformComponentState(new Vector2(1, 1), Angle.FromDegrees(45), node1, false, false); + compState = new TransformComponentState(new Vector2(1, 1), Angle.FromDegrees(45), entMan.GetNetEntity(node1), false, false); handleState = new ComponentHandleState(compState, null); xformSystem.OnHandleState(node2, node2Trans, ref handleState); - compState = new TransformComponentState(new Vector2(0, 0), Angle.FromDegrees(45), node2, false, false); + compState = new TransformComponentState(new Vector2(0, 0), Angle.FromDegrees(45), entMan.GetNetEntity(node2), false, false); handleState = new ComponentHandleState(compState, null); xformSystem.OnHandleState(node3, node3Trans, ref handleState); diff --git a/Robust.UnitTesting/RobustIntegrationTest.cs b/Robust.UnitTesting/RobustIntegrationTest.cs index 943e6dc0b..b059c1829 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.cs @@ -269,7 +269,7 @@ namespace Robust.UnitTesting public ISharedPlayerManager PlayerMan { get; private set; } = default!; public IGameTiming Timing { get; private set; } = default!; public IMapManager MapMan { get; private set; } = default!; - + protected virtual void ResolveIoC(IDependencyCollection deps) { EntMan = deps.Resolve(); @@ -903,6 +903,17 @@ namespace Robust.UnitTesting GameLoop.RunInit(); ResolveIoC(deps); + // Offset client generated Uids. + // Not that we have client-server uid separation, there might be bugs where tests might accidentally + // use server side uids on the client and vice versa. This can sometimes accidentally work if the + // entities get created in the same order. For that reason we arbitrarily increment the queued Uid by + // some arbitrary quantity. + var e = (EntityManager) EntMan; + for (var i = 0; i < 10; i++) + { + e.GenerateEntityUid(); + } + return client; } diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs index e36593644..1ba156b64 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Container_Test.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using JetBrains.Annotations; using NUnit.Framework; using Robust.Server.Containers; using Robust.Shared.Containers; @@ -8,6 +10,8 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; using Robust.Shared.Utility; // ReSharper disable AccessToStaticMemberViaDerivedType @@ -64,10 +68,6 @@ namespace Robust.UnitTesting.Server.GameObjects.Components Assert.That(() => manager.GetContainer("dummy3"), Throws.TypeOf()); entManager.DeleteEntity(entity); - - Assert.That(manager.Deleted, Is.True); - Assert.That(container.Deleted, Is.True); - Assert.That(container2.Deleted, Is.True); } [Test] @@ -172,7 +172,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(container.Insert(entity), Is.False); - Assert.That(container.CanInsert(entity), Is.False); + Assert.That(containerSys.CanInsert(entity, container), Is.False); } [Test] @@ -186,7 +186,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(container.Insert(mapEnt), Is.False); - Assert.That(container.CanInsert(mapEnt), Is.False); + Assert.That(containerSys.CanInsert(mapEnt, container), Is.False); } [Test] @@ -200,7 +200,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components var container = containerSys.MakeContainer(entity, "dummy"); Assert.That(container.Insert(grid), Is.False); - Assert.That(container.CanInsert(grid), Is.False); + Assert.That(containerSys.CanInsert(grid, container), Is.False); } [Test] @@ -284,28 +284,26 @@ namespace Robust.UnitTesting.Server.GameObjects.Components Assert.That(state.Containers, Has.Count.EqualTo(1)); var cont = state.Containers.Values.First(); - Assert.That(cont.Id, Is.EqualTo("dummy")); + Assert.That(state.Containers.Keys.First(), Is.EqualTo("dummy")); Assert.That(cont.OccludesLight, Is.True); Assert.That(cont.ShowContents, Is.True); - Assert.That(cont.ContainedEntities.Length, Is.EqualTo(1)); - Assert.That(cont.ContainedEntities[0], Is.EqualTo(childEnt)); + Assert.That(cont.ContainedEntities.Count, Is.EqualTo(1)); + Assert.That(cont.ContainedEntities[0], Is.EqualTo(entManager.GetNetEntity(childEnt))); } + [SerializedType(nameof(ContainerOnlyContainer))] private sealed partial class ContainerOnlyContainer : BaseContainer { /// /// The generic container class uses a list of entities /// private readonly List _containerList = new(); - private readonly List _expectedEntities = new(); - public override string ContainerType => nameof(ContainerOnlyContainer); + public override int Count => _containerList.Count; /// public override IReadOnlyList ContainedEntities => _containerList; - public override List ExpectedEntities => _expectedEntities; - /// protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) { @@ -324,6 +322,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components if (!_containerList.Contains(contained)) return false; + if (IoCManager.Resolve().ApplyingState) + return true; + var flags = IoCManager.Resolve().GetComponent(contained).Flags; DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0); return true; @@ -341,10 +342,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components } } - public override bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) + protected internal override bool CanInsert(EntityUid toinsert, bool assumeEmpty, IEntityManager entMan) { - IoCManager.Resolve(ref entMan); - return entMan.TryGetComponent(toinsert, out ContainerManagerComponent? _); + return entMan.HasComponent(toinsert); } } } diff --git a/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs b/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs index 8dcc41f12..02587dd43 100644 --- a/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs +++ b/Robust.UnitTesting/Server/GameStates/PvsSystemTests.cs @@ -29,12 +29,14 @@ public sealed class PvsSystemTests : RobustIntegrationTest var mapMan = server.ResolveDependency(); var sEntMan = server.ResolveDependency(); - var netMan = client.ResolveDependency(); var confMan = server.ResolveDependency(); - var cPlayerMan = client.ResolveDependency(); var sPlayerMan = server.ResolveDependency(); var xforms = sEntMan.System(); + var cEntMan = client.ResolveDependency(); + var netMan = client.ResolveDependency(); + var cPlayerMan = client.ResolveDependency(); + Assert.DoesNotThrow(() => client.SetConnectTarget(server)); client.Post(() => netMan.ClientConnect(null!, 0, null!)); server.Post(() => confMan.SetCVar(CVars.NetPVS, true)); @@ -87,8 +89,8 @@ public sealed class PvsSystemTests : RobustIntegrationTest // Check player got properly attached await client.WaitPost(() => { - var ent = cPlayerMan.LocalPlayer?.ControlledEntity; - Assert.That(ent, Is.EqualTo(player)); + var ent = cEntMan.GetNetEntity(cPlayerMan.LocalPlayer?.ControlledEntity); + Assert.That(ent, Is.EqualTo(sEntMan.GetNetEntity(player))); }); // Move the player off-grid and back onto the grid in the same tick diff --git a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs index 1b73fe5b7..778ceae3a 100644 --- a/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/ContainerTests.cs @@ -3,10 +3,8 @@ using System.Numerics; using System.Threading.Tasks; using NUnit.Framework; using Robust.Client.GameObjects; -using Robust.Client.GameStates; using Robust.Client.Timing; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -14,7 +12,6 @@ using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Timing; -using MapSystem = Robust.Server.GameObjects.MapSystem; // ReSharper disable AccessToStaticMemberViaDerivedType @@ -28,15 +25,26 @@ namespace Robust.UnitTesting.Shared.GameObjects /// /// [Test] - public async Task TestContainerNonexistantItems() - { + public async Task TestContainerNonexistantItems() + { var server = StartServer(); var client = StartClient(); await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); + var cEntManager = client.ResolveDependency(); + var clientNetManager = client.ResolveDependency(); + + var sMapManager = server.ResolveDependency(); + var sEntManager = server.ResolveDependency(); + var sPlayerManager = server.ResolveDependency(); + Assert.DoesNotThrow(() => client.SetConnectTarget(server)); - client.Post(() => IoCManager.Resolve().ClientConnect(null!, 0, null!)); + client.Post(() => + { + + clientNetManager.ClientConnect(null!, 0, null!); + }); for (int i = 0; i < 10; i++) { @@ -49,25 +57,21 @@ namespace Robust.UnitTesting.Shared.GameObjects var mapPos = MapCoordinates.Nullspace; EntityUid entityUid = default!; - EntityUid itemUid = default!; await server.WaitAssertion(() => { - var mapMan = IoCManager.Resolve(); - var entMan = IoCManager.Resolve(); - var playerMan = IoCManager.Resolve(); - var containerSys = IoCManager.Resolve().GetEntitySystem(); + var containerSys = sEntManager.System(); - mapId = mapMan.CreateMap(); + mapId = sMapManager.CreateMap(); mapPos = new MapCoordinates(new Vector2(0, 0), mapId); - entityUid = entMan.SpawnEntity(null, mapPos); - entMan.GetComponent(entityUid).EntityName = "Container"; + entityUid = sEntManager.SpawnEntity(null, mapPos); + sEntManager.GetComponent(entityUid).EntityName = "Container"; containerSys.EnsureContainer(entityUid, "dummy"); // Setup PVS - entMan.AddComponent(entityUid); - var player = playerMan.ServerSessions.First(); + sEntManager.AddComponent(entityUid); + var player = sPlayerManager.ServerSessions.First(); player.AttachToEntity(entityUid); player.JoinGame(); }); @@ -78,28 +82,29 @@ namespace Robust.UnitTesting.Shared.GameObjects await client.WaitRunTicks(1); } + EntityUid itemUid = default!; await server.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - var containerSys = IoCManager.Resolve().GetEntitySystem(); + var containerSys = sEntManager.System(); - itemUid = entMan.SpawnEntity(null, mapPos); - entMan.GetComponent(itemUid).EntityName = "Item"; + itemUid = sEntManager.SpawnEntity(null, mapPos); + sEntManager.GetComponent(itemUid).EntityName = "Item"; var container = containerSys.EnsureContainer(entityUid, "dummy"); Assert.That(container.Insert(itemUid)); // Move item out of PVS so that it doesn't get sent to the client - entMan.GetComponent(itemUid).LocalPosition = new Vector2(100000, 0); + sEntManager.GetComponent(itemUid).LocalPosition = new Vector2(100000, 0); }); // Needs minimum 4 to sync to client because buffer size is 3 - await server.WaitRunTicks(10); - await client.WaitRunTicks(40); + await server.WaitRunTicks(4); + await client.WaitRunTicks(10); + EntityUid cEntityUid = default!; await client.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(entityUid, out var containerManagerComp)) + cEntityUid = client.EntMan.GetEntity(server.EntMan.GetNetEntity(entityUid)); + if (!cEntManager.TryGetComponent(cEntityUid, out var containerManagerComp)) { Assert.Fail(); return; @@ -109,17 +114,15 @@ namespace Robust.UnitTesting.Shared.GameObjects Assert.That(container.ContainedEntities.Count, Is.EqualTo(0)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(1)); - var containerSystem = IoCManager.Resolve().GetEntitySystem(); - Assert.That(containerSystem.ExpectedEntities.ContainsKey(itemUid)); + var containerSystem = cEntManager.System(); + Assert.That(containerSystem.ExpectedEntities.ContainsKey(sEntManager.GetNetEntity(itemUid))); Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(1)); }); await server.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - // Move item into PVS so it gets sent to the client - entMan.GetComponent(itemUid).LocalPosition = new Vector2(0, 0); + sEntManager.GetComponent(itemUid).LocalPosition = new Vector2(0, 0); }); await server.WaitRunTicks(1); @@ -127,8 +130,7 @@ namespace Robust.UnitTesting.Shared.GameObjects await client.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(entityUid, out var containerManagerComp)) + if (!cEntManager.TryGetComponent(cEntityUid, out var containerManagerComp)) { Assert.Fail(); return; @@ -138,9 +140,9 @@ namespace Robust.UnitTesting.Shared.GameObjects Assert.That(container.ContainedEntities.Count, Is.EqualTo(1)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0)); - var containerSystem = IoCManager.Resolve().GetEntitySystem(); - Assert.That(!containerSystem.ExpectedEntities.ContainsKey(itemUid)); - Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(0)); + var containerSystem = cEntManager.System(); + Assert.That(!containerSystem.ExpectedEntities.ContainsKey(sEntManager.GetNetEntity(itemUid))); + Assert.That(containerSystem.ExpectedEntities, Is.Empty); }); } @@ -157,8 +159,20 @@ namespace Robust.UnitTesting.Shared.GameObjects await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); + var cEntManager = client.ResolveDependency(); + var clientTime = client.ResolveDependency(); + var clientNetManager = client.ResolveDependency(); + + var sMapManager = server.ResolveDependency(); + var sEntManager = server.ResolveDependency(); + var sPlayerManager = server.ResolveDependency(); + var serverTime = server.ResolveDependency(); + Assert.DoesNotThrow(() => client.SetConnectTarget(server)); - client.Post(() => IoCManager.Resolve().ClientConnect(null!, 0, null!)); + await client.WaitPost(() => + { + clientNetManager.ClientConnect(null!, 0, null!); + }); for (int i = 0; i < 10; i++) { @@ -167,30 +181,28 @@ namespace Robust.UnitTesting.Shared.GameObjects } // Setup - var mapId = MapId.Nullspace; + MapId mapId; var mapPos = MapCoordinates.Nullspace; - EntityUid entityUid = default!; - EntityUid itemUid = default!; + EntityUid sEntityUid = default!; + EntityUid sItemUid = default!; + NetEntity netEnt = default; await server.WaitAssertion(() => { - var mapMan = IoCManager.Resolve(); - var entMan = IoCManager.Resolve(); - var playerMan = IoCManager.Resolve(); - var containerSys = IoCManager.Resolve().GetEntitySystem(); + var containerSys = sEntManager.System(); - mapId = mapMan.CreateMap(); + mapId = sMapManager.CreateMap(); mapPos = new MapCoordinates(new Vector2(0, 0), mapId); - entityUid = entMan.SpawnEntity(null, mapPos); - entMan.GetComponent(entityUid).EntityName = "Container"; - containerSys.EnsureContainer(entityUid, "dummy"); + sEntityUid = sEntManager.SpawnEntity(null, mapPos); + sEntManager.GetComponent(sEntityUid).EntityName = "Container"; + containerSys.EnsureContainer(sEntityUid, "dummy"); // Setup PVS - entMan.AddComponent(entityUid); - var player = playerMan.ServerSessions.First(); - player.AttachToEntity(entityUid); + sEntManager.AddComponent(sEntityUid); + var player = sPlayerManager.ServerSessions.First(); + player.AttachToEntity(sEntityUid); player.JoinGame(); }); @@ -202,30 +214,30 @@ namespace Robust.UnitTesting.Shared.GameObjects await server.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - var containerSys = IoCManager.Resolve().GetEntitySystem(); + var containerSys = sEntManager.System(); - itemUid = entMan.SpawnEntity(null, mapPos); - entMan.GetComponent(itemUid).EntityName = "Item"; - var container = containerSys.EnsureContainer(entityUid, "dummy"); - container.Insert(itemUid); + sItemUid = sEntManager.SpawnEntity(null, mapPos); + netEnt = sEntManager.GetNetEntity(sItemUid); + sEntManager.GetComponent(sItemUid).EntityName = "Item"; + var container = containerSys.GetContainer(sEntityUid, "dummy"); + container.Insert(sItemUid); // Move item out of PVS so that it doesn't get sent to the client - entMan.GetComponent(itemUid).LocalPosition = new Vector2(100000, 0); + sEntManager.GetComponent(sItemUid).LocalPosition = new Vector2(100000, 0); }); await server.WaitRunTicks(1); - var serverTime = server.ResolveDependency(); - var clientTime = client.ResolveDependency(); + while (clientTime.LastRealTick < serverTime.CurTick - 1) { await client.WaitRunTicks(1); } + var cUid = cEntManager.GetEntity(sEntManager.GetNetEntity(sEntityUid)); + await client.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(entityUid, out var containerManagerComp)) + if (!cEntManager.TryGetComponent(cUid, out var containerManagerComp)) { Assert.Fail(); return; @@ -235,23 +247,25 @@ namespace Robust.UnitTesting.Shared.GameObjects Assert.That(container.ContainedEntities.Count, Is.EqualTo(0)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(1)); - var containerSystem = IoCManager.Resolve().GetEntitySystem(); - Assert.That(containerSystem.ExpectedEntities.ContainsKey(itemUid)); + var containerSystem = cEntManager.System(); + Assert.That(containerSystem.ExpectedEntities.ContainsKey(netEnt)); Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(1)); }); await server.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - var containerSystem = IoCManager.Resolve().GetEntitySystem(); + var containerSystem = sEntManager.System(); // If possible it'd be best to only have the DeleteEntity, but right now // the entity deleted event is not played on the client if the entity does not exist on the client. - if (entMan.EntityExists(itemUid) + if (sEntManager.EntityExists(sItemUid) // && itemUid.TryGetContainer(out var container)) - && containerSystem.TryGetContainingContainer(itemUid, out var container)) - container.ForceRemove(itemUid); - entMan.DeleteEntity(itemUid); + && containerSystem.TryGetContainingContainer(sItemUid, out var container)) + { + container.ForceRemove(sItemUid); + } + + sEntManager.DeleteEntity(sItemUid); }); await server.WaitRunTicks(1); @@ -259,8 +273,7 @@ namespace Robust.UnitTesting.Shared.GameObjects await client.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(entityUid, out var containerManagerComp)) + if (!cEntManager.TryGetComponent(cUid, out var containerManagerComp)) { Assert.Fail(); return; @@ -270,13 +283,12 @@ namespace Robust.UnitTesting.Shared.GameObjects Assert.That(container.ContainedEntities.Count, Is.EqualTo(0)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0)); - var containerSystem = IoCManager.Resolve().GetEntitySystem(); - Assert.That(!containerSystem.ExpectedEntities.ContainsKey(itemUid)); + var containerSystem = cEntManager.System(); + Assert.That(!containerSystem.ExpectedEntities.ContainsKey(netEnt)); Assert.That(containerSystem.ExpectedEntities.Count, Is.EqualTo(0)); }); } - /// /// Sets up a new container, initializes map, saves the map, then loads it again on another map. The contained entity should still /// be inside the container. @@ -288,22 +300,22 @@ namespace Robust.UnitTesting.Shared.GameObjects await Task.WhenAll(server.WaitIdleAsync()); + var sEntManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + await server.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - var containerSys = entMan.EntitySysManager.GetEntitySystem(); + var containerSys = sEntManager.EntitySysManager.GetEntitySystem(); // build the map - var mapManager = IoCManager.Resolve(); - var mapIdOne = mapManager.CreateMap(); Assert.That(mapManager.IsMapInitialized(mapIdOne), Is.True); - var containerEnt = entMan.SpawnEntity(null, new MapCoordinates(1, 1, mapIdOne)); - entMan.GetComponent(containerEnt).EntityName = "ContainerEnt"; + var containerEnt = sEntManager.SpawnEntity(null, new MapCoordinates(1, 1, mapIdOne)); + sEntManager.GetComponent(containerEnt).EntityName = "ContainerEnt"; - var containeeEnt = entMan.SpawnEntity(null, new MapCoordinates(2, 2, mapIdOne)); - entMan.GetComponent(containeeEnt).EntityName = "ContaineeEnt"; + var containeeEnt = sEntManager.SpawnEntity(null, new MapCoordinates(2, 2, mapIdOne)); + sEntManager.GetComponent(containeeEnt).EntityName = "ContaineeEnt"; var container = containerSys.MakeContainer(containerEnt, "testContainer"); container.OccludesLight = true; @@ -311,7 +323,7 @@ namespace Robust.UnitTesting.Shared.GameObjects container.Insert(containeeEnt); // save the map - var mapLoader = entMan.EntitySysManager.GetEntitySystem(); + var mapLoader = sEntManager.EntitySysManager.GetEntitySystem(); mapLoader.SaveMap(mapIdOne, "container_test.yml"); mapManager.DeleteMap(mapIdOne); @@ -322,8 +334,7 @@ namespace Robust.UnitTesting.Shared.GameObjects await server.WaitAssertion(() => { - var mapManager = IoCManager.Resolve(); - var mapLoader = IoCManager.Resolve().GetEntitySystem(); + var mapLoader = sEntManager.System(); var mapIdTwo = mapManager.CreateMap(); // load the map @@ -335,22 +346,20 @@ namespace Robust.UnitTesting.Shared.GameObjects await server.WaitAssertion(() => { - var entMan = IoCManager.Resolve(); - // verify container - var containerQuery = entMan.EntityQuery(); + var containerQuery = sEntManager.EntityQuery(); var containerComp = containerQuery.First(); var containerEnt = containerComp.Owner; - Assert.That(entMan.GetComponent(containerEnt).EntityName, Is.EqualTo("ContainerEnt")); + Assert.That(sEntManager.GetComponent(containerEnt).EntityName, Is.EqualTo("ContainerEnt")); Assert.That(containerComp.Containers.ContainsKey("testContainer")); - var iContainer = containerComp.GetContainer("testContainer"); - Assert.That(iContainer.ContainedEntities.Count, Is.EqualTo(1)); + var baseContainer = containerComp.GetContainer("testContainer"); + Assert.That(baseContainer.ContainedEntities, Has.Count.EqualTo(1)); - var containeeEnt = iContainer.ContainedEntities[0]; - Assert.That(entMan.GetComponent(containeeEnt).EntityName, Is.EqualTo("ContaineeEnt")); + var containeeEnt = baseContainer.ContainedEntities[0]; + Assert.That(sEntManager.GetComponent(containeeEnt).EntityName, Is.EqualTo("ContaineeEnt")); }); } } diff --git a/Robust.UnitTesting/Shared/GameObjects/EntityState_Tests.cs b/Robust.UnitTesting/Shared/GameObjects/EntityState_Tests.cs index eee676e84..b0aa0353c 100644 --- a/Robust.UnitTesting/Shared/GameObjects/EntityState_Tests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/EntityState_Tests.cs @@ -66,7 +66,7 @@ namespace Robust.UnitTesting.Shared.GameObjects using(var stream = new MemoryStream()) { var payload = new EntityState( - new EntityUid(512), + new NetEntity(64), new [] { new ComponentChange(0, new MapGridComponentState(16, chunkData: null), default) diff --git a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs index ce115c979..7e22dc4b7 100644 --- a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs +++ b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs @@ -63,8 +63,11 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest var coordsA = new EntityCoordinates(map, default); var coordsB = new EntityCoordinates(map, new Vector2(100, 100)); EntityUid player = default; - EntityUid entA = default; - EntityUid entB = default; + EntityUid cPlayer = default; + EntityUid serverEntA = default; + EntityUid serverEntB = default; + NetEntity serverNetA = default; + NetEntity serverNetB = default; await server.WaitPost(() => { @@ -75,17 +78,19 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest session.JoinGame(); // Spawn test entities. - entA = server.EntMan.SpawnAttachedTo(null, coordsA); - entB = server.EntMan.SpawnAttachedTo(null, coordsB); + serverEntA = server.EntMan.SpawnAttachedTo(null, coordsA); + serverEntB = server.EntMan.SpawnAttachedTo(null, coordsB); + serverNetA = server.EntMan.GetNetEntity(serverEntA); + serverNetB = server.EntMan.GetNetEntity(serverEntB); // Setup components - var cmp = server.EntMan.EnsureComponent(entA); - cmp.Other = entB; - server.EntMan.Dirty(entA, cmp); + var cmp = server.EntMan.EnsureComponent(serverEntA); + cmp.Other = serverEntB; + server.EntMan.Dirty(serverEntA, cmp); - cmp = server.EntMan.EnsureComponent(entB); - cmp.Other = entA; - server.EntMan.Dirty(entB, cmp); + cmp = server.EntMan.EnsureComponent(serverEntB); + cmp.Other = serverEntA; + server.EntMan.Dirty(serverEntB, cmp); }); await RunTicks(); @@ -93,10 +98,11 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest // Check player got properly attached and only knows about the expected entities await client.WaitPost(() => { - Assert.That(client.AttachedEntity, Is.EqualTo(player)); - Assert.That(client.EntMan.EntityExists(player)); - Assert.That(client.EntMan.EntityExists(entA), Is.False); - Assert.That(client.EntMan.EntityExists(entB), Is.False); + cPlayer = client.EntMan.GetEntity(server.EntMan.GetNetEntity(player)); + Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer)); + Assert.That(client.EntMan.EntityExists(cPlayer)); + Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False); + Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetB)), Is.False); }); // Move the player into PVS range of one of the entities. @@ -105,11 +111,13 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest await client.WaitPost(() => { - Assert.That(client.EntMan.EntityExists(entB), Is.True); - Assert.That(client.EntMan.EntityExists(entA), Is.False); + var clientEntA = client.EntMan.GetEntity(serverNetA); + var clientEntB = client.EntMan.GetEntity(serverNetB); + Assert.That(client.EntMan.EntityExists(clientEntB), Is.True); + Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False); - Assert.That(client.EntMan.TryGetComponent(entB, out UnknownEntityTestComponent? cmp)); - Assert.That(cmp?.Other, Is.EqualTo(entA)); + Assert.That(client.EntMan.TryGetComponent(clientEntB, out UnknownEntityTestComponent? cmp)); + Assert.That(cmp?.Other, Is.EqualTo(clientEntA)); }); // Move the player into PVS range of the other entity @@ -118,14 +126,16 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest await client.WaitPost(() => { - Assert.That(client.EntMan.EntityExists(entB), Is.True); - Assert.That(client.EntMan.EntityExists(entA), Is.True); + var clientEntA = client.EntMan.GetEntity(serverNetA); + var clientEntB = client.EntMan.GetEntity(serverNetB); + Assert.That(client.EntMan.EntityExists(clientEntB), Is.True); + Assert.That(client.EntMan.EntityExists(clientEntA), Is.True); - Assert.That(client.EntMan.TryGetComponent(entB, out UnknownEntityTestComponent? cmp)); - Assert.That(cmp?.Other, Is.EqualTo(entA)); + Assert.That(client.EntMan.TryGetComponent(clientEntB, out UnknownEntityTestComponent? cmp)); + Assert.That(cmp?.Other, Is.EqualTo(clientEntA)); - Assert.That(client.EntMan.TryGetComponent(entA, out cmp)); - Assert.That(cmp?.Other, Is.EqualTo(entB)); + Assert.That(client.EntMan.TryGetComponent(clientEntA, out cmp)); + Assert.That(cmp?.Other, Is.EqualTo(clientEntB)); }); server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, false)); @@ -150,4 +160,4 @@ public sealed partial class UnknownEntityTestComponent : Component { [AutoNetworkedField] public EntityUid? Other; -} \ No newline at end of file +} diff --git a/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs b/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs index 48df3cb5f..236094899 100644 --- a/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs +++ b/Robust.UnitTesting/Shared/GameState/DeletionNetworkingTests.cs @@ -63,20 +63,24 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest // Set up map & grids EntityUid grid1 = default; EntityUid grid2 = default; - EntityUid map = default; + NetEntity grid1Net = default; + NetEntity grid2Net = default; + await server.WaitPost(() => { var mapId = mapMan.CreateMap(); - map = mapMan.GetMapEntityId(mapId); + mapMan.GetMapEntityId(mapId); var gridComp = mapMan.CreateGrid(mapId); gridComp.SetTile(Vector2i.Zero, new Tile(1)); grid1 = gridComp.Owner; xformSys.SetLocalPosition(grid1, new Vector2(-2,0)); + grid1Net = sEntMan.GetNetEntity(grid1); gridComp = mapMan.CreateGrid(mapId); gridComp.SetTile(Vector2i.Zero, new Tile(1)); grid2 = gridComp.Owner; xformSys.SetLocalPosition(grid2, new Vector2(2,0)); + grid2Net = sEntMan.GetNetEntity(grid2); }); // Spawn player entity on grid 1 @@ -95,8 +99,8 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest // Check player got properly attached await client.WaitPost(() => { - var ent = cPlayerMan.LocalPlayer?.ControlledEntity; - Assert.That(ent, Is.EqualTo(player)); + var ent = cEntMan.GetNetEntity(cPlayerMan.LocalPlayer?.ControlledEntity); + Assert.That(ent, Is.EqualTo(sEntMan.GetNetEntity(player))); }); // Spawn two entities, each with one child. @@ -104,6 +108,12 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest EntityUid entB = default; EntityUid childA = default; EntityUid childB = default; + + NetEntity entANet = default; + NetEntity entBNet = default; + NetEntity childANet = default; + NetEntity childBNet = default!; + var coords = new EntityCoordinates(grid2, new Vector2(0.5f, 0.5f)); await server.WaitPost(() => { @@ -111,67 +121,84 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest entB = sEntMan.SpawnEntity(null, coords); childA = sEntMan.SpawnEntity(null, new EntityCoordinates(entA, default)); childB = sEntMan.SpawnEntity(null, new EntityCoordinates(entB, default)); + + entANet = sEntMan.GetNetEntity(entA); + entBNet = sEntMan.GetNetEntity(entB); + childANet = sEntMan.GetNetEntity(childA); + childBNet = sEntMan.GetNetEntity(childB); }); await RunTicks(); + // Get the client version of the entities. + var cEntA = cEntMan.GetEntity(entANet); + var cEntB = cEntMan.GetEntity(entBNet); + var cChildA = cEntMan.GetEntity(childANet); + var cChildB = cEntMan.GetEntity(childBNet); + var cGrid2 = cEntMan.GetEntity(grid2Net); + // Spawn client-side children and one client-side entity - EntityUid entC = default; - EntityUid childC = default; + EntityUid cEntC = default; + EntityUid cChildC = default; EntityUid clientChildA = default; EntityUid clientChildB = default; + + NetEntity entCNet = NetEntity.Invalid; + await client.WaitPost(() => { - entC = cEntMan.SpawnEntity(null, coords); - childC = cEntMan.SpawnEntity(null, new EntityCoordinates(entC, default)); - clientChildA = cEntMan.SpawnEntity(null, new EntityCoordinates(entA, default)); - clientChildB = cEntMan.SpawnEntity(null, new EntityCoordinates(entB, default)); + cEntC = cEntMan.SpawnEntity(null, cEntMan.GetCoordinates(sEntMan.GetNetCoordinates(coords))); + entCNet = cEntMan.GetNetEntity(cEntC); + cChildC = cEntMan.SpawnEntity(null, new EntityCoordinates(cEntC, default)); + clientChildA = cEntMan.SpawnEntity(null, new EntityCoordinates(cEntA, default)); + clientChildB = cEntMan.SpawnEntity(null, new EntityCoordinates(cEntB, default)); }); await RunTicks(); // Verify entities exist and have the correct parents. - EntityUid Parent(EntityUid uid) => cEntMan!.GetComponent(uid).ParentUid; + NetEntity Parent(EntityUid uid) => cEntMan.GetNetEntity(cEntMan.GetComponent(uid).ParentUid); await client.WaitPost(() => { // Exist - Assert.That(cEntMan.EntityExists(entA)); - Assert.That(cEntMan.EntityExists(entB)); - Assert.That(cEntMan.EntityExists(entC)); - Assert.That(cEntMan.EntityExists(childA)); - Assert.That(cEntMan.EntityExists(childB)); - Assert.That(cEntMan.EntityExists(childC)); + Assert.That(cEntMan.EntityExists(cEntA)); + Assert.That(cEntMan.EntityExists(cEntB)); + Assert.That(cEntMan.EntityExists(cEntC)); + Assert.That(cEntMan.EntityExists(cChildA)); + Assert.That(cEntMan.EntityExists(cChildB)); + Assert.That(cEntMan.EntityExists(cChildC)); Assert.That(cEntMan.EntityExists(clientChildA)); Assert.That(cEntMan.EntityExists(clientChildB)); // Client-side where appropriate - Assert.That(entC.IsClientSide()); - Assert.That(childC.IsClientSide()); - Assert.That(clientChildA.IsClientSide()); - Assert.That(clientChildB.IsClientSide()); - Assert.That(!entA.IsClientSide()); - Assert.That(!entB.IsClientSide()); - Assert.That(!childA.IsClientSide()); - Assert.That(!childB.IsClientSide()); + Assert.That(cEntMan.IsClientSide(cEntC)); + Assert.That(cEntMan.IsClientSide(cChildC)); + Assert.That(cEntMan.IsClientSide(clientChildA)); + Assert.That(cEntMan.IsClientSide(clientChildB)); + Assert.That(!cEntMan.IsClientSide(cEntA)); + Assert.That(!cEntMan.IsClientSide(cEntB)); + Assert.That(!cEntMan.IsClientSide(cChildA)); + Assert.That(!cEntMan.IsClientSide(cChildB)); // Correct parents. - Assert.That(Parent(entA), Is.EqualTo(grid2)); - Assert.That(Parent(entB), Is.EqualTo(grid2)); - Assert.That(Parent(entC), Is.EqualTo(grid2)); - Assert.That(Parent(childA), Is.EqualTo(entA)); - Assert.That(Parent(childB), Is.EqualTo(entB)); - Assert.That(Parent(childC), Is.EqualTo(entC)); - Assert.That(Parent(clientChildA), Is.EqualTo(entA)); - Assert.That(Parent(clientChildB), Is.EqualTo(entB)); + + Assert.That(Parent(cEntA), Is.EqualTo(grid2Net)); + Assert.That(Parent(cEntB), Is.EqualTo(grid2Net)); + Assert.That(Parent(cEntC), Is.EqualTo(grid2Net)); + Assert.That(Parent(cChildA), Is.EqualTo(entANet)); + Assert.That(Parent(cChildB), Is.EqualTo(entBNet)); + Assert.That(Parent(cChildC), Is.EqualTo(entCNet)); + Assert.That(Parent(clientChildA), Is.EqualTo(entANet)); + Assert.That(Parent(clientChildB), Is.EqualTo(entBNet)); }); // Delete client-side entity. - await client.WaitPost(() => cEntMan.DeleteEntity(entC)); + await client.WaitPost(() => cEntMan.DeleteEntity(cEntC)); await RunTicks(); await client.WaitPost(() => { - Assert.That(!cEntMan.EntityExists(entC)); - Assert.That(!cEntMan.EntityExists(childC)); + Assert.That(!cEntMan.EntityExists(cEntC)); + Assert.That(!cEntMan.EntityExists(cChildC)); }); // Delete server-side entity. @@ -179,8 +206,8 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest await RunTicks(); await client.WaitPost(() => { - Assert.That(!cEntMan.EntityExists(entB)); - Assert.That(!cEntMan.EntityExists(childB)); + Assert.That(!cEntMan.EntityExists(cEntB)); + Assert.That(!cEntMan.EntityExists(cChildB)); // Was never explicitly deleted by the client. Assert.That(cEntMan.EntityExists(clientChildB)); @@ -191,9 +218,9 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest await RunTicks(); await client.WaitPost(() => { - Assert.That(!cEntMan.EntityExists(grid2)); - Assert.That(!cEntMan.EntityExists(entA)); - Assert.That(!cEntMan.EntityExists(childA)); + Assert.That(!cEntMan.EntityExists(cGrid2)); + Assert.That(!cEntMan.EntityExists(cEntA)); + Assert.That(!cEntMan.EntityExists(cChildA)); // Was never explicitly deleted by the client. Assert.That(cEntMan.EntityExists(clientChildA)); diff --git a/Robust.UnitTesting/Shared/Input/Binding/CommandBindRegistry_Test.cs b/Robust.UnitTesting/Shared/Input/Binding/CommandBindRegistry_Test.cs index 62f65aaa5..e6e53b54e 100644 --- a/Robust.UnitTesting/Shared/Input/Binding/CommandBindRegistry_Test.cs +++ b/Robust.UnitTesting/Shared/Input/Binding/CommandBindRegistry_Test.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.Players; @@ -33,7 +34,7 @@ namespace Robust.UnitTesting.Shared.Input.Binding return name; } - public override bool HandleCmdMessage(ICommonSession? session, InputCmdMessage message) + public override bool HandleCmdMessage(IEntityManager entManager, ICommonSession? session, IFullInputCmdMessage message) { return false; } diff --git a/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs b/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs index cb500733a..fede28915 100644 --- a/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs +++ b/Robust.UnitTesting/Shared/Physics/BroadphaseNetworkingTest.cs @@ -69,6 +69,8 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest grid1 = gridComp.Owner; }); + var map1Net = sEntMan.GetNetEntity(map1); + // Spawn player entity on grid 1 EntityUid player = default; await server.WaitPost(() => @@ -93,6 +95,8 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest session.JoinGame(); }); + var playerNet = sEntMan.GetNetEntity(player); + for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); @@ -102,20 +106,26 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest // Check player got properly attached await client.WaitPost(() => { - var ent = cPlayerMan.LocalPlayer?.ControlledEntity; - Assert.That(ent, Is.EqualTo(player)); + var ent = cEntMan.GetNetEntity(cPlayerMan.LocalPlayer?.ControlledEntity); + Assert.That(ent, Is.EqualTo(playerNet)); }); var sPlayerXform = sEntMan.GetComponent(player); - var cPlayerXform = cEntMan.GetComponent(player); + var cPlayerXform = cEntMan.GetComponent(cEntMan.GetEntity(playerNet)); // Client initially has correct transform data. var broadphase = new BroadphaseData(grid1, map1, true, false); - Assert.That(cPlayerXform.GridUid, Is.EqualTo(grid1)); + var grid1Net = sEntMan.GetNetEntity(grid1); + + Assert.That(cPlayerXform.GridUid, Is.EqualTo(cEntMan.GetEntity(grid1Net))); Assert.That(sPlayerXform.GridUid, Is.EqualTo(grid1)); - Assert.That(cPlayerXform.MapUid, Is.EqualTo(map1)); + Assert.That(cPlayerXform.MapUid, Is.EqualTo(cEntMan.GetEntity(map1Net))); Assert.That(sPlayerXform.MapUid, Is.EqualTo(map1)); - Assert.That(cPlayerXform.Broadphase, Is.EqualTo(broadphase)); + + Assert.That(cPlayerXform.Broadphase?.Uid, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.Uid)))); + Assert.That(cPlayerXform.Broadphase?.PhysicsMap, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.PhysicsMap)))); + Assert.That(cPlayerXform.Broadphase?.Static, Is.EqualTo(broadphase.Static)); + Assert.That(cPlayerXform.Broadphase?.CanCollide, Is.EqualTo(broadphase.CanCollide)); Assert.That(sPlayerXform.Broadphase, Is.EqualTo(broadphase)); // Set up maps 2 & grid 2 and move the player there (in the same tick). @@ -136,6 +146,9 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest }); + var grid2Net = sEntMan.GetNetEntity(grid2); + var map2Net = sEntMan.GetNetEntity(map2); + for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); @@ -144,11 +157,15 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest // Player & server xforms should match. broadphase = new BroadphaseData(grid2, map2, true, false); - Assert.That(cPlayerXform.GridUid, Is.EqualTo(grid2)); + Assert.That(cEntMan.GetNetEntity(cPlayerXform.GridUid), Is.EqualTo(grid2Net)); Assert.That(sPlayerXform.GridUid, Is.EqualTo(grid2)); - Assert.That(cPlayerXform.MapUid, Is.EqualTo(map2)); + Assert.That(cEntMan.GetNetEntity(cPlayerXform.MapUid), Is.EqualTo(map2Net)); Assert.That(sPlayerXform.MapUid, Is.EqualTo(map2)); - Assert.That(cPlayerXform.Broadphase, Is.EqualTo(broadphase)); + + Assert.That(cPlayerXform.Broadphase?.Uid, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.Uid)))); + Assert.That(cPlayerXform.Broadphase?.PhysicsMap, Is.EqualTo(cEntMan.GetEntity(sEntMan.GetNetEntity(broadphase.PhysicsMap)))); + Assert.That(cPlayerXform.Broadphase?.Static, Is.EqualTo(broadphase.Static)); + Assert.That(cPlayerXform.Broadphase?.CanCollide, Is.EqualTo(broadphase.CanCollide)); Assert.That(sPlayerXform.Broadphase, Is.EqualTo(broadphase)); } } diff --git a/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs b/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs index e853dcf56..2e1135815 100644 --- a/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs +++ b/Robust.UnitTesting/Shared/Spawning/EntitySpawnHelpersTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; @@ -5,6 +6,7 @@ using NUnit.Framework; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Serialization; namespace Robust.UnitTesting.Shared.Spawning; @@ -100,18 +102,19 @@ public abstract partial class EntitySpawnHelpersTest : RobustIntegrationTest /// /// Simple container that can store up to 2 entities. /// + [SerializedType(nameof(TestContainer))] private sealed partial class TestContainer : BaseContainer { private readonly List _ents = new(); - private readonly List _expected = new(); - public override string ContainerType => nameof(TestContainer); + + public override int Count => _ents.Count; + public override IReadOnlyList ContainedEntities => _ents; - public override List ExpectedEntities => _expected; protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan) => _ents.Add(toInsert); protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan) => _ents.Remove(toRemove); public override bool Contains(EntityUid contained) => _ents.Contains(contained); protected override void InternalShutdown(IEntityManager entMan, bool isClient) { } - public override bool CanInsert(EntityUid toinsert, IEntityManager? entMan = null) + protected internal override bool CanInsert(EntityUid toinsert, bool assumeEmpty, IEntityManager entMan) => _ents.Count < 2 && !_ents.Contains(toinsert); } } diff --git a/Robust.UnitTesting/Shared/Toolshed/ToolshedParserTest.cs b/Robust.UnitTesting/Shared/Toolshed/ToolshedParserTest.cs index 00b56c320..59c54c71e 100644 --- a/Robust.UnitTesting/Shared/Toolshed/ToolshedParserTest.cs +++ b/Robust.UnitTesting/Shared/Toolshed/ToolshedParserTest.cs @@ -38,14 +38,14 @@ public sealed partial class ToolshedParserTest : ToolshedTest } [Test] - public async Task EntityUidTypeParser() + public async Task EntityTypeParser() { await Server.WaitAssertion(() => { ParseCommand("ent 1"); - ParseCommand("ent c1"); + // Clientside entities are a myth. - ExpectError(); + ExpectError(); ParseCommand("ent foodigity"); }); }