Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buffer edits on world.contents while enumerating them #1459

Closed
15 changes: 15 additions & 0 deletions Content.Tests/DMProject/Tests/List/worldcontents.dm
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 15 additions & 0 deletions Content.Tests/DMProject/Tests/List/worldcontents2.dm
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions Content.Tests/DMProject/Tests/List/worldcontents3.dm
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions DMCompiler/DMStandard/Types/World.dm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/world
var/list/contents = null
var/list/vars
var/const/list/contents = null
var/const/list/vars

var/log = null

Expand Down
52 changes: 47 additions & 5 deletions OpenDreamRuntime/AtomManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,54 @@
using Dependency = Robust.Shared.IoC.DependencyAttribute;

namespace OpenDreamRuntime {

/// <summary>
/// A list that has special behaviour so we can enumerate world.contents without copying the entire list.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class WorldContentList<T> : List<T> {
private int _isEnumerating = 0; //keep count of how many enumerations are active (for nested iterations of world.contents)
/// <summary>
/// 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.
/// </summary>
/// <param name="Value"></param>
/// <returns></returns>
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<DreamObjectArea> Areas { get; } = new();
public List<DreamObjectTurf> Turfs { get; } = new();
public List<DreamObjectMovable> Movables { get; } = new();
public List<DreamObjectMovable> Objects { get; } = new();
public List<DreamObjectMob> Mobs { get; } = new();
public WorldContentList<DreamObjectArea> Areas { get; } = new();
public WorldContentList<DreamObjectTurf> Turfs { get; } = new();
public WorldContentList<DreamObjectMovable> Movables { get; } = new();
public WorldContentList<DreamObjectMovable> Objects { get; } = new();
public WorldContentList<DreamObjectMob> Mobs { get; } = new();
public int AtomCount => Areas.Count + Turfs.Count + Movables.Count + Objects.Count + Mobs.Count;

[Dependency] private readonly IEntityManager _entityManager = default!;
Expand Down
6 changes: 3 additions & 3 deletions OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
19 changes: 6 additions & 13 deletions OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
96 changes: 95 additions & 1 deletion OpenDreamRuntime/Procs/DreamEnumerators.cs
Original file line number Diff line number Diff line change
@@ -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();
}

/// <summary>
Expand All @@ -30,6 +32,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {

return successful;
}

void IDreamValueEnumerator.EndEnumeration() {
}
}

/// <summary>
Expand Down Expand Up @@ -57,6 +62,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {
state.AssignReference(reference.Value, new DreamValue(_dreamObjectEnumerator.Current));
return success;
}

void IDreamValueEnumerator.EndEnumeration() {
}
}

/// <summary>
Expand All @@ -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() {
}
}

/// <summary>
Expand Down Expand Up @@ -112,6 +123,9 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {
}
} while (true);
}

void IDreamValueEnumerator.EndEnumeration() {
}
}

/// <summary>
Expand All @@ -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)
Expand All @@ -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();
}
}
}
}
Loading