diff --git a/Content.Tests/DMProject/Tests/List/worldcontents.dm b/Content.Tests/DMProject/Tests/List/worldcontents.dm new file mode 100644 index 0000000000..a60b3d1e76 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/worldcontents.dm @@ -0,0 +1,15 @@ +/proc/RunTest() + var/obj/object2 = null + for(var/i in 1 to 3) + var/obj/O = new /obj(locate(1,1,1)) + O.name = "object [i]" + if(i==2) + object2 = O + var/startcount = length(world.contents) + var/i = 0 + for(var/O in world) + if(i==0) + del(object2) + ASSERT(length(world.contents) == startcount-1) + i++ + ASSERT(i == startcount-1) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/worldcontents2.dm b/Content.Tests/DMProject/Tests/List/worldcontents2.dm new file mode 100644 index 0000000000..5a23f5abc4 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/worldcontents2.dm @@ -0,0 +1,15 @@ +/proc/RunTest() + var/obj/object2 = null + for(var/i in 1 to 3) + var/obj/O = new /obj(locate(1,1,1)) + O.name = "object [i]" + if(i==2) + object2 = O + var/startcount = length(world.contents) + var/i = 0 + for(var/O in world) + if(i==3) + del(object2) + ASSERT(length(world.contents) == startcount-1) + i++ + ASSERT(i == startcount) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/worldcontents3.dm b/Content.Tests/DMProject/Tests/List/worldcontents3.dm new file mode 100644 index 0000000000..28a427f94f --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/worldcontents3.dm @@ -0,0 +1,12 @@ +/proc/RunTest() + for(var/j in 1 to 3) + var/obj/O = new /obj(locate(1,1,1)) + O.name = "object [j]" + var/startcount = length(world.contents) + var/i = 0 + for(var/O in world) + if(i==1) + new /obj(locate(1,1,1)) + ASSERT(length(world.contents) == startcount+1) + i++ + ASSERT(i == startcount+1) \ No newline at end of file diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index d7137e0137..05573c5394 100644 --- a/DMCompiler/DMStandard/Types/World.dm +++ b/DMCompiler/DMStandard/Types/World.dm @@ -1,6 +1,6 @@ /world - var/list/contents = null - var/list/vars + var/const/list/contents = null + var/const/list/vars var/log = null diff --git a/OpenDreamRuntime/AtomManager.cs b/OpenDreamRuntime/AtomManager.cs index 47d99387dd..20fbc2bb67 100644 --- a/OpenDreamRuntime/AtomManager.cs +++ b/OpenDreamRuntime/AtomManager.cs @@ -10,12 +10,54 @@ using Dependency = Robust.Shared.IoC.DependencyAttribute; namespace OpenDreamRuntime { + + /// + /// A list that has special behaviour so we can enumerate world.contents without copying the entire list. + /// + /// + public sealed class WorldContentList : List { + private int _isEnumerating = 0; //keep count of how many enumerations are active (for nested iterations of world.contents) + /// + /// Removes the first instance of the given value, replacing it with the last value in the list. This is done to avoid shifting the entire list. + /// + /// + /// + private bool RemoveSwapLast(T Value) { + int index = IndexOf(Value); + if (index == -1) + return false; + var old = this[index]; + var replacement = this[this.Count - 1]; + this[index] = replacement; + base.RemoveAt(this.Count - 1); + return true; + } + + public new void Remove(T Value) { + if (_isEnumerating == 0) { + RemoveSwapLast(Value); + return; + } else { + base.Remove(Value); + } + } + public void StartEnumeration() { + _isEnumerating++; + } + + public void FinishEnumeration() { + if(_isEnumerating > 0) + _isEnumerating--; + else + throw new Exception("FinishEnumeration called without StartEnumeration"); + } + } public sealed class AtomManager { - public List Areas { get; } = new(); - public List Turfs { get; } = new(); - public List Movables { get; } = new(); - public List Objects { get; } = new(); - public List Mobs { get; } = new(); + public WorldContentList Areas { get; } = new(); + public WorldContentList Turfs { get; } = new(); + public WorldContentList Movables { get; } = new(); + public WorldContentList Objects { get; } = new(); + public WorldContentList Mobs { get; } = new(); public int AtomCount => Areas.Count + Turfs.Count + Movables.Count + Objects.Count + Mobs.Count; [Dependency] private readonly IEntityManager _entityManager = default!; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs index af1ff3dd33..3173400fb1 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs @@ -63,11 +63,11 @@ public override void Initialize(DreamProcArguments args) { protected override void HandleDeletion() { if (IsSubtypeOf(ObjectTree.Obj)) - AtomManager.Objects.RemoveSwap(AtomManager.Objects.IndexOf(this)); + AtomManager.Objects.Remove(this); else if (IsSubtypeOf(ObjectTree.Mob)) - AtomManager.Mobs.RemoveSwap(AtomManager.Mobs.IndexOf((DreamObjectMob)this)); + AtomManager.Mobs.Remove((DreamObjectMob)this); else - AtomManager.Movables.RemoveSwap(AtomManager.Movables.IndexOf(this)); + AtomManager.Movables.Remove(this); AtomManager.DeleteMovableEntity(this); base.HandleDeletion(); diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index e10ff95c28..6345907b15 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -86,17 +86,6 @@ private static IDreamValueEnumerator GetContentsEnumerator(DreamObjectTree objec if (dreamObject is DreamObjectAtom) { list = dreamObject.GetVariable("contents").MustGetValueAsDreamList(); } else if (dreamObject is DreamObjectWorld) { - // Use a different enumerator for /area and /turf that only enumerates those rather than all atoms - if (filterType?.ObjectDefinition.IsSubtypeOf(objectTree.Area) == true) { - return new DreamObjectEnumerator(atomManager.Areas, filterType); - } else if (filterType?.ObjectDefinition.IsSubtypeOf(objectTree.Turf) == true) { - return new DreamObjectEnumerator(atomManager.Turfs, filterType); - } else if (filterType?.ObjectDefinition.IsSubtypeOf(objectTree.Obj) == true) { - return new DreamObjectEnumerator(atomManager.Objects, filterType); - } else if (filterType?.ObjectDefinition.IsSubtypeOf(objectTree.Mob) == true) { - return new DreamObjectEnumerator(atomManager.Mobs, filterType); - } - return new WorldContentsEnumerator(atomManager, filterType); } } @@ -239,8 +228,10 @@ public static ProcStatus Enumerate(DMProcState state) { DreamReference outputRef = state.ReadReference(); int jumpToIfFailure = state.ReadInt(); - if (!enumerator.Enumerate(state, outputRef)) + if (!enumerator.Enumerate(state, outputRef)) { + enumerator.EndEnumeration(); state.Jump(jumpToIfFailure); + } return ProcStatus.Continue; } @@ -249,8 +240,10 @@ public static ProcStatus EnumerateNoAssign(DMProcState state) { IDreamValueEnumerator enumerator = state.EnumeratorStack.Peek(); int jumpToIfFailure = state.ReadInt(); - if (!enumerator.Enumerate(state, null)) + if (!enumerator.Enumerate(state, null)) { + enumerator.EndEnumeration(); state.Jump(jumpToIfFailure); + } return ProcStatus.Continue; } diff --git a/OpenDreamRuntime/Procs/DreamEnumerators.cs b/OpenDreamRuntime/Procs/DreamEnumerators.cs index 38b424e640..31d545c5e9 100644 --- a/OpenDreamRuntime/Procs/DreamEnumerators.cs +++ b/OpenDreamRuntime/Procs/DreamEnumerators.cs @@ -1,9 +1,11 @@ using OpenDreamRuntime.Objects; +using OpenDreamRuntime.Objects.Types; using OpenDreamShared.Dream.Procs; namespace OpenDreamRuntime.Procs { public interface IDreamValueEnumerator { public bool Enumerate(DMProcState state, DreamReference? reference); + public void EndEnumeration(); } /// @@ -30,6 +32,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { return successful; } + + void IDreamValueEnumerator.EndEnumeration() { + } } /// @@ -57,6 +62,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { state.AssignReference(reference.Value, new DreamValue(_dreamObjectEnumerator.Current)); return success; } + + void IDreamValueEnumerator.EndEnumeration() { + } } /// @@ -79,6 +87,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { state.AssignReference(reference.Value, success ? _dreamValueArray[_current] : DreamValue.Null); // Assign regardless of success return success; } + + void IDreamValueEnumerator.EndEnumeration() { + } } /// @@ -112,6 +123,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { } } while (true); } + + void IDreamValueEnumerator.EndEnumeration() { + } } /// @@ -122,14 +136,73 @@ sealed class WorldContentsEnumerator : IDreamValueEnumerator { private readonly AtomManager _atomManager; private readonly TreeEntry? _filterType; private int _current = -1; + private int _startCount = 0; public WorldContentsEnumerator(AtomManager atomManager, TreeEntry? filterType) { _atomManager = atomManager; _filterType = filterType; + if(filterType is not null) + if(filterType.ObjectDefinition.IsSubtypeOf(filterType.ObjectDefinition.ObjectTree.Area)) { + atomManager.Areas.StartEnumeration(); + _startCount = atomManager.Areas.Count; + } else if(filterType.ObjectDefinition.IsSubtypeOf(filterType.ObjectDefinition.ObjectTree.Mob)) { + atomManager.Mobs.StartEnumeration(); + _startCount = atomManager.Mobs.Count; + } else if(filterType.ObjectDefinition.IsSubtypeOf(filterType.ObjectDefinition.ObjectTree.Obj)) { + atomManager.Objects.StartEnumeration(); + _startCount = atomManager.Objects.Count; + } else if(filterType.ObjectDefinition.IsSubtypeOf(filterType.ObjectDefinition.ObjectTree.Turf)) { + atomManager.Turfs.StartEnumeration(); + _startCount = atomManager.Turfs.Count; + } else if(filterType.ObjectDefinition.IsSubtypeOf(filterType.ObjectDefinition.ObjectTree.Movable)) { + atomManager.Movables.StartEnumeration(); + _startCount = atomManager.Movables.Count; + } + else { + atomManager.Areas.StartEnumeration(); + atomManager.Mobs.StartEnumeration(); + atomManager.Objects.StartEnumeration(); + atomManager.Turfs.StartEnumeration(); + atomManager.Movables.StartEnumeration(); + _startCount = atomManager.AtomCount; + } } public bool Enumerate(DMProcState state, DreamReference? reference) { do { + if(_filterType is null){ + if(_startCount != _atomManager.AtomCount){ + _current = _current - Math.Max(0, _startCount - _atomManager.AtomCount); //if we got smaller, we need to adjust our current index + _startCount = _atomManager.AtomCount; + } + } else { + if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Area)) { + if(_startCount != _atomManager.Areas.Count){ + _current = _current - Math.Max(0, _startCount - _atomManager.Areas.Count); //if we got smaller, we need to adjust our current index + _startCount = _atomManager.Areas.Count; + } + } else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Mob)) { + if(_startCount != _atomManager.Mobs.Count){ + _current = _current - Math.Max(0, _startCount - _atomManager.Mobs.Count); //if we got smaller, we need to adjust our current index + _startCount = _atomManager.Mobs.Count; + } + } else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Obj)) { + if(_startCount != _atomManager.Objects.Count){ + _current = _current - Math.Max(0, _startCount - _atomManager.Objects.Count); //if we got smaller, we need to adjust our current index + _startCount = _atomManager.Objects.Count; + } + } else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Turf)) { + if(_startCount != _atomManager.Turfs.Count){ + _current = _current - Math.Max(0, _startCount - _atomManager.Turfs.Count); //if we got smaller, we need to adjust our current index + _startCount = _atomManager.Turfs.Count; + } + } else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Movable)) { + if(_startCount != _atomManager.Movables.Count){ + _current = _current - Math.Max(0, _startCount - _atomManager.Movables.Count); //if we got smaller, we need to adjust our current index + _startCount = _atomManager.Movables.Count; + } + } + } _current++; if (_current >= _atomManager.AtomCount) { if (reference != null) @@ -138,12 +211,33 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { } DreamObject atom = _atomManager.GetAtom(_current); - if (_filterType == null || atom.IsSubtypeOf(_filterType)) { + if (!atom.Deleted && (_filterType == null || atom.IsSubtypeOf(_filterType))) { if (reference != null) state.AssignReference(reference.Value, new DreamValue(atom)); return true; } } while (true); } + + void IDreamValueEnumerator.EndEnumeration() { + if(_filterType is not null) + if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Area)) + _atomManager.Areas.FinishEnumeration(); + else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Mob)) + _atomManager.Mobs.FinishEnumeration(); + else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Obj)) + _atomManager.Objects.FinishEnumeration(); + else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Turf)) + _atomManager.Turfs.FinishEnumeration(); + else if(_filterType.ObjectDefinition.IsSubtypeOf(_filterType.ObjectDefinition.ObjectTree.Movable)) + _atomManager.Movables.FinishEnumeration(); + else { + _atomManager.Areas.FinishEnumeration(); + _atomManager.Mobs.FinishEnumeration(); + _atomManager.Objects.FinishEnumeration(); + _atomManager.Turfs.FinishEnumeration(); + _atomManager.Movables.FinishEnumeration(); + } + } } }