From d919f71149e25c4c59263d96f84865f839c89553 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Thu, 26 Oct 2023 17:49:15 -0700
Subject: [PATCH] fix: Fixes map iterators for Items (#1564)
### Summary
Modifying a ValueLinkList using one of the methods will bump the "version". This field is used by iterators (foreach loops) to determine if the link list was modified while iterating. The sector.Items (and in the future other lists), will no longer be safe to modify while iterating. The server will _CRASH_ if the ValueLinkList is modified.
Thanks to @stefanomerotta for help!
### Screenshots
---
.../Tests/Collections/ValueLinkListTests.cs | 300 +++++++++++++++---
Projects/Server/Collections/ValueLinkList.cs | 141 +++++---
Projects/Server/IEntity.cs | 14 +
Projects/Server/Items/Item.cs | 17 +-
Projects/Server/Maps/Map.ItemEnumerator.cs | 201 ++++++------
Projects/Server/Maps/Map.cs | 36 +--
Projects/Server/Maps/PooledEnumeration.cs | 1 +
Projects/Server/Mobiles/Mobile.cs | 173 ++++++----
.../Commands/Object Creation/GenTeleporter.cs | 13 +-
Projects/UOContent/Commands/SignParser.cs | 10 +-
.../Engines/ConPVP/Games/BombingRun.cs | 4 +-
.../UOContent/Engines/Doom/GenGauntlet.cs | 6 +-
.../Doom/LeverPuzzle/LeverPuzzleController.cs | 2 +-
.../Engines/Factions/Core/Generator.cs | 2 +-
.../Factions/Items/Traps/BaseFactionTrap.cs | 2 +-
.../Engines/Harvest/Core/HarvestSystem.cs | 2 +-
.../UOContent/Engines/Khaldun/KhaldunGen.cs | 10 +-
.../UOContent/Engines/ML Quests/MLQuest.cs | 4 +-
.../UOContent/Engines/Pathing/Movement.cs | 158 ++-------
.../Quests/The Summoning/Mobiles/Victoria.cs | 8 +-
.../Commands/GenerateSpawnersCommand.cs | 2 +-
.../Christmas/2010/Addons/FireFliesDeed.cs | 4 +-
.../UOContent/Items/Addons/SHTeleporter.cs | 2 +-
.../Items/Containers/MarkContainer.cs | 2 +-
Projects/UOContent/Items/Misc/OilFlask.cs | 2 +-
Projects/UOContent/Items/Misc/WarningItem.cs | 15 +-
Projects/UOContent/Mobiles/AI/BaseAI.cs | 17 +-
.../ML/Humanoid/Magic/InterredGrizzle .cs | 2 +-
.../Mobiles/Monsters/ML/Special/Ilhenir.cs | 2 +-
.../UOContent/Multis/Houses/HousePlacement.cs | 6 +-
.../UOContent/Spells/Seventh/GateTravel.cs | 2 +-
.../Spells/Spellweaving/ArcaneCircle.cs | 2 +-
Projects/UOContent/Spells/Third/Teleport.cs | 12 +-
33 files changed, 706 insertions(+), 468 deletions(-)
diff --git a/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs b/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs
index d96ba57d8..9683c8712 100644
--- a/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs
+++ b/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs
@@ -1,3 +1,4 @@
+using System;
using Server.Collections;
using Xunit;
@@ -32,20 +33,35 @@ public class ValueLinkListTests
Assert.True(entity1.OnLinkList);
Assert.Equal(1, linkList.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity1, linkList.Last);
+
+ Assert.Collection(linkList.ToArray(), item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Null(item.Next);
+ }
+ );
var entity2 = new TestEntity(2);
-
linkList.AddFirst(entity2);
Assert.True(entity2.OnLinkList);
Assert.Equal(2, linkList.Count);
- Assert.Equal(entity1, linkList.Last);
- Assert.Equal(entity2, entity1.Previous);
- Assert.Equal(entity2, linkList.First);
- Assert.Equal(entity1, entity2.Next);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity1);
+ },
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Equal(item.Previous, entity2);
+ Assert.Null(item.Next);
+ }
+ );
}
[Fact]
@@ -58,8 +74,14 @@ public class ValueLinkListTests
Assert.True(entity1.OnLinkList);
Assert.Equal(1, linkList.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity1, linkList.Last);
+
+ Assert.Collection(linkList.ToArray(), item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Null(item.Next);
+ }
+ );
var entity2 = new TestEntity(2);
@@ -67,11 +89,21 @@ public class ValueLinkListTests
Assert.True(entity2.OnLinkList);
Assert.Equal(2, linkList.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity2, entity1.Next);
- Assert.Equal(entity2, linkList.Last);
- Assert.Equal(entity1, entity2.Previous);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity2);
+ },
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Equal(item.Previous, entity1);
+ Assert.Null(item.Next);
+ }
+ );
}
[Fact]
@@ -87,20 +119,48 @@ public class ValueLinkListTests
Assert.True(entity1.OnLinkList);
Assert.True(entity2.OnLinkList);
Assert.Equal(2, linkList.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity2, linkList.Last);
+
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity2);
+ },
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Equal(item.Previous, entity1);
+ Assert.Null(item.Next);
+ }
+ );
+
var entity3 = new TestEntity(3);
linkList.AddBefore(entity2, entity3);
Assert.True(entity3.OnLinkList);
Assert.Equal(3, linkList.Count);
- Assert.Equal(entity1, entity3.Previous);
- Assert.Equal(entity2, entity3.Next);
-
- // First and Last should not have changed
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity2, linkList.Last);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity3);
+ },
+ item =>
+ {
+ Assert.Equal(entity3, item);
+ Assert.Equal(item.Previous, entity1);
+ Assert.Equal(item.Next, entity2);
+ },
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Equal(item.Previous, entity3);
+ Assert.Null(item.Next);
+ }
+ );
}
[Fact]
@@ -113,23 +173,31 @@ public class ValueLinkListTests
linkList.AddFirst(entity1);
linkList.AddLast(entity2);
- Assert.True(entity1.OnLinkList);
- Assert.True(entity2.OnLinkList);
- Assert.Equal(2, linkList.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity2, linkList.Last);
-
var entity3 = new TestEntity(3);
linkList.AddAfter(entity1, entity3);
Assert.True(entity3.OnLinkList);
Assert.Equal(3, linkList.Count);
- Assert.Equal(entity1, entity3.Previous);
- Assert.Equal(entity2, entity3.Next);
-
- // First and Last should not have changed
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity2, linkList.Last);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity3);
+ },
+ item =>
+ {
+ Assert.Equal(entity3, item);
+ Assert.Equal(item.Previous, entity1);
+ Assert.Equal(item.Next, entity2);
+ },
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Equal(item.Previous, entity3);
+ Assert.Null(item.Next);
+ }
+ );
}
[Fact]
@@ -157,12 +225,47 @@ public class ValueLinkListTests
Assert.Equal(1, linkList.Count);
Assert.Equal(5, linkList2.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity1, linkList.Last);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Null(item.Next);
+ }
+ );
- Assert.Equal(entity2, entity6.Next);
- Assert.Equal(entity6, entity2.Previous);
- Assert.Equal(entity4, linkList2.Last);
+ Assert.Collection(linkList2.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity5, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity6);
+ },
+ item =>
+ {
+ Assert.Equal(entity6, item);
+ Assert.Equal(item.Previous, entity5);
+ Assert.Equal(item.Next, entity2);
+ },
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Equal(item.Previous, entity6);
+ Assert.Equal(item.Next, entity3);
+ },
+ item =>
+ {
+ Assert.Equal(entity3, item);
+ Assert.Equal(item.Previous, entity2);
+ Assert.Equal(item.Next, entity4);
+ },
+ item =>
+ {
+ Assert.Equal(entity4, item);
+ Assert.Equal(item.Previous, entity3);
+ Assert.Null(item.Next);
+ }
+ );
}
[Fact]
@@ -184,10 +287,20 @@ public class ValueLinkListTests
linkList.RemoveAllBefore(entity4);
Assert.Equal(2, linkList.Count);
- Assert.Equal(entity4, linkList.First);
- Assert.Equal(entity5, linkList.Last);
- Assert.Null(entity4.Previous);
- Assert.Null(entity5.Next);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity4, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity5);
+ },
+ item =>
+ {
+ Assert.Equal(entity5, item);
+ Assert.Equal(item.Previous, entity4);
+ Assert.Null(item.Next);
+ }
+ );
}
[Fact]
@@ -209,9 +322,104 @@ public class ValueLinkListTests
linkList.RemoveAllAfter(entity2);
Assert.Equal(2, linkList.Count);
- Assert.Equal(entity1, linkList.First);
- Assert.Equal(entity2, linkList.Last);
- Assert.Null(entity1.Previous);
- Assert.Null(entity2.Next);
+ Assert.Collection(linkList.ToArray(),
+ item =>
+ {
+ Assert.Equal(entity1, item);
+ Assert.Null(item.Previous);
+ Assert.Equal(item.Next, entity2);
+ },
+ item =>
+ {
+ Assert.Equal(entity2, item);
+ Assert.Equal(item.Previous, entity1);
+ Assert.Null(item.Next);
+ }
+ );
+ }
+
+ [Fact]
+ public void TestVersionIncrements()
+ {
+ var linkList = new ValueLinkList();
+
+ var entity1 = new TestEntity(1);
+ var entity2 = new TestEntity(2);
+ var entity3 = new TestEntity(3);
+ var entity4 = new TestEntity(4);
+
+ var version = 0;
+ Assert.Equal(version, linkList.Version);
+
+ linkList.AddFirst(entity1); // 1
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.AddLast(entity2); // 1, 2
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.AddBefore(entity2, entity3); // 1, 3, 2
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.AddAfter(entity3, entity4); // 1, 3, 4, 2
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.Remove(entity1); // 3, 4, 2
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.RemoveAllAfter(entity4); // 3, 4
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.RemoveAllBefore(entity4); // 4
+ Assert.Equal(++version, linkList.Version);
+
+ linkList.RemoveAll(); // None
+ Assert.Equal(++version, linkList.Version);
+ }
+
+ [Fact]
+ public void TestThrowsIfModifiedWhileIterating()
+ {
+ Assert.Throws(
+ () =>
+ {
+ var linkList = new ValueLinkList();
+
+ var entity1 = new TestEntity(1);
+ var entity2 = new TestEntity(2);
+
+ linkList.AddFirst(entity1);
+ linkList.AddLast(entity2);
+
+ foreach (var item in linkList)
+ {
+ linkList.Remove(item);
+ }
+ }
+ );
+ }
+
+ [Fact]
+ public void TestThrowsIfModifiedWhileIteratingNested()
+ {
+ Assert.Throws(
+ () =>
+ {
+ var linkList = new ValueLinkList();
+
+ var entity1 = new TestEntity(1);
+ var entity2 = new TestEntity(2);
+
+ linkList.AddFirst(entity1);
+ linkList.AddLast(entity2);
+
+ foreach (var item in linkList)
+ {
+ foreach (var nestedItem in linkList)
+ {
+ linkList.Remove(nestedItem);
+ }
+ }
+ }
+ );
}
}
diff --git a/Projects/Server/Collections/ValueLinkList.cs b/Projects/Server/Collections/ValueLinkList.cs
index 00727ab0a..0accf691f 100644
--- a/Projects/Server/Collections/ValueLinkList.cs
+++ b/Projects/Server/Collections/ValueLinkList.cs
@@ -28,8 +28,10 @@ public interface IValueLinkListNode where T : class
public struct ValueLinkList where T : class, IValueLinkListNode
{
public int Count { get; internal set; }
- public T First { get; internal set; }
- public T Last { get; internal set; }
+ internal T _first;
+ internal T _last;
+
+ public int Version { get; private set; }
public void Remove(T node)
{
@@ -46,19 +48,19 @@ public struct ValueLinkList where T : class, IValueLinkListNode
if (node.Previous == null)
{
// If previous is null, then it is the first element.
- if (First != node)
+ if (_first != node)
{
throw new ArgumentException("Attempted to remove a node that is not on the list.");
}
- if (First == Last)
+ if (_first == _last)
{
- Last = null;
- First = null;
+ _last = null;
+ _first = null;
}
else
{
- First = node.Next;
+ _first = node.Next;
}
if (node.Next != null)
@@ -73,7 +75,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode
// If next is null, then it is the last element.
if (node.Next == null)
{
- Last = node.Previous;
+ _last = node.Previous;
}
else
{
@@ -85,6 +87,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode
node.Previous = null;
node.OnLinkList = false;
Count--;
+ Version++;
if (Count < 0)
{
@@ -130,7 +133,8 @@ public struct ValueLinkList where T : class, IValueLinkListNode
current = previous;
}
- First = e;
+ _first = e;
+ Version++;
}
// Remove all entries after this node, not including this node.
@@ -171,7 +175,8 @@ public struct ValueLinkList where T : class, IValueLinkListNode
current = next;
}
- Last = e;
+ _last = e;
+ Version++;
}
public void AddLast(T e)
@@ -186,15 +191,16 @@ public struct ValueLinkList where T : class, IValueLinkListNode
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
- if (Last != null)
+ if (_last != null)
{
- AddAfter(Last, e);
+ AddAfter(_last, e);
}
else
{
- First = e;
- Last = e;
+ _first = e;
+ _last = e;
Count = 1;
+ Version++;
e.OnLinkList = true;
}
}
@@ -211,15 +217,16 @@ public struct ValueLinkList where T : class, IValueLinkListNode
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
- if (First != null)
+ if (_first != null)
{
- AddBefore(First, e);
+ AddBefore(_first, e);
}
else
{
- First = e;
- Last = e;
+ _first = e;
+ _last = e;
Count = 1;
+ Version++;
e.OnLinkList = true;
}
}
@@ -252,12 +259,13 @@ public struct ValueLinkList where T : class, IValueLinkListNode
}
else
{
- First = node;
+ _first = node;
}
existing.Previous = node;
node.OnLinkList = true;
Count++;
+ Version++;
}
public void AddAfter(T existing, T node)
@@ -288,17 +296,18 @@ public struct ValueLinkList where T : class, IValueLinkListNode
}
else
{
- Last = node;
+ _last = node;
}
existing.Next = node;
node.OnLinkList = true;
Count++;
+ Version++;
}
public void RemoveAll()
{
- var current = First;
+ var current = _first;
while (current != null)
{
var next = current.Next;
@@ -309,15 +318,16 @@ public struct ValueLinkList where T : class, IValueLinkListNode
current = next;
}
- First = null;
- Last = null;
+ _first = null;
+ _last = null;
Count = 0;
+ Version++;
}
public void AddLast(ref ValueLinkList otherList, T start, T end)
{
// Should we check if start and end actually exist on the other list?
- if (otherList.Count == 0 || otherList.Count == 1 && (start != end || otherList.First != start))
+ if (otherList.Count == 0 || otherList.Count == 1 && (start != end || otherList._first != start))
{
throw new ArgumentException("Attempted to add nodes that are not on the specified linklist.");
}
@@ -329,7 +339,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode
else
{
// Start is first
- otherList.First = end.Next;
+ otherList._first = end.Next;
}
if (end.Next != null)
@@ -338,7 +348,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode
}
else
{
- otherList.Last = start.Previous;
+ otherList._last = start.Previous;
}
var count = 1;
@@ -358,45 +368,62 @@ public struct ValueLinkList where T : class, IValueLinkListNode
throw new Exception("Count is negative!");
}
- if (Last != null)
+ if (_last != null)
{
- Last.Next = start;
- start.Previous = Last;
+ _last.Next = start;
+ start.Previous = _last;
}
else
{
- First = start;
+ _first = start;
}
- Last = end;
+ _last = end;
Count += count;
+ Version++;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ValueListEnumerator GetEnumerator() => new(First);
+ public T[] ToArray()
+ {
+ var arr = new T[Count];
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public DescendingValueListEnumerator ByDescending() => new(Last);
+ var index = 0;
+ foreach (var t in this)
+ {
+ arr[index++] = t;
+ }
+
+ return arr;
+ }
public ref struct ValueListEnumerator
{
- private T _head;
+ private bool _started;
private T _current;
+ private ref readonly ValueLinkList _linkList;
+ private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ValueListEnumerator(T head)
+ public ValueListEnumerator(in ValueLinkList linkList)
{
- _head = head;
+ _linkList = ref linkList;
+ _started = false;
_current = null;
+ _version = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
- if (_current == null)
+ if (!_started)
{
- _current = _head;
- _head = null;
+ _current = _linkList._first;
+ _started = true;
+ _version = _linkList.Version;
+ }
+ else if (_linkList.Version != _version)
+ {
+ throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
else
{
@@ -415,23 +442,32 @@ public struct ValueLinkList where T : class, IValueLinkListNode
public ref struct DescendingValueListEnumerator
{
- private T _tail;
+ private bool _started;
private T _current;
+ private ref readonly ValueLinkList _linkList;
+ private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public DescendingValueListEnumerator(T head)
+ public DescendingValueListEnumerator(in ValueLinkList linkList)
{
- _tail = head;
+ _linkList = ref linkList;
+ _started = false;
_current = null;
+ _version = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
- if (_current == null)
+ if (!_started)
{
- _current = _tail;
- _tail = null;
+ _current = _linkList._last;
+ _started = true;
+ _version = _linkList.Version;
+ }
+ else if (_linkList.Version != _version)
+ {
+ throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
else
{
@@ -451,3 +487,14 @@ public struct ValueLinkList where T : class, IValueLinkListNode
public DescendingValueListEnumerator GetEnumerator() => this;
}
}
+
+public static class ValueLinkListExt
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ValueLinkList.ValueListEnumerator GetEnumerator(this in ValueLinkList linkList)
+ where T : class, IValueLinkListNode => new(in linkList);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ValueLinkList.DescendingValueListEnumerator ByDescending(this in ValueLinkList linkList)
+ where T : class, IValueLinkListNode => new(in linkList);
+}
diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs
index d34fdaa14..511748427 100644
--- a/Projects/Server/IEntity.cs
+++ b/Projects/Server/IEntity.cs
@@ -30,6 +30,12 @@ public interface IEntity : IPoint3D, ISerializable
bool InRange(Point3D p, int range);
void RemoveItem(Item item);
+
+ bool OnMoveOff(Mobile m);
+
+ bool OnMoveOver(Mobile m);
+
+ public void OnMovement(Mobile m, Point3D oldLocation);
}
public class Entity : IEntity
@@ -83,6 +89,14 @@ public class Entity : IEntity
{
}
+ public bool OnMoveOff(Mobile m) => true;
+
+ public bool OnMoveOver(Mobile m) => true;
+
+ public void OnMovement(Mobile m, Point3D oldLocation)
+ {
+ }
+
public bool InRange(Point2D p, int range) =>
p.m_X >= Location.m_X - range
&& p.m_X <= Location.m_X + range
diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs
index 4875452dc..faf042d15 100644
--- a/Projects/Server/Items/Item.cs
+++ b/Projects/Server/Items/Item.cs
@@ -2241,7 +2241,6 @@ public class Item : IHued, IComparable- , ISpawnable, IObjectPropertyListEnt
public virtual bool CanDecay() => Decays && Parent == null && Map != Map.Internal;
-
public virtual bool OnDecay() =>
CanDecay() && Region.Find(Location, Map).OnDecay(this);
@@ -2474,12 +2473,20 @@ public class Item : IHued, IComparable
- , ISpawnable, IObjectPropertyListEnt
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Map.ItemEnumerable
- GetItemsInRange(int range) =>
- m_Map == null ? Map.ItemEnumerable
- .Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
+ public Map.ItemAtEnumerable
- GetItemsAt() =>
+ m_Map == null ? Map.ItemAtEnumerable
- .Empty : m_Map.GetItemsAt(m_Parent == null ? m_Location : GetWorldLocation());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Map.ItemEnumerable GetItemsInRange(int range) where T : Item =>
- m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
+ public Map.ItemAtEnumerable GetItemsAt() where T : Item =>
+ m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Parent == null ? m_Location : GetWorldLocation());
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Map.ItemBoundsEnumerable
- GetItemsInRange(int range) =>
+ m_Map == null ? Map.ItemBoundsEnumerable
- .Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Map.ItemBoundsEnumerable GetItemsInRange(int range) where T : Item =>
+ m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
public IPooledEnumerable GetMobilesInRange(int range)
{
diff --git a/Projects/Server/Maps/Map.ItemEnumerator.cs b/Projects/Server/Maps/Map.ItemEnumerator.cs
index 5be8c34a7..69fa938cf 100644
--- a/Projects/Server/Maps/Map.ItemEnumerator.cs
+++ b/Projects/Server/Maps/Map.ItemEnumerator.cs
@@ -13,140 +13,159 @@
* along with this program. If not, see . *
*************************************************************************/
-using System.Collections.Generic;
+using System;
using System.Runtime.CompilerServices;
+using Server.Collections;
namespace Server;
public partial class Map
{
- private int _iteratingItems;
- private readonly List<(MapAction, Point3D, Item)> _delayedItemActions = new();
-
- public bool IsIteratingItems
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _iteratingItems > 0;
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemAtEnumerable
- GetItemsAt(Point3D p) => GetItemsAt
- (p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsAt(Point3D p) => GetItemsInRange(p, 0);
+ public ItemAtEnumerable GetItemsAt(Point3D p) where T : Item => GetItemsAt(new Point2D(p.X, p.Y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsAt(Point3D p) where T : Item => GetItemsInRange(p, 0);
+ public ItemAtEnumerable
- GetItemsAt(int x, int y) => GetItemsAt
- (new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsInRange(Point3D p) => GetItemsInRange
- (p);
+ public ItemAtEnumerable GetItemsAt(int x, int y) where T : Item => GetItemsAt(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsInRange(Point3D p, int range) => GetItemsInRange
- (p, range);
+ public ItemAtEnumerable
- GetItemsAt(Point2D p) => GetItemsAt
- (p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsInRange(Point3D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange);
+ public ItemAtEnumerable GetItemsAt(Point2D p) where T : Item => new(this, p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsInRange(Point3D p, int range) where T : Item =>
+ public ItemBoundsEnumerable
- GetItemsInRange(Point3D p) => GetItemsInRange
- (p);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemBoundsEnumerable
- GetItemsInRange(Point3D p, int range) => GetItemsInRange
- (p, range);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemBoundsEnumerable GetItemsInRange(Point3D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemBoundsEnumerable GetItemsInRange(Point3D p, int range) where T : Item =>
GetItemsInRange(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsAt(Point2D p) => GetItemsInRange(p, 0);
+ public ItemBoundsEnumerable
- GetItemsInRange(Point2D p) => GetItemsInRange
- (p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsAt(Point2D p) where T : Item => GetItemsInRange(p, 0);
+ public ItemBoundsEnumerable
- GetItemsInRange(Point2D p, int range) => GetItemsInRange
- (p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsInRange(Point2D p) => GetItemsInRange
- (p);
+ public ItemBoundsEnumerable GetItemsInRange(Point2D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsInRange(Point2D p, int range) => GetItemsInRange
- (p, range);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsInRange(Point2D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsInRange(Point2D p, int range) where T : Item =>
+ public ItemBoundsEnumerable GetItemsInRange(Point2D p, int range) where T : Item =>
GetItemsInRange(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsAt(int x, int y) => GetItemsAt
- (x, y);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsAt(int x, int y) where T : Item => GetItemsInRange(x, y, 0);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsInRange(int x, int y, int range) where T : Item =>
+ public ItemBoundsEnumerable GetItemsInRange(int x, int y, int range) where T : Item =>
GetItemsInBounds(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable
- GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds
- (bounds);
+ public ItemBoundsEnumerable
- GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds
- (bounds);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ItemEnumerable GetItemsInBounds(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item =>
+ public ItemBoundsEnumerable GetItemsInBounds(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item =>
new(this, bounds, makeBoundsInclusive);
- private void BeginIteratingItems()
+ public ref struct ItemAtEnumerable where T : Item
{
-#if THREADGUARD
- if (Thread.CurrentThread != Core.Thread)
- {
- Utility.PushColor(ConsoleColor.Red);
- Console.WriteLine($"Iterating through items on {this} from an invalid thread!");
- Console.WriteLine(new StackTrace());
- Utility.PopColor();
- return;
- }
-#endif
+ public static ItemAtEnumerable Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new();
+ }
- _iteratingItems++;
+ private Map _map;
+ private Point2D _location;
+
+ public ItemAtEnumerable(Map map, Point2D loc)
+ {
+ _map = map;
+ _location = loc;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemAtEnumerator GetEnumerator() => new(_map, _location);
}
- private void EndIteratingItems()
+ public ref struct ItemAtEnumerator where T : Item
{
-#if THREADGUARD
- if (Thread.CurrentThread != Core.Thread)
- {
- Utility.PushColor(ConsoleColor.Red);
- Console.WriteLine($"Iterating through items on {this} from an invalid thread!");
- Console.WriteLine(new StackTrace());
- Utility.PopColor();
- return;
- }
-#endif
+ private bool _started;
+ private Point2D _location;
+ private ref readonly ValueLinkList
- _linkList;
+ private int _version;
+ private T _current;
- _iteratingItems--;
-
- // Finished iterating, check for deferred actions
- if (_iteratingItems == 0 && _delayedItemActions.Count > 0)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemAtEnumerator(Map map, Point2D loc)
{
- foreach (var (a, p, i) in _delayedItemActions)
+ _started = false;
+ _location = loc;
+ _linkList = ref map.GetRealSector(loc.m_X, loc.m_Y).Items;
+ _version = 0;
+ _current = null;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ ref var loc = ref _location;
+ Item current;
+
+ if (!_started)
{
- switch (a)
+ current = _linkList._first;
+ _started = true;
+ _version = _linkList.Version;
+
+ if (current is T { Deleted: false, Parent: null } o && o.X == loc.m_X && o.Y == loc.m_Y)
{
- case MapAction.Enter:
- {
- OnEnter(p, i);
- break;
- }
- case MapAction.Leave:
- {
- OnLeave(p, i);
- break;
- }
- case MapAction.Move:
- {
- OnMove(p, i);
- break;
- }
+ _current = o;
+ return true;
+ }
+ }
+ else if (_linkList.Version != _version)
+ {
+ throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
+ }
+ else
+ {
+ current = _current;
+ }
+
+ while (current != null)
+ {
+ current = current.Next;
+
+ if (current is T { Deleted: false, Parent: null } o && o.X == loc.m_X && o.Y == loc.m_Y)
+ {
+ _current = o;
+ return true;
}
}
- _delayedItemActions.Clear();
+ return false;
+ }
+
+ public T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _current;
}
}
- public ref struct ItemEnumerable where T : Item
+ public ref struct ItemBoundsEnumerable where T : Item
{
- public static ItemEnumerable Empty
+ public static ItemBoundsEnumerable Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(null, Rectangle2D.Empty, false);
@@ -156,14 +175,13 @@ public partial class Map
private Rectangle2D _bounds;
private bool _makeBoundsInclusive;
- public ItemEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
+ public ItemBoundsEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;
_makeBoundsInclusive = makeBoundsInclusive;
}
- // The enumerator MUST be disposed. Not disposing it will damage the sector irreparably.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerator GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
}
@@ -178,6 +196,9 @@ public partial class Map
private int _currentSectorX;
private int _currentSectorY;
+
+ private ref readonly ValueLinkList
- _linkList;
+ private int _currentVersion;
private T _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -199,8 +220,6 @@ public partial class Map
// We start the X sector one short because it gets incremented immediately in MoveNext()
_currentSectorX = _sectorStartX - 1;
_currentSectorY = _sectorStartY;
-
- _map.BeginIteratingItems();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -242,7 +261,14 @@ public partial class Map
return false;
}
- current = map.GetRealSector(currentSectorX, currentSectorY).Items.First;
+ _linkList = ref map.GetRealSector(currentSectorX, currentSectorY).Items;
+ _currentVersion = _linkList.Version;
+ current = _linkList._first;
+ }
+
+ if (_linkList.Version != _currentVersion)
+ {
+ throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
if (current is T { Deleted: false, Parent: null } o && bounds.Contains(o.Location))
@@ -253,9 +279,6 @@ public partial class Map
}
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Dispose() => _map.EndIteratingItems();
-
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Projects/Server/Maps/Map.cs b/Projects/Server/Maps/Map.cs
index 99f2b2928..6a5d5be1a 100644
--- a/Projects/Server/Maps/Map.cs
+++ b/Projects/Server/Maps/Map.cs
@@ -627,12 +627,6 @@ public sealed partial class Map : IComparable