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();
+ }
+ }
}
}