diff --git a/Content.Tests/DMProject/Tests/List/ListIndexToKey.dm b/Content.Tests/DMProject/Tests/List/ListIndexToKey.dm
new file mode 100644
index 0000000000..37ddbc713d
--- /dev/null
+++ b/Content.Tests/DMProject/Tests/List/ListIndexToKey.dm
@@ -0,0 +1,13 @@
+/proc/RunTest()
+ var/list/A = list("thing")
+ ASSERT(A[1] == "thing")
+
+ A["thing"] = 6
+ ASSERT(A["thing"] == 6)
+
+ var/list/L = list()
+ for(var/i in 1 to 5)
+ L.Add("[i]")
+ L["[i]"] = "item [i]"
+ ASSERT(length(L) == 5)
+ ASSERT(L["3"] == "item 3")
diff --git a/OpenDreamRuntime/Objects/Types/DreamAssocList.cs b/OpenDreamRuntime/Objects/Types/DreamAssocList.cs
index 8b84d4aa53..bfed4e2a65 100644
--- a/OpenDreamRuntime/Objects/Types/DreamAssocList.cs
+++ b/OpenDreamRuntime/Objects/Types/DreamAssocList.cs
@@ -119,4 +119,16 @@ public bool ContainsValue(DreamValue value) {
return _values.ContainsKey(value);
}
+ public override DreamValue OperatorAppend(DreamValue b) {
+ if (b.TryGetValueAsDreamList(out var bList)) {
+ foreach (var value in bList.EnumerateValues()) {
+ AddValue(value);
+ }
+ } else {
+ AddValue(b);
+ }
+
+ return new(this);
+ }
+
}
diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs
index 7dc0e2e830..bf15b0906b 100644
--- a/OpenDreamRuntime/Objects/Types/DreamList.cs
+++ b/OpenDreamRuntime/Objects/Types/DreamList.cs
@@ -18,6 +18,10 @@ public class DreamList : DreamObject, IDreamList {
public virtual bool IsAssociative => _associativeValues is { Count: > 0 };
+ ///
+ /// Looks up the key to find the index
+ ///
+ private readonly Dictionary _reverseLookup = [];
private readonly List _values;
private Dictionary? _associativeValues;
@@ -29,6 +33,12 @@ public DreamList(DreamObjectDefinition listDef, int size) : base(listDef) {
if (size >= DreamManager.ListPoolThreshold && ListPool.TryPop(out var poppedValues)) {
_values = poppedValues;
_values.EnsureCapacity(size);
+ foreach (var value in poppedValues) {
+ if (!_reverseLookup.TryAdd(value, 1)) {
+ _reverseLookup[value] += 1;
+ }
+ }
+
} else {
_values = new List(size);
}
@@ -39,6 +49,11 @@ public DreamList(DreamObjectDefinition listDef, int size) : base(listDef) {
///
public DreamList(DreamObjectDefinition listDef, List values, Dictionary? associativeValues) : base(listDef) {
_values = values;
+ foreach (var value in values) {
+ if (!_reverseLookup.TryAdd(value, 1)) {
+ _reverseLookup[value] += 1;
+ }
+ }
_associativeValues = associativeValues;
#if TOOLS
@@ -172,13 +187,31 @@ public virtual DreamValue GetValue(DreamValue key) {
public virtual void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
if (key.TryGetValueAsInteger(out int keyInteger)) {
- if (allowGrowth && keyInteger == _values.Count + 1) {
+ var index = _values.Count + 1;
+ if (allowGrowth && keyInteger == index) {
_values.Add(value);
+
+ if (!_reverseLookup.TryAdd(value, 1)) {
+ _reverseLookup[value] += 1;
+ }
} else {
+ var oldValue = _values[keyInteger - 1];
+ var rLCount = _reverseLookup[oldValue] -= 1;
+ if(rLCount <= 0) {
+ _reverseLookup.Remove(oldValue);
+ }
+
_values[keyInteger - 1] = value;
+
+ if (!_reverseLookup.TryAdd(value, 1)) {
+ _reverseLookup[value] += 1;
+ }
+
}
} else {
- if (!ContainsValue(key)) _values.Add(key);
+ if (_reverseLookup.TryAdd(key, 1)) {
+ _values.Add(key);
+ }
_associativeValues ??= new Dictionary(1);
_associativeValues[key] = value;
@@ -191,6 +224,13 @@ public virtual void RemoveValue(DreamValue value) {
int valueIndex = _values.LastIndexOf(value);
if (valueIndex != -1) {
+ if (_reverseLookup.ContainsKey(value)) {
+ var rLCount = _reverseLookup[value] -= 1;
+ if (rLCount <= 0) {
+ _reverseLookup.Remove(value);
+ }
+ }
+
_associativeValues?.Remove(value);
_values.RemoveAt(valueIndex);
}
@@ -199,18 +239,17 @@ public virtual void RemoveValue(DreamValue value) {
}
public virtual void AddValue(DreamValue value) {
- _values.Add(value);
+ _values.Add(value);
+ if (!_reverseLookup.TryAdd(value, 1)) {
+ _reverseLookup[value] += 1;
+ }
+
UpdateTracyContentsMemory();
}
//Does not include associations
public virtual bool ContainsValue(DreamValue value) {
- for (int i = 0; i < _values.Count; i++) {
- if (_values[i].Equals(value))
- return true;
- }
-
- return false;
+ return _reverseLookup.ContainsKey(value);
}
public virtual bool ContainsKey(DreamValue value) {
@@ -220,6 +259,8 @@ public virtual bool ContainsKey(DreamValue value) {
public virtual int FindValue(DreamValue value, int start = 1, int end = 0) {
if (end == 0 || end > _values.Count) end = _values.Count + 1;
+ if(!ContainsValue(value)) return 0;
+
for (int i = start; i < end; i++) {
if (_values[i - 1].Equals(value)) return i;
}
@@ -235,14 +276,30 @@ public virtual void Cut(int start = 1, int end = 0) {
_associativeValues.Remove(_values[i - 1]);
}
- if (end > start)
- _values.RemoveRange(start - 1, end - start);
+ if (end > start) {
+ var index = start - 1;
+ var len = end - start;
+ var elements = _values.GetRange(index, len);
+
+ foreach (var element in elements) {
+ var rlCache = _reverseLookup[element] -= 1;
+
+ if (rlCache <= 0) {
+ _reverseLookup.Remove(element);
+ }
+ }
+
+ _values.RemoveRange(index, len);
+ }
UpdateTracyContentsMemory();
}
public void Insert(int index, DreamValue value) {
_values.Insert(index - 1, value);
+ if (!_reverseLookup.TryAdd(value, 1)) {
+ _reverseLookup[value] += 1;
+ }
UpdateTracyContentsMemory();
}
@@ -652,6 +709,16 @@ public override IEnumerable EnumerateValues() {
yield return new(verb);
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
throw new Exception("Cannot set the values of a verbs list");
}
@@ -716,6 +783,16 @@ public override IEnumerable EnumerateValues() {
}
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
throw new Exception("Cannot set the values of a verbs list");
}
@@ -790,6 +867,16 @@ public override IEnumerable EnumerateValues() {
}
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void Cut(int start = 1, int end = 0) {
_atomManager.UpdateAppearance(_owner, appearance => {
var overlaysList = GetOverlaysList(appearance);
@@ -907,6 +994,16 @@ public override IEnumerable EnumerateValues() {
yield return new(visContent);
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void Cut(int start = 1, int end = 0) {
int count = _visContents.Count + 1;
if (end == 0 || end > count) end = count;
@@ -1064,6 +1161,16 @@ public override IEnumerable EnumerateValues() {
}
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
if (!value.TryGetValueAsDreamObject(out var filterObject) && !value.IsNull)
throw new Exception($"Cannot set value of filter list to {value}");
@@ -1212,6 +1319,16 @@ public override IEnumerable EnumerateValues() {
return _imageObjects;
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
throw new Exception("Cannot write to an index of a client images list");
}
@@ -1275,6 +1392,16 @@ public override IEnumerable EnumerateValues() {
return AtomManager.EnumerateAtoms().Select(atom => new DreamValue(atom));
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
throw new Exception("Cannot set the value of world contents list");
}
@@ -1318,6 +1445,16 @@ public override IEnumerable EnumerateValues() {
yield return new(movable);
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
throw new Exception("Cannot set an index of turf contents list");
}
@@ -1386,6 +1523,16 @@ public override IEnumerable EnumerateValues() {
}
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
throw new Exception("Cannot set an index of area contents list");
}
@@ -1537,6 +1684,16 @@ public override IEnumerable EnumerateValues() {
yield return state.GetArguments()[i];
}
+ public override bool ContainsValue(DreamValue value) {
+ foreach (var containedVal in EnumerateValues()) {
+ if (value.Equals(containedVal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) {
if (!key.TryGetValueAsInteger(out var index))
throw new Exception($"Invalid index into args list: {key}");