diff --git a/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.ClientContainer.cs b/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.ClientContainer.cs index d181b21581..39ec35f862 100644 --- a/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.ClientContainer.cs +++ b/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.ClientContainer.cs @@ -26,7 +26,7 @@ namespace Robust.Client.GameObjects.Components.Containers [ViewVariables] public string ID { get; } [ViewVariables] public IEntity Owner => Manager.Owner; [ViewVariables] public bool Deleted { get; private set; } - [ViewVariables] public IReadOnlyCollection ContainedEntities => Entities; + [ViewVariables] public IReadOnlyList ContainedEntities => Entities; public bool ShowContents { get; set; } public bool CanInsert(IEntity toinsert) diff --git a/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.cs b/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.cs index c126ec1e3e..d1d98a3b70 100644 --- a/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.cs +++ b/Robust.Client/GameObjects/Components/Containers/ContainerManagerComponent.cs @@ -26,8 +26,10 @@ namespace Robust.Client.GameObjects.Components.Containers throw new NotSupportedException("Cannot modify containers on the client."); } - public override IEnumerable GetAllContainers() => - _containers.Values.Where(c => !c.Deleted); + protected override IEnumerable GetAllContainersImpl() + { + return _containers.Values.Where(c => !c.Deleted); + } public override IContainer GetContainer(string id) { diff --git a/Robust.Server/GameObjects/Components/Container/Container.cs b/Robust.Server/GameObjects/Components/Container/Container.cs index 507cdd74e8..dbf3d3f99f 100644 --- a/Robust.Server/GameObjects/Components/Container/Container.cs +++ b/Robust.Server/GameObjects/Components/Container/Container.cs @@ -29,7 +29,7 @@ namespace Robust.Server.GameObjects.Components.Container public Container(string id, IContainerManager manager) : base(id, manager) { } /// - public override IReadOnlyCollection ContainedEntities => _containerList.AsReadOnly(); + public override IReadOnlyList ContainedEntities => _containerList; /// protected override void InternalInsert(IEntity toinsert) @@ -85,7 +85,7 @@ namespace Robust.Server.GameObjects.Components.Container /// [ViewVariables] - public abstract IReadOnlyCollection ContainedEntities { get; } + public abstract IReadOnlyList ContainedEntities { get; } /// [ViewVariables(VVAccess.ReadWrite)] diff --git a/Robust.Server/GameObjects/Components/Container/ContainerManagerComponent.cs b/Robust.Server/GameObjects/Components/Container/ContainerManagerComponent.cs index 28f6f7a318..9c54fb96aa 100644 --- a/Robust.Server/GameObjects/Components/Container/ContainerManagerComponent.cs +++ b/Robust.Server/GameObjects/Components/Container/ContainerManagerComponent.cs @@ -1,6 +1,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -21,8 +22,7 @@ namespace Robust.Server.GameObjects.Components.Container private readonly Dictionary EntityContainers = new Dictionary(); private Dictionary>? _entitiesWaitingResolve; - [ViewVariables] - private IEnumerable _allContainers => EntityContainers.Values; + [ViewVariables] private IEnumerable _allContainers => EntityContainers.Values; /// /// Shortcut method to make creation of containers easier. @@ -63,7 +63,8 @@ namespace Robust.Server.GameObjects.Components.Container if (!(existing is T container)) { - throw new InvalidOperationException($"The container exists but is of a different type: {existing.GetType()}"); + throw new InvalidOperationException( + $"The container exists but is of a different type: {existing.GetType()}"); } alreadyExisted = true; @@ -81,15 +82,22 @@ namespace Robust.Server.GameObjects.Components.Container { throw new ArgumentException($"Container with specified ID already exists: '{id}'"); } - var container = (IContainer)Activator.CreateInstance(type, id, this)!; + + var container = (IContainer) Activator.CreateInstance(type, id, this)!; EntityContainers[id] = container; Dirty(); return container; } - /// - public override IEnumerable GetAllContainers() => - EntityContainers.Values.Where(c => !c.Deleted); + public new AllContainersEnumerable GetAllContainers() + { + return new AllContainersEnumerable(this); + } + + protected override IEnumerable GetAllContainersImpl() + { + return GetAllContainers(); + } /// public override IContainer GetContainer(string id) @@ -111,6 +119,7 @@ namespace Robust.Server.GameObjects.Components.Container container = null; return false; } + container = GetContainer(id); return true; } @@ -171,6 +180,7 @@ namespace Robust.Server.GameObjects.Components.Container return containers.Remove(entity); } } + return true; // If we don't contain the entity, it will always be removed } @@ -179,10 +189,11 @@ namespace Robust.Server.GameObjects.Components.Container base.OnRemove(); // IContianer.Shutdown modifies the EntityContainers collection - foreach(var container in EntityContainers.Values.ToArray()) + foreach (var container in EntityContainers.Values.ToArray()) { container.Shutdown(); } + EntityContainers.Clear(); } @@ -209,6 +220,7 @@ namespace Robust.Server.GameObjects.Components.Container { continue; } + var list = new List(datum.Entities.Where(u => u.IsValid())); _entitiesWaitingResolve.Add(key, list); } @@ -225,7 +237,8 @@ namespace Robust.Server.GameObjects.Components.Container } // ReSharper disable once RedundantTypeArgumentsOfMethod - serializer.DataWriteFunction?>("containers", null, () => dict); + serializer.DataWriteFunction?>("containers", null, + () => dict); } } @@ -275,5 +288,68 @@ namespace Robust.Server.GameObjects.Components.Container serializer.DataField(ref Type, "type", null); } } + + public struct AllContainersEnumerable : IEnumerable + { + private readonly ContainerManagerComponent _manager; + + public AllContainersEnumerable(ContainerManagerComponent manager) + { + _manager = manager; + } + + public AllContainersEnumerator GetEnumerator() + { + return new AllContainersEnumerator(_manager); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public struct AllContainersEnumerator : IEnumerator + { + private Dictionary.ValueCollection.Enumerator _enumerator; + + public AllContainersEnumerator(ContainerManagerComponent manager) + { + _enumerator = manager.EntityContainers.Values.GetEnumerator(); + Current = default; + } + + public bool MoveNext() + { + while (_enumerator.MoveNext()) + { + if (!_enumerator.Current.Deleted) + { + Current = _enumerator.Current; + return true; + } + } + + return false; + } + + void IEnumerator.Reset() + { + ((IEnumerator) _enumerator).Reset(); + } + + [AllowNull] public IContainer Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + } + } } } diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs index 36d1a2ec0d..efba0b4be3 100644 --- a/Robust.Server/GameObjects/ServerEntityManager.cs +++ b/Robust.Server/GameObjects/ServerEntityManager.cs @@ -311,8 +311,11 @@ namespace Robust.Server.GameObjects foreach (var container in contMgr.GetAllContainers()) { - foreach (var contEnt in container.ContainedEntities) + // Manual for loop to cut out allocations. + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < container.ContainedEntities.Count; i++) { + var contEnt = container.ContainedEntities[i]; set.Add(contEnt); AddContainedRecursive(contEnt, set); } diff --git a/Robust.Shared/GameObjects/Components/Containers/SharedContainerManagerComponent.cs b/Robust.Shared/GameObjects/Components/Containers/SharedContainerManagerComponent.cs index 0ffaa51879..d41d35192a 100644 --- a/Robust.Shared/GameObjects/Components/Containers/SharedContainerManagerComponent.cs +++ b/Robust.Shared/GameObjects/Components/Containers/SharedContainerManagerComponent.cs @@ -35,7 +35,9 @@ namespace Robust.Shared.GameObjects.Components.Containers } } - public abstract IEnumerable GetAllContainers(); + public IEnumerable GetAllContainers() => GetAllContainersImpl(); + // Separate impl method to facilitate method hiding in the subclasses. + protected abstract IEnumerable GetAllContainersImpl(); } } diff --git a/Robust.Shared/Interfaces/GameObjects/Components/IContainer.cs b/Robust.Shared/Interfaces/GameObjects/Components/IContainer.cs index f59cc7a478..5bcdf99076 100644 --- a/Robust.Shared/Interfaces/GameObjects/Components/IContainer.cs +++ b/Robust.Shared/Interfaces/GameObjects/Components/IContainer.cs @@ -46,7 +46,7 @@ namespace Robust.Shared.Interfaces.GameObjects.Components /// /// Readonly collection of all the entities contained within this specific container /// - IReadOnlyCollection ContainedEntities { get; } + IReadOnlyList ContainedEntities { get; } /// /// Should the contents of this container be shown? False for closed containers like lockers, true for