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

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