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}");