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
<img width="588" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/83ee0b6e-ff4f-4768-9e29-84456e04b1ec">
This commit is contained in:
Kamron Batman 2023-10-26 17:49:15 -07:00 • committed by GitHub
parent 658f564f34
commit d919f71149
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 706 additions and 468 deletions

View file

@ -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<TestEntity>();
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<InvalidOperationException>(
() =>
{
var linkList = new ValueLinkList<TestEntity>();
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<InvalidOperationException>(
() =>
{
var linkList = new ValueLinkList<TestEntity>();
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);
}
}
}
);
}
}

View file

@ -28,8 +28,10 @@ public interface IValueLinkListNode<T> where T : class
public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
{
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<T> where T : class, IValueLinkListNode<T>
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<T> where T : class, IValueLinkListNode<T>
// 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<T> where T : class, IValueLinkListNode<T>
node.Previous = null;
node.OnLinkList = false;
Count--;
Version++;
if (Count < 0)
{
@ -130,7 +133,8 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
current = previous;
}
First = e;
_first = e;
Version++;
}
// Remove all entries after this node, not including this node.
@ -171,7 +175,8 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
current = next;
}
Last = e;
_last = e;
Version++;
}
public void AddLast(T e)
@ -186,15 +191,16 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
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<T> where T : class, IValueLinkListNode<T>
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<T> where T : class, IValueLinkListNode<T>
}
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<T> where T : class, IValueLinkListNode<T>
}
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<T> where T : class, IValueLinkListNode<T>
current = next;
}
First = null;
Last = null;
_first = null;
_last = null;
Count = 0;
Version++;
}
public void AddLast(ref ValueLinkList<T> 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<T> where T : class, IValueLinkListNode<T>
else
{
// Start is first
otherList.First = end.Next;
otherList._first = end.Next;
}
if (end.Next != null)
@ -338,7 +348,7 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
}
else
{
otherList.Last = start.Previous;
otherList._last = start.Previous;
}
var count = 1;
@ -358,45 +368,62 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
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<T> _linkList;
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ValueListEnumerator(T head)
public ValueListEnumerator(in ValueLinkList<T> 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<T> where T : class, IValueLinkListNode<T>
public ref struct DescendingValueListEnumerator
{
private T _tail;
private bool _started;
private T _current;
private ref readonly ValueLinkList<T> _linkList;
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingValueListEnumerator(T head)
public DescendingValueListEnumerator(in ValueLinkList<T> 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<T> where T : class, IValueLinkListNode<T>
public DescendingValueListEnumerator GetEnumerator() => this;
}
}
public static class ValueLinkListExt
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueLinkList<T>.ValueListEnumerator GetEnumerator<T>(this in ValueLinkList<T> linkList)
where T : class, IValueLinkListNode<T> => new(in linkList);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueLinkList<T>.DescendingValueListEnumerator ByDescending<T>(this in ValueLinkList<T> linkList)
where T : class, IValueLinkListNode<T> => new(in linkList);
}

View file

@ -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

View file

@ -2241,7 +2241,6 @@ public class Item : IHued, IComparable<Item>, 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<Item>, ISpawnable, IObjectPropertyListEnt
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<Item> GetItemsInRange(int range) =>
m_Map == null ? Map.ItemEnumerable<Item>.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
public Map.ItemAtEnumerable<Item> GetItemsAt() =>
m_Map == null ? Map.ItemAtEnumerable<Item>.Empty : m_Map.GetItemsAt(m_Parent == null ? m_Location : GetWorldLocation());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
m_Map == null ? Map.ItemEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Parent == null ? m_Location : GetWorldLocation(), range);
public Map.ItemAtEnumerable<T> GetItemsAt<T>() where T : Item =>
m_Map == null ? Map.ItemAtEnumerable<T>.Empty : m_Map.GetItemsAt<T>(m_Parent == null ? m_Location : GetWorldLocation());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<Item> GetItemsInRange(int range) =>
m_Map == null ? Map.ItemBoundsEnumerable<Item>.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
m_Map == null ? Map.ItemBoundsEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Parent == null ? m_Location : GetWorldLocation(), range);
public IPooledEnumerable<Mobile> GetMobilesInRange(int range)
{

View file

@ -13,140 +13,159 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<Item> GetItemsAt(Point3D p) => GetItemsAt<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsAt(Point3D p) => GetItemsInRange(p, 0);
public ItemAtEnumerable<T> GetItemsAt<T>(Point3D p) where T : Item => GetItemsAt<T>(new Point2D(p.X, p.Y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsAt<T>(Point3D p) where T : Item => GetItemsInRange<T>(p, 0);
public ItemAtEnumerable<Item> GetItemsAt(int x, int y) => GetItemsAt<Item>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point3D p) => GetItemsInRange<Item>(p);
public ItemAtEnumerable<T> GetItemsAt<T>(int x, int y) where T : Item => GetItemsAt<T>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point3D p, int range) => GetItemsInRange<Item>(p, range);
public ItemAtEnumerable<Item> GetItemsAt(Point2D p) => GetItemsAt<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point3D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
public ItemAtEnumerable<T> GetItemsAt<T>(Point2D p) where T : Item => new(this, p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point3D p, int range) where T : Item =>
public ItemBoundsEnumerable<Item> GetItemsInRange(Point3D p) => GetItemsInRange<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemBoundsEnumerable<Item> GetItemsInRange(Point3D p, int range) => GetItemsInRange<Item>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point3D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point3D p, int range) where T : Item =>
GetItemsInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsAt(Point2D p) => GetItemsInRange(p, 0);
public ItemBoundsEnumerable<Item> GetItemsInRange(Point2D p) => GetItemsInRange<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsAt<T>(Point2D p) where T : Item => GetItemsInRange<T>(p, 0);
public ItemBoundsEnumerable<Item> GetItemsInRange(Point2D p, int range) => GetItemsInRange<Item>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point2D p) => GetItemsInRange<Item>(p);
public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point2D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point2D p, int range) => GetItemsInRange<Item>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point2D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point2D p, int range) where T : Item =>
public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point2D p, int range) where T : Item =>
GetItemsInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsAt(int x, int y) => GetItemsAt<Item>(x, y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsAt<T>(int x, int y) where T : Item => GetItemsInRange<T>(x, y, 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(int x, int y, int range) where T : Item =>
public ItemBoundsEnumerable<T> GetItemsInRange<T>(int x, int y, int range) where T : Item =>
GetItemsInBounds<T>(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds<Item>(bounds);
public ItemBoundsEnumerable<Item> GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds<Item>(bounds);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInBounds<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item =>
public ItemBoundsEnumerable<T> GetItemsInBounds<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item =>
new(this, bounds, makeBoundsInclusive);
private void BeginIteratingItems()
public ref struct ItemAtEnumerable<T> 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<T> 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<T> GetEnumerator() => new(_map, _location);
}
private void EndIteratingItems()
public ref struct ItemAtEnumerator<T> 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<Item> _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<T> where T : Item
public ref struct ItemBoundsEnumerable<T> where T : Item
{
public static ItemEnumerable<T> Empty
public static ItemBoundsEnumerable<T> 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<T> GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
}
@ -178,6 +196,9 @@ public partial class Map
private int _currentSectorX;
private int _currentSectorY;
private ref readonly ValueLinkList<Item> _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)]

View file

@ -627,12 +627,6 @@ public sealed partial class Map : IComparable<Map>, 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<Map>, 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<Map>, 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<Map>, 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<Map>, 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<Map>, 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<Map>, ISpanFormattable, ISpanParsa
public List<Mobile> Mobiles => _mobiles ?? m_DefaultMobileList;
public ref ValueLinkList<Item> Items => ref _items;
internal ref readonly ValueLinkList<Item> Items => ref _items;
public List<NetState> Clients => _clients ?? m_DefaultClientList;

View file

@ -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;

View file

@ -194,9 +194,6 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0);
private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0);
private static readonly List<IEntity> m_MoveList = new();
private static readonly List<Mobile> m_MoveClientList = new();
private static readonly object m_GhostMutateContext = new();
private static readonly List<Mobile> m_Hears = new();
@ -3567,6 +3564,8 @@ public partial class Mobile : IHued, IComparable<Mobile>, 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<T>() => BeginAction(typeof(T));
public bool BeginAction(object toLock)
@ -4154,12 +4153,20 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
if (oldSector != newSector)
{
using var queue = PooledRefQueue<IEntity>.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<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, 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<Mobile>, ISpawnable, IObjectPro
if (m_Map != null)
{
using var moveQueue = PooledRefQueue<IEntity>.Create(2048);
using var moveClientQueue = PooledRefQueue<Mobile>.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<Mobile>, 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<Mobile>, ISpawnable, IObjectPro
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<Item> GetItemsInRange(int range) => GetItemsInRange<Item>(range);
public Map.ItemAtEnumerable<Item> GetItemsAt() =>
m_Map == null ? Map.ItemAtEnumerable<Item>.Empty : m_Map.GetItemsAt(m_Location);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
m_Map == null ? Map.ItemEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Location, range);
public Map.ItemAtEnumerable<T> GetItemsAt<T>() where T : Item =>
m_Map == null ? Map.ItemAtEnumerable<T>.Empty : m_Map.GetItemsAt<T>(m_Location);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<Item> GetItemsInRange(int range) => GetItemsInRange<Item>(range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
m_Map == null ? Map.ItemBoundsEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Location, range);
public IPooledEnumerable<IEntity> GetObjectsInRange(int range) =>
m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable<IEntity>.Instance;

View file

@ -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<Teleporter>(worldLocation, 0))
using var queue = PooledRefQueue<Item>.Create();
foreach (var item in worldLocation.Map.GetItemsAt<Teleporter>(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;
}

View file

@ -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<Item> 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<Item>.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;

View file

@ -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)
{

View file

@ -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<T>(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;

View file

@ -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)
{

View file

@ -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))
{

View file

@ -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)
{

View file

@ -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))
{

View file

@ -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<MorphItem>(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<EffectController>(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<T>(int x, int y, int z, T srcItem) where T : Item
{
foreach (var item in Map.Felucca.GetItemsInBounds<T>(new Rectangle2D(x, y, 1, 1)))
foreach (var item in Map.Felucca.GetItemsAt<T>(x, y))
{
srcItem.Delete();
return item;

View file

@ -267,7 +267,7 @@ namespace Server.Engines.MLQuests
var name = $"MLQS-{GetType().Name}";
using var queue = PooledRefQueue<Item>.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<Item>.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)
{

View file

@ -22,8 +22,6 @@ namespace Server.Movement
private readonly List<Item>[] _pools = { new(), new(), new(), new() };
private readonly HashSet<Map.Sector> _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<Item> items
)
{
@ -326,7 +230,7 @@ namespace Server.Movement
return true;
}
private bool Check(
private static bool Check(
Map map,
Mobile m,
List<Item> 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<Item> itemList, out int zLow, out int zTop)
private static void GetStartZ(Mobile m, Map map, Point3D loc, List<Item> 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)
{

View file

@ -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<SummoningAltar>(AltarRange))
{
if (item is SummoningAltar altar)
{
_altar = altar;
break;
}
_altar = altar;
}
}

View file

@ -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<BaseSpawner>(location, 0))
foreach (var spawner in map.GetItemsAt<BaseSpawner>(location))
{
if (spawner.GetType() == type)
{

View file

@ -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<Fireflies>(p3d))
{
if (item is Fireflies)
if (fireflies.Z == p3d.Z)
{
isclear = false;
}

View file

@ -267,7 +267,7 @@ namespace Server.Items
public static SHTeleporter FindSHTeleporter(Map map, Point3D p)
{
foreach (var teleporter in map.GetItemsInRange<SHTeleporter>(p, 0))
foreach (var teleporter in map.GetItemsAt<SHTeleporter>(p))
{
if (teleporter.Z == p.Z)
{

View file

@ -130,7 +130,7 @@ public partial class MarkContainer : LockableContainer
private static bool FindMarkContainer(Point3D p, Map map)
{
foreach (var item in map.GetItemsInRange<MarkContainer>(p, 0))
foreach (var item in map.GetItemsAt<MarkContainer>(p))
{
if (item.Z == p.Z)
{

View file

@ -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))
{

View file

@ -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<WarningItem>();
foreach (var item in GetItemsInRange(NeighborRange))
using var queue = PooledRefQueue<WarningItem>.Create();
foreach (var warningItem in GetItemsInRange<WarningItem>(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);
}
}

View file

@ -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<Item> 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<Item>.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);
}
}

View file

@ -118,7 +118,7 @@ namespace Server.Mobiles
p = GetSpawnPosition(2);
var atLocation = false;
foreach (var item in Map.GetItemsInRange<StainedOoze>(p, 0))
foreach (var item in Map.GetItemsAt<StainedOoze>(p))
{
atLocation = true;
break;

View file

@ -235,7 +235,7 @@ namespace Server.Mobiles
p = GetSpawnPosition(2);
var atLocation = false;
foreach (var item in Map.GetItemsInRange<StainedOoze>(p, 0))
foreach (var item in Map.GetItemsAt<StainedOoze>(p))
{
atLocation = true;
break;

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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))
{

View file

@ -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<Item>.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();