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, ISpanFormattable, ISpanParsa public void OnEnter(Item item) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Enter, item.Location, item)); - return; - } - OnEnter(item.Location, item); } @@ -669,12 +663,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public void OnLeave(Item item) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Leave, item.Location, item)); - return; - } - OnLeave(item.Location, item); } @@ -765,12 +753,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa if (oldSector != newSector) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Move, item.Location, item)); - return; - } - oldSector.OnLeave(item); newSector.OnEnter(item); } @@ -787,12 +769,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa if (oldStart != start || oldEnd != end) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Move, oldLocation, item)); - return; - } - RemoveMulti(m, oldStart, oldEnd); AddMulti(m, start, end); } @@ -1038,8 +1014,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); - // public Sector GetSector(IPoint2D p) => InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); - public Sector GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift); public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); @@ -1459,14 +1433,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa return false; } - private enum MapAction - { - None, - Enter, - Leave, - Move - } - public class Sector { // TODO: Can we avoid this? @@ -1495,7 +1461,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public List Mobiles => _mobiles ?? m_DefaultMobileList; - public ref ValueLinkList Items => ref _items; + internal ref readonly ValueLinkList Items => ref _items; public List Clients => _clients ?? m_DefaultClientList; diff --git a/Projects/Server/Maps/PooledEnumeration.cs b/Projects/Server/Maps/PooledEnumeration.cs index 8f5eeb818..ef718e30b 100644 --- a/Projects/Server/Maps/PooledEnumeration.cs +++ b/Projects/Server/Maps/PooledEnumeration.cs @@ -17,6 +17,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Server.Collections; using Server.Items; using Server.Network; diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 60a4dd9e7..4b3ff0eab 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -194,9 +194,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0); private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0); - private static readonly List m_MoveList = new(); - private static readonly List m_MoveClientList = new(); - private static readonly object m_GhostMutateContext = new(); private static readonly List m_Hears = new(); @@ -3567,6 +3564,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public bool InLOS(Point3D target) => !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); + public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; + public bool BeginAction() => BeginAction(typeof(T)); public bool BeginAction(object toLock) @@ -4154,12 +4153,20 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro if (oldSector != newSector) { + using var queue = PooledRefQueue.Create(2048); for (var i = 0; i < oldSector.Mobiles.Count; ++i) { var m = oldSector.Mobiles[i]; - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOff(this)) { return false; } @@ -4168,8 +4175,15 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro foreach (var item in oldSector.Items) { if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z)) + { + queue.Enqueue(item); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOff(this)) { return false; } @@ -4179,7 +4193,15 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro { var m = newSector.Mobiles[i]; - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOver(this)) { return false; } @@ -4188,8 +4210,15 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro foreach (var item in newSector.Items) { if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z)) + { + queue.Enqueue(item); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOver(this)) { return false; } @@ -4197,17 +4226,36 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } else { + using var queue = PooledRefQueue<(IEntity, byte)>.Create(2048); for (var i = 0; i < oldSector.Mobiles.Count; ++i) { var m = oldSector.Mobiles[i]; - - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) + byte flag; + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z) { - return false; + flag = 1; + } + else + { + flag = 0; } - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z) + { + flag += 2; + } + + if (flag > 0) + { + queue.Enqueue((m, flag)); + } + } + + while (queue.Count > 0) + { + var (entity, flag) = queue.Dequeue(); + + if (flag > 0 && !entity.OnMoveOff(this) || flag > 1 && !entity.OnMoveOver(this)) { return false; } @@ -4215,16 +4263,34 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro foreach (var item in oldSector.Items) { + byte flag; if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z)) { - return false; + flag = 1; + } + else + { + flag = 0; } if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z)) + { + flag += 2; + } + + if (flag > 0) + { + queue.Enqueue((item, flag)); + } + } + + while (queue.Count > 0) + { + var (entity, flag) = queue.Dequeue(); + + if (flag > 0 && !entity.OnMoveOff(this) || flag > 1 && !entity.OnMoveOver(this)) { return false; } @@ -4281,6 +4347,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro if (m_Map != null) { + using var moveQueue = PooledRefQueue.Create(2048); + using var moveClientQueue = PooledRefQueue.Create(2048); var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); foreach (var o in eable) @@ -4294,54 +4362,39 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro { if (mob.NetState != null) { - m_MoveClientList.Add(mob); + moveClientQueue.Enqueue(mob); } - m_MoveList.Add(mob); + moveQueue.Enqueue(mob); } else if (o is Item item && item.HandlesOnMovement) { - m_MoveList.Add(item); + moveQueue.Enqueue(item); } } - const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; - const int width = OutgoingMobilePackets.MobileMovingPacketLength; - - var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); - - foreach (var m in m_MoveClientList) + if (moveClientQueue.Count > 0) { - var ns = m.NetState; + const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; + const int width = OutgoingMobilePackets.MobileMovingPacketLength; - if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) + var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); + + while (moveClientQueue.Count > 0) { - ns.SendMobileMovingUsingCache(mobileMovingCache, m, this); + var m = moveClientQueue.Dequeue(); + var ns = m.NetState; + + if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) + { + ns.SendMobileMovingUsingCache(mobileMovingCache, m, this); + } } } - for (var i = 0; i < m_MoveList.Count; ++i) + while (moveQueue.Count > 0) { - var o = m_MoveList[i]; - - if (o is Mobile mobile) - { - mobile.OnMovement(this, oldLocation); - } - else if (o is Item item) - { - item.OnMovement(this, oldLocation); - } - } - - if (m_MoveList.Count > 0) - { - m_MoveList.Clear(); - } - - if (m_MoveClientList.Count > 0) - { - m_MoveClientList.Clear(); + moveQueue.Dequeue().OnMovement(this, oldLocation); } } @@ -8059,11 +8112,19 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Map.ItemEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + public Map.ItemAtEnumerable GetItemsAt() => + m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Location); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Map.ItemEnumerable GetItemsInRange(int range) where T : Item => - m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Location, range); + public Map.ItemAtEnumerable GetItemsAt() where T : Item => + m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Location); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemBoundsEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemBoundsEnumerable GetItemsInRange(int range) where T : Item => + m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Location, range); public IPooledEnumerable GetObjectsInRange(int range) => m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; diff --git a/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs b/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs index fea3077e5..2f99d037a 100644 --- a/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs +++ b/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text.Json.Serialization; +using Server.Collections; using Server.Items; using Server.Json; using Server.Network; @@ -121,16 +122,20 @@ namespace Server.Commands public static int DeleteTeleporters(WorldLocation worldLocation) { - var count = 0; - foreach (var item in worldLocation.Map.GetItemsInRange(worldLocation, 0)) + using var queue = PooledRefQueue.Create(); + foreach (var item in worldLocation.Map.GetItemsAt(worldLocation)) { if (item is not (KeywordTeleporter or SkillTeleporter) && IsWithinZ(item.Z - worldLocation.Z)) { - count++; - item.Delete(); + queue.Enqueue(item); } } + var count = queue.Count; + while (queue.Count > 0) + { + queue.Dequeue().Delete(); + } return count; } diff --git a/Projects/UOContent/Commands/SignParser.cs b/Projects/UOContent/Commands/SignParser.cs index 704c38f1b..c98df5159 100644 --- a/Projects/UOContent/Commands/SignParser.cs +++ b/Projects/UOContent/Commands/SignParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Server.Collections; using Server.Items; using Server.Network; @@ -8,8 +9,6 @@ namespace Server.Commands { public static class SignParser { - private static readonly Queue m_ToDelete = new(); - public static void Initialize() { CommandSystem.Register("SignGen", AccessLevel.Administrator, SignGen_OnCommand); @@ -91,17 +90,18 @@ namespace Server.Commands public static void Add_Static(int itemID, Point3D location, Map map, string name) { + using var queue = PooledRefQueue.Create(); foreach (var item in map.GetItemsInRange(location, 0)) { if (item is Sign && item.Z == location.Z && item.ItemID == itemID) { - m_ToDelete.Enqueue(item); + queue.Enqueue(item); } } - while (m_ToDelete.Count > 0) + while (queue.Count > 0) { - m_ToDelete.Dequeue().Delete(); + queue.Dequeue().Delete(); } Item sign; diff --git a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs index 29dd6d84e..483136a1c 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs @@ -468,7 +468,7 @@ namespace Server.Engines.ConPVP if (landTile.ID == 0x244 && statics.Length == 0) // 0x244 = invalid land tile { var empty = true; - foreach (var item in Map.GetItemsInRange(point, 0)) + foreach (var item in Map.GetItemsAt(point)) { if (item != this) { @@ -660,7 +660,7 @@ namespace Server.Engines.ConPVP } } - foreach (var item in GetItemsInRange(0)) + foreach (var item in GetItemsAt()) { if (item.Visible && item != this) { diff --git a/Projects/UOContent/Engines/Doom/GenGauntlet.cs b/Projects/UOContent/Engines/Doom/GenGauntlet.cs index 04f0baebd..d177c4fc4 100644 --- a/Projects/UOContent/Engines/Doom/GenGauntlet.cs +++ b/Projects/UOContent/Engines/Doom/GenGauntlet.cs @@ -180,7 +180,7 @@ namespace Server.Engines.Doom public static void RemoveDoorSet(int x, int y) { var loc = new Point3D(x, y, -1); - foreach (var item in Map.Malas.GetItemsInRange(loc, 0)) + foreach (var item in Map.Malas.GetItemsAt(loc)) { if (item is BaseDoor door) { @@ -226,9 +226,9 @@ namespace Server.Engines.Doom public static void RemoveItem(int x, int y, int z = -1) where T : Item { - foreach (var item in Map.Malas.GetItemsInRange(new Point3D(x, y, z), 0)) + foreach (var item in Map.Malas.GetItemsAt(x, y)) { - if (item is T) + if (item is T && z == -1 || item.Z == z) { item.Delete(); break; diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs index 58501ba18..29a803fde 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs @@ -197,7 +197,7 @@ public partial class LeverPuzzleController : Item [Usage("GenLeverPuzzle"), Description("Generates lamp room and lever puzzle in doom.")] public static void GenLampPuzzle_OnCommand(CommandEventArgs e) { - foreach (var item in Map.Malas.GetItemsInRange(lp_Center, 0)) + foreach (var item in Map.Malas.GetItemsAt(lp_Center)) { if (item is LeverPuzzleController) { diff --git a/Projects/UOContent/Engines/Factions/Core/Generator.cs b/Projects/UOContent/Engines/Factions/Core/Generator.cs index dc26bb0b1..a94cc2d22 100644 --- a/Projects/UOContent/Engines/Factions/Core/Generator.cs +++ b/Projects/UOContent/Engines/Factions/Core/Generator.cs @@ -78,7 +78,7 @@ namespace Server.Factions private static bool CheckExistence(Point3D loc, Map facet, Type type) { - foreach (var item in facet.GetItemsInRange(loc, 0)) + foreach (var item in facet.GetItemsAt(loc)) { if (type.IsInstanceOfType(item)) { diff --git a/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs b/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs index baa605d1c..05ba279c4 100644 --- a/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs +++ b/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs @@ -114,7 +114,7 @@ namespace Server.Factions if (Core.ML) { - foreach (var item in m.GetItemsInRange(p, 0)) + foreach (var item in m.GetItemsAt(p)) { if (item is BaseFactionTrap trap && trap.Faction == Faction) { diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs index 947080454..99ccdd10a 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs @@ -307,7 +307,7 @@ namespace Server.Engines.Harvest return false; } - foreach (var i in m.GetItemsInRange(0)) + foreach (var i in m.GetItemsAt()) { if (i.StackWith(m, i, false)) { diff --git a/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs b/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs index febd01cbb..4c1ea7639 100644 --- a/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs +++ b/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs @@ -15,9 +15,9 @@ namespace Server.Commands public static bool FindMorphItem(int x, int y, int z, int inactiveItemID, int activeItemID) { var found = false; - foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) + foreach (var morphItem in Map.Felucca.GetItemsAt(x, y)) { - if (item is MorphItem morphItem && morphItem.Z == z && morphItem.InactiveItemId == inactiveItemID && morphItem.ActiveItemId == activeItemID) + if (morphItem.Z == z && morphItem.InactiveItemId == inactiveItemID && morphItem.ActiveItemId == activeItemID) { found = true; break; @@ -30,9 +30,9 @@ namespace Server.Commands public static bool FindEffectController(int x, int y, int z) { var found = false; - foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) + foreach (var item in Map.Felucca.GetItemsAt(x, y)) { - if (item is EffectController && item.Z == z) + if (item.Z == z) { found = true; break; @@ -44,7 +44,7 @@ namespace Server.Commands public static T TryCreateItem(int x, int y, int z, T srcItem) where T : Item { - foreach (var item in Map.Felucca.GetItemsInBounds(new Rectangle2D(x, y, 1, 1))) + foreach (var item in Map.Felucca.GetItemsAt(x, y)) { srcItem.Delete(); return item; diff --git a/Projects/UOContent/Engines/ML Quests/MLQuest.cs b/Projects/UOContent/Engines/ML Quests/MLQuest.cs index 46789014d..e714cae40 100644 --- a/Projects/UOContent/Engines/ML Quests/MLQuest.cs +++ b/Projects/UOContent/Engines/ML Quests/MLQuest.cs @@ -267,7 +267,7 @@ namespace Server.Engines.MLQuests var name = $"MLQS-{GetType().Name}"; using var queue = PooledRefQueue.Create(); - foreach (var item in map.GetItemsInRange(loc, 0)) + foreach (var item in map.GetItemsAt(loc)) { // This predates GUIDs. Let's build these spawners and export them so we can delete this code! if (item is BaseSpawner spawner && spawner.Name == name) @@ -288,7 +288,7 @@ namespace Server.Engines.MLQuests public static void PutDeco(Item deco, Point3D loc, Map map) { using var queue = PooledRefQueue.Create(); - foreach (var item in map.GetItemsInRange(loc, 0)) + foreach (var item in map.GetItemsAt(loc)) { if (item.ItemID == deco.ItemID && item.Z == loc.Z) { diff --git a/Projects/UOContent/Engines/Pathing/Movement.cs b/Projects/UOContent/Engines/Pathing/Movement.cs index 91afe95d5..79a3c411a 100644 --- a/Projects/UOContent/Engines/Pathing/Movement.cs +++ b/Projects/UOContent/Engines/Pathing/Movement.cs @@ -22,8 +22,6 @@ namespace Server.Movement private readonly List[] _pools = { new(), new(), new(), new() }; - private readonly HashSet _sectors = new(); - private MovementImpl() { } @@ -83,149 +81,55 @@ namespace Server.Movement var checkMobs = (m as BaseCreature)?.Controlled == false && (xForward != _goal.X || yForward != _goal.Y); - if (checkDiagonals) + foreach (var entity in map.GetObjectsInRange(loc, 1)) { - var sectorStart = map.GetSector(xStart, yStart); - var sectorForward = map.GetSector(xForward, yForward); - var sectorLeft = map.GetSector(xLeft, yLeft); - var sectorRight = map.GetSector(xRight, yRight); - - _sectors.Add(sectorStart); - - _sectors.Add(sectorForward); - _sectors.Add(sectorLeft); - _sectors.Add(sectorRight); - - foreach (var sector in _sectors) - { - foreach (var item in sector.Items) - { - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) - { - continue; - } - - if (!item.ItemData[reqFlags]) - { - continue; - } - - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - { - continue; - } - - if (sector == sectorStart && item.AtWorldPoint(xStart, yStart)) - { - itemsStart.Add(item); - } - else if (sector == sectorForward && item.AtWorldPoint(xForward, yForward)) - { - itemsForward.Add(item); - } - else if (sector == sectorLeft && item.AtWorldPoint(xLeft, yLeft)) - { - itemsLeft.Add(item); - } - else if (sector == sectorRight && item.AtWorldPoint(xRight, yRight)) - { - itemsRight.Add(item); - } - } - - if (checkMobs) - { - for (var j = 0; j < sector.Mobiles.Count; ++j) - { - var mob = sector.Mobiles[j]; - - if (sector == sectorForward && mob.X == xForward && mob.Y == yForward) - { - mobsForward.Add(mob); - } - else if (sector == sectorLeft && mob.X == xLeft && mob.Y == yLeft) - { - mobsLeft.Add(mob); - } - else if (sector == sectorRight && mob.X == xRight && mob.Y == yRight) - { - mobsRight.Add(mob); - } - } - } - } - - _sectors.Clear(); - } - else - { - var sectorStart = map.GetSector(xStart, yStart); - var sectorForward = map.GetSector(xForward, yForward); - var sectorStartIsForward = sectorStart == sectorForward; - - if (!sectorStartIsForward) - { - foreach (var item in sectorForward.Items) - { - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) - { - continue; - } - - if (!item.ItemData[reqFlags]) - { - continue; - } - - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - { - continue; - } - - if (item.AtWorldPoint(xForward, yForward)) - { - itemsForward.Add(item); - } - } - } - - foreach (var item in sectorStart.Items) + if (entity is Item item) { if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) { continue; } - if (!item.ItemData[reqFlags]) + if (!item.ItemData[reqFlags] || item.ItemID > TileData.MaxItemValue || item.Parent != null) { continue; } - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) + if (item is BaseMulti) { continue; } - if (item.AtWorldPoint(xStart, yStart)) + if (item.AtPoint(xStart, yStart)) { itemsStart.Add(item); } - else if (sectorStartIsForward && item.AtWorldPoint(xForward, yForward)) + else if (item.AtPoint(xForward, yForward)) { itemsForward.Add(item); } - } - - if (checkMobs) - { - for (var i = 0; i < sectorForward.Mobiles.Count; ++i) + else if (checkDiagonals && item.AtPoint(xLeft, yLeft)) { - var mob = sectorForward.Mobiles[i]; - - if (mob.X == xForward && mob.Y == yForward) - { - mobsForward.Add(mob); - } + itemsLeft.Add(item); + } + else if (checkDiagonals && item.AtPoint(xRight, yRight)) + { + itemsRight.Add(item); + } + } + else if (checkMobs && entity is Mobile mob) + { + if (mob.AtPoint(xForward, yForward)) + { + mobsForward.Add(mob); + } + else if (checkDiagonals && mob.AtPoint(xLeft, yLeft)) + { + mobsLeft.Add(mob); + } + else if (checkDiagonals && mob.AtPoint(xRight, yRight)) + { + mobsRight.Add(mob); } } } @@ -274,7 +178,7 @@ namespace Server.Movement public bool CheckMovement(Mobile m, Direction d, out int newZ) => CheckMovement(m, m.Map, m.Location, d, out newZ); - private bool IsOk( + private static bool IsOk( bool ignoreDoors, bool ignoreSpellFields, int ourZ, int ourTop, StaticTile[] tiles, List items ) { @@ -326,7 +230,7 @@ namespace Server.Movement return true; } - private bool Check( + private static bool Check( Map map, Mobile m, List items, @@ -596,7 +500,7 @@ namespace Server.Movement private static bool CanMoveOver(Mobile m, Mobile t) => !t.Alive || !m.Alive || t.IsDeadBondedPet || m.IsDeadBondedPet || t.Hidden && t.AccessLevel > AccessLevel.Player; - private void GetStartZ(Mobile m, Map map, Point3D loc, List itemList, out int zLow, out int zTop) + private static void GetStartZ(Mobile m, Map map, Point3D loc, List itemList, out int zLow, out int zTop) { int xCheck = loc.X, yCheck = loc.Y; @@ -694,7 +598,7 @@ namespace Server.Movement } } - public void Offset(Direction d, ref int x, ref int y) + public static void Offset(Direction d, ref int x, ref int y) { switch (d & Direction.Mask) { diff --git a/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs b/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs index 69df4fed4..ad1c54305 100644 --- a/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs +++ b/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs @@ -29,13 +29,9 @@ public partial class Victoria : BaseQuester if (_altar?.Deleted != false || _altar.Map != Map || !Utility.InRange(_altar.Location, Location, AltarRange)) { - foreach (var item in GetItemsInRange(AltarRange)) + foreach (var altar in GetItemsInRange(AltarRange)) { - if (item is SummoningAltar altar) - { - _altar = altar; - break; - } + _altar = altar; } } diff --git a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs index c81ead0e1..74deb41cc 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs @@ -147,7 +147,7 @@ namespace Server.Engines.Spawners // Delete all spawners at this location. // Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners? - foreach (var spawner in map.GetItemsInRange(location, 0)) + foreach (var spawner in map.GetItemsAt(location)) { if (spawner.GetType() == type) { diff --git a/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs b/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs index 2e88a0a9b..2ce94fa80 100644 --- a/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs +++ b/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs @@ -199,9 +199,9 @@ namespace Server.Items bool isclear = true; - foreach (Item item in Map.Malas.GetItemsInRange(p3d, 0)) + foreach (Fireflies fireflies in Map.Malas.GetItemsAt(p3d)) { - if (item is Fireflies) + if (fireflies.Z == p3d.Z) { isclear = false; } diff --git a/Projects/UOContent/Items/Addons/SHTeleporter.cs b/Projects/UOContent/Items/Addons/SHTeleporter.cs index 9d6f936c2..9dbd7303d 100644 --- a/Projects/UOContent/Items/Addons/SHTeleporter.cs +++ b/Projects/UOContent/Items/Addons/SHTeleporter.cs @@ -267,7 +267,7 @@ namespace Server.Items public static SHTeleporter FindSHTeleporter(Map map, Point3D p) { - foreach (var teleporter in map.GetItemsInRange(p, 0)) + foreach (var teleporter in map.GetItemsAt(p)) { if (teleporter.Z == p.Z) { diff --git a/Projects/UOContent/Items/Containers/MarkContainer.cs b/Projects/UOContent/Items/Containers/MarkContainer.cs index 78e43f920..c193f967e 100644 --- a/Projects/UOContent/Items/Containers/MarkContainer.cs +++ b/Projects/UOContent/Items/Containers/MarkContainer.cs @@ -130,7 +130,7 @@ public partial class MarkContainer : LockableContainer private static bool FindMarkContainer(Point3D p, Map map) { - foreach (var item in map.GetItemsInRange(p, 0)) + foreach (var item in map.GetItemsAt(p)) { if (item.Z == p.Z) { diff --git a/Projects/UOContent/Items/Misc/OilFlask.cs b/Projects/UOContent/Items/Misc/OilFlask.cs index 35b16c664..c890f0861 100644 --- a/Projects/UOContent/Items/Misc/OilFlask.cs +++ b/Projects/UOContent/Items/Misc/OilFlask.cs @@ -50,7 +50,7 @@ public partial class OilFlask : Item { var didStack = false; - foreach (var i in from.GetItemsInRange(0)) + foreach (var i in from.GetItemsAt()) { if (i.StackWith(from, this, false)) { diff --git a/Projects/UOContent/Items/Misc/WarningItem.cs b/Projects/UOContent/Items/Misc/WarningItem.cs index 5194a0926..5d0b491a4 100644 --- a/Projects/UOContent/Items/Misc/WarningItem.cs +++ b/Projects/UOContent/Items/Misc/WarningItem.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; namespace Server.Items; @@ -83,19 +83,18 @@ public partial class WarningItem : Item if (NeighborRange >= 0) { - var list = new List(); - - foreach (var item in GetItemsInRange(NeighborRange)) + using var queue = PooledRefQueue.Create(); + foreach (var warningItem in GetItemsInRange(NeighborRange)) { - if (item != this && item is WarningItem warningItem) + if (warningItem != this) { - list.Add(warningItem); + queue.Enqueue(warningItem); } } - for (var i = 0; i < list.Count; i++) + while (queue.Count > 0) { - list[i].Broadcast(triggerer); + queue.Dequeue().Broadcast(triggerer); } } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index dda4cbd3d..5a10ddac4 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.ContextMenus; using Server.Engines.Quests; using Server.Engines.Quests.Necro; @@ -94,7 +95,6 @@ public abstract class BaseAI SkillName.Meditation }; - private static readonly Queue m_Obstacles = new(); protected ActionType m_Action; public BaseCreature m_Mobile; @@ -2156,8 +2156,9 @@ public abstract class BaseAI int x = m_Mobile.X, y = m_Mobile.Y; Movement.Movement.Offset(d, ref x, ref y); + using var queue = PooledRefQueue.Create(); var destroyables = 0; - foreach (var item in map.GetItemsInRange(new Point3D(x, y, m_Mobile.Location.Z), 1)) + foreach (var item in map.GetItemsInRange(new Point2D(x, y), 1)) { if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > m_Mobile.Z && m_Mobile.Z + 16 > door.Z) @@ -2169,7 +2170,7 @@ public abstract class BaseAI if (!door.Locked || !door.UseLocks()) { - m_Obstacles.Enqueue(door); + queue.Enqueue(door); } if (!canDestroyObstacles) @@ -2185,7 +2186,7 @@ public abstract class BaseAI continue; } - m_Obstacles.Enqueue(item); + queue.Enqueue(item); ++destroyables; } } @@ -2195,14 +2196,14 @@ public abstract class BaseAI Effects.PlaySound(new Point3D(x, y, m_Mobile.Z), m_Mobile.Map, 0x3B3); } - if (m_Obstacles.Count > 0) + if (queue.Count > 0) { blocked = false; // retry movement } - while (m_Obstacles.Count > 0) + while (queue.Count > 0) { - var item = m_Obstacles.Dequeue(); + var item = queue.Dequeue(); if (item is BaseDoor door) { @@ -2238,7 +2239,7 @@ public abstract class BaseAI if (check.Movable && check.ItemData.Impassable && cont.Z + check.ItemData.Height > m_Mobile.Z) { - m_Obstacles.Enqueue(check); + queue.Enqueue(check); } } diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs index cddab9e31..6df9e3d77 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs @@ -118,7 +118,7 @@ namespace Server.Mobiles p = GetSpawnPosition(2); var atLocation = false; - foreach (var item in Map.GetItemsInRange(p, 0)) + foreach (var item in Map.GetItemsAt(p)) { atLocation = true; break; diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs index c54756028..1a241281d 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs @@ -235,7 +235,7 @@ namespace Server.Mobiles p = GetSpawnPosition(2); var atLocation = false; - foreach (var item in Map.GetItemsInRange(p, 0)) + foreach (var item in Map.GetItemsAt(p)) { atLocation = true; break; diff --git a/Projects/UOContent/Multis/Houses/HousePlacement.cs b/Projects/UOContent/Multis/Houses/HousePlacement.cs index 206a3cd62..e21a1c046 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacement.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacement.cs @@ -358,11 +358,9 @@ namespace Server.Multis } } - var sector = map.GetSector(borderPoint.X, borderPoint.Y); - - foreach (var item in sector.Items) + foreach (var item in map.GetItemsAt(borderPoint)) { - if (item.X != borderPoint.X || item.Y != borderPoint.Y || item.Movable) + if (item.Movable) { continue; } diff --git a/Projects/UOContent/Spells/Seventh/GateTravel.cs b/Projects/UOContent/Spells/Seventh/GateTravel.cs index f4b0afac6..40b33ca07 100644 --- a/Projects/UOContent/Spells/Seventh/GateTravel.cs +++ b/Projects/UOContent/Spells/Seventh/GateTravel.cs @@ -141,7 +141,7 @@ namespace Server.Spells.Seventh private static bool GateExistsAt(Map map, Point3D loc) { - foreach (var item in map.GetItemsInRange(loc, 0)) + foreach (var item in map.GetItemsAt(loc)) { if (item is Moongate or PublicMoongate) { diff --git a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs index b7a615ef5..e51e7f936 100644 --- a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs +++ b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs @@ -97,7 +97,7 @@ namespace Server.Spells.Spellweaving } } - foreach (var item in map.GetItemsInRange(location, 0)) + foreach (var item in map.GetItemsAt(location)) { if (item.Z + item.ItemData.CalcHeight == location.Z && IsValidTile(item.ItemID)) { diff --git a/Projects/UOContent/Spells/Third/Teleport.cs b/Projects/UOContent/Spells/Third/Teleport.cs index cab15f8c9..b7c21bcf2 100644 --- a/Projects/UOContent/Spells/Third/Teleport.cs +++ b/Projects/UOContent/Spells/Third/Teleport.cs @@ -1,3 +1,4 @@ +using Server.Collections; using Server.Factions; using Server.Items; using Server.Misc; @@ -96,14 +97,21 @@ namespace Server.Spells.Third m.PlaySound(0x1FE); - foreach (var item in m.GetItemsInRange(0)) + using var queue = PooledRefQueue.Create(); + foreach (var item in m.GetItemsAt()) { if (item is ParalyzeFieldSpell.InternalItem or PoisonFieldSpell.InternalItem or FireFieldSpell.FireFieldItem) { - item.OnMoveOver(m); + // Use a queue just in case OnMoveOver changes the item's sector + queue.Enqueue(item); } } + + while (queue.Count > 0) + { + queue.Dequeue().OnMoveOver(m); + } } FinishSequence();