fix: Adds multis to map iterators, fixes searching empty nested containers (#1581)

### Summary
Eliminates `IPooledEnumerable<BaseMulti>` and `eable.Free()` from `Map` for multis. This drastically simplifies code that iterates in range, for example:

```cs
foreach (var m in m.GetMultisInRange(5))
{
}
```
The code above no longer requires an eable and calling `Free()`.


### Bug Fixes
- [X] Fixes an issue with searching through nested empty containers.
This commit is contained in:
Kamron Batman 2023-11-05 08:17:34 -08:00 • committed by GitHub
parent 1f04a13f67
commit ca3df9cfa7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 960 additions and 860 deletions

View file

@ -0,0 +1,263 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Map.MultiEnumerator.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Server.Items;
namespace Server;
public partial class Map
{
private static SectorMultiValueLinkList _emptyMultiLinkList = new();
public static ref readonly SectorMultiValueLinkList EmptyMultiLinkList => ref _emptyMultiLinkList;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<BaseMulti> GetMultisAt(Point3D p) => GetMultisAt<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<T> GetMultisAt<T>(Point3D p) where T : BaseMulti => GetMultisAt<T>(new Point2D(p.X, p.Y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<BaseMulti> GetMultisAt(int x, int y) => GetMultisAt<BaseMulti>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<T> GetMultisAt<T>(int x, int y) where T : BaseMulti => GetMultisAt<T>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<BaseMulti> GetMultisAt(Point2D p) => GetMultisAt<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<T> GetMultisAt<T>(Point2D p) where T : BaseMulti => new(this, p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point3D p) => GetMultisInRange<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point3D p, int range) => GetMultisInRange<BaseMulti>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point3D p) where T : BaseMulti => GetMultisInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point3D p, int range) where T : BaseMulti =>
GetMultisInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point2D p) => GetMultisInRange<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point2D p, int range) => GetMultisInRange<BaseMulti>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point2D p) where T : BaseMulti => GetMultisInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point2D p, int range) where T : BaseMulti =>
GetMultisInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(int x, int y, int range) where T : BaseMulti =>
GetMultisInBounds<T>(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInBounds(Rectangle2D bounds) => GetMultisInBounds<BaseMulti>(bounds);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInBounds<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : BaseMulti =>
new(this, bounds, makeBoundsInclusive);
public ref struct MultiAtEnumerable<T> where T : BaseMulti
{
public static MultiAtEnumerable<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new();
}
private readonly Map _map;
private readonly Point2D _location;
public MultiAtEnumerable(Map map, Point2D loc)
{
_map = map;
_location = loc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerator<T> GetEnumerator() => new(_map, _location);
}
public ref struct MultiAtEnumerator<T> where T : BaseMulti
{
private Point2D _location;
private readonly Span<BaseMulti> _list;
private int _index;
private T _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerator(Map map, Point2D loc)
{
_location = loc;
_list = map == null
? Span<BaseMulti>.Empty
: CollectionsMarshal.AsSpan(map.GetRealSector(loc.m_X, loc.m_Y).Multis);
_index = 0;
_current = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
ref var loc = ref _location;
while ((uint)_index < (uint)_list.Length)
{
var current = _list[_index++];
if (current is T { Deleted: false } o && o.X == loc.m_X && o.Y == loc.m_Y)
{
_current = o;
return true;
}
}
return false;
}
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
public ref struct MultiBoundsEnumerable<T> where T : BaseMulti
{
public static MultiBoundsEnumerable<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(null, Rectangle2D.Empty, false);
}
private Map _map;
private Rectangle2D _bounds;
private bool _makeBoundsInclusive;
public MultiBoundsEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;
_makeBoundsInclusive = makeBoundsInclusive;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiEnumerator<T> GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
}
public ref struct MultiEnumerator<T> where T : BaseMulti
{
private readonly Map _map;
private readonly int _sectorStartX;
private readonly int _sectorEndX;
private readonly int _sectorEndY;
private Rectangle2D _bounds;
private int _currentSectorX;
private int _currentSectorY;
private Span<BaseMulti> _list;
private T _current;
private int _index;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;
if (makeBoundsInclusive)
{
++bounds.Width;
++bounds.Height;
}
_bounds = bounds;
map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
// We start the X sector one short because it gets incremented immediately in MoveNext()
_currentSectorX = _sectorStartX - 1;
_currentSectorY = _sectorStartY;
_index = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool GetMulti()
{
ref Rectangle2D bounds = ref _bounds;
while ((uint)_index < (uint)_list.Length)
{
var current = _list[_index++];
if (current is T { Deleted: false } o && bounds.Contains(o.Location))
{
_current = o;
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool GetSector()
{
var currentSectorX = _currentSectorX;
var currentSectorY = _currentSectorY;
var sectorEndX = _sectorEndX;
var sectorEndY = _sectorEndY;
// Move to next sector
if (currentSectorX < sectorEndX)
{
_currentSectorX = ++currentSectorX;
}
else if (currentSectorY < sectorEndY)
{
_currentSectorX = currentSectorX = _sectorStartX;
_currentSectorY = ++currentSectorY;
}
else
{
// Ran out of sectors
return false;
}
_list = CollectionsMarshal.AsSpan(_map.GetRealSector(currentSectorX, currentSectorY).Multis);
return GetMulti();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _map != null && (GetMulti() || GetSector());
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
}

View file

@ -0,0 +1,121 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Map.MultiTileEnumerator.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Runtime.CompilerServices;
using Server.Items;
namespace Server;
public partial class Map
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerable GetMultiTilesAt(int x, int y) => GetMultiTilesAt(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerable GetMultiTilesAt(Point2D p) => new(this, p);
public ref struct MultiTilesAtEnumerable
{
public static MultiTilesAtEnumerable Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new();
}
private readonly Map _map;
private readonly Point2D _location;
public MultiTilesAtEnumerable(Map map, Point2D loc)
{
_map = map;
_location = loc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerator GetEnumerator() => new(_map, _location);
}
public ref struct MultiTilesAtEnumerator
{
private Point2D _location;
private MultiAtEnumerator<BaseMulti> _multis;
private BaseMulti _currentMulti;
private StaticTile[] _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerator(Map map, Point2D loc)
{
_multis = (map == null ? MultiAtEnumerable<BaseMulti>.Empty : map.GetMultisAt(loc)).GetEnumerator();
_current = null;
_location = loc;
_currentMulti = null;
}
private bool SetStaticTiles()
{
var mcl = _currentMulti.Components;
var x = _location.X;
var xo = x - (_currentMulti.X + mcl.Min.X);
var y = _location.Y;
if (xo < 0 || xo >= mcl.Width)
{
return false;
}
var yo = y - (_currentMulti.Y + mcl.Min.Y);
if (yo < 0 || yo >= mcl.Height)
{
return false;
}
var t = mcl.Tiles[xo][yo];
// TODO: Remove the allocation.
var r = new StaticTile[t.Length];
for (var i = 0; i < t.Length; i++)
{
r[i] = t[i];
r[i].Z += _currentMulti.Z;
}
_current = r;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
while (_multis.MoveNext())
{
_currentMulti = _multis.Current;
if (SetStaticTiles())
{
return true;
}
}
return false;
}
public StaticTile[] Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
}

View file

@ -69,7 +69,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
m_Name = name;
Rules = rules;
Regions = new Dictionary<string, Region>(StringComparer.OrdinalIgnoreCase);
InvalidSector = new Sector(0, 0, this);
_invalidSector = new Sector(0, 0, this);
m_SectorsWidth = width >> SectorShift;
m_SectorsHeight = height >> SectorShift;
m_Sectors = new Sector[m_SectorsWidth][];
@ -123,7 +123,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public MapRules Rules { get; set; }
public Sector InvalidSector { get; }
private readonly Sector _invalidSector;
public string Name
{
@ -566,7 +566,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y)
{
var sect = GetRealSector(x, y);
if (sect != InvalidSector)
if (sect != _invalidSector)
{
sect.Activate();
}
@ -581,7 +581,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y)
{
var sect = GetRealSector(x, y);
if (sect != InvalidSector && !PlayersInRange(sect, SectorActiveRange))
if (sect != _invalidSector && !PlayersInRange(sect, SectorActiveRange))
{
sect.Deactivate();
}
@ -596,7 +596,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
for (var y = sect.Y - range; y <= sect.Y + range; ++y)
{
var check = GetRealSector(x, y);
if (check != InvalidSector && check.Clients.Count > 0)
if (check != _invalidSector && check.Clients.Count > 0)
{
return true;
}
@ -690,7 +690,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
}
}
public void RemoveMulti(BaseMulti m, Sector start, Sector end)
private void RemoveMulti(BaseMulti m, Sector start, Sector end)
{
if (this == Internal)
{
@ -706,7 +706,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
}
}
public void AddMulti(BaseMulti m, Sector start, Sector end)
private void AddMulti(BaseMulti m, Sector start, Sector end)
{
if (this == Internal)
{
@ -862,9 +862,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return p;
}
public IPooledEnumerable<StaticTile[]> GetMultiTilesAt(int x, int y) =>
PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1));
public bool CanFit(
Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true,
bool requireSurface = true
@ -1011,7 +1008,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return sec;
}
return InvalidSector;
return _invalidSector;
}
public bool LineOfSight(Point3D org, Point3D dest)
@ -1408,13 +1405,12 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public class Sector
{
// TODO: Can we avoid this?
private static readonly List<BaseMulti> m_DefaultMultiList = new();
private static readonly List<Region> m_DefaultRectList = new();
private bool m_Active;
private ValueLinkList<NetState> _clients;
private ValueLinkList<Item> _items;
private ValueLinkList<Mobile> _mobiles;
private List<BaseMulti> _multis;
private List<BaseMulti> _multis = new();
private List<Region> _regions;
public Sector(int x, int y, Map owner)
@ -1427,7 +1423,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public List<Region> Regions => _regions ?? m_DefaultRectList;
public List<BaseMulti> Multis => _multis ?? m_DefaultMultiList;
internal List<BaseMulti> Multis => _multis;
internal ref ValueLinkList<Mobile> Mobiles => ref _mobiles;
@ -1553,12 +1549,12 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public void OnMultiEnter(BaseMulti multi)
{
Utility.Add(ref _multis, multi);
_multis.Add(multi);
}
public void OnMultiLeave(BaseMulti multi)
{
Utility.Remove(ref _multis, multi);
_multis.Remove(multi);
}
public void Activate()

View file

@ -1,298 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PooledEnumeration.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
namespace Server;
public interface IPooledEnumerable<T> : IEnumerable<T>, IDisposable
{
}
public static class PooledEnumeration
{
public delegate IEnumerable<T> Selector<out T>(Map.Sector sector, Rectangle2D bounds);
static PooledEnumeration()
{
MultiSelector = SelectMultis;
MultiTileSelector = SelectMultiTiles;
}
public static Selector<BaseMulti> MultiSelector { get; set; }
public static Selector<StaticTile[]> MultiTileSelector { get; set; }
public static IEnumerable<BaseMulti> SelectMultis(Map.Sector s, Rectangle2D bounds)
{
var entities = new List<BaseMulti>(s.Multis.Count);
for (int i = s.Multis.Count - 1; i >= 0; --i)
{
BaseMulti multi = s.Multis[i];
if (multi is { Deleted: false } && bounds.Contains(multi.Location))
{
entities.Add(multi);
}
}
return entities;
}
public static IEnumerable<StaticTile[]> SelectMultiTiles(Map.Sector s, Rectangle2D bounds)
{
for (int l = s.Multis.Count - 1; l >= 0; --l)
{
BaseMulti o = s.Multis[l];
if (o?.Deleted != false)
{
continue;
}
MultiComponentList c = o.Components;
int x, y, xo, yo;
StaticTile[] t, r;
for (x = bounds.Start.X; x < bounds.End.X; x++)
{
xo = x - (o.X + c.Min.X);
if (xo < 0 || xo >= c.Width)
{
continue;
}
for (y = bounds.Start.Y; y < bounds.End.Y; y++)
{
yo = y - (o.Y + c.Min.Y);
if (yo < 0 || yo >= c.Height)
{
continue;
}
t = c.Tiles[xo][yo];
if (t.Length <= 0)
{
continue;
}
r = new StaticTile[t.Length];
for (var i = 0; i < t.Length; i++)
{
r[i] = t[i];
r[i].Z += o.Z;
}
yield return r;
}
}
}
}
public static PooledEnumerable<BaseMulti> GetMultis(Map map, Rectangle2D bounds) =>
PooledEnumerable<BaseMulti>.Instantiate(map, bounds, MultiSelector ?? SelectMultis);
public static PooledEnumerable<StaticTile[]> GetMultiTiles(Map map, Rectangle2D bounds) =>
PooledEnumerable<StaticTile[]>.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles);
public static IEnumerable<Map.Sector> EnumerateSectors(Map map, Rectangle2D bounds)
{
if (map == null || map == Map.Internal)
{
yield break;
}
var x1 = bounds.Start.X;
var y1 = bounds.Start.Y;
var x2 = bounds.End.X;
var y2 = bounds.End.Y;
if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector))
{
yield break;
}
var index = 0;
while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s))
{
yield return s;
}
}
public static bool Bound(
Map map,
ref int x1,
ref int y1,
ref int x2,
ref int y2,
out int xSector,
out int ySector
)
{
if (map == null || map == Map.Internal)
{
xSector = ySector = 0;
return false;
}
map.Bound(x1, y1, out x1, out y1);
map.Bound(x2 - 1, y2 - 1, out x2, out y2);
x1 >>= Map.SectorShift;
y1 >>= Map.SectorShift;
x2 >>= Map.SectorShift;
y2 >>= Map.SectorShift;
xSector = x1;
ySector = y1;
return true;
}
private static bool NextSector(
Map map,
int x1,
int y1,
int x2,
int y2,
ref int index,
ref int xSector,
ref int ySector,
out Map.Sector s
)
{
if (map == null)
{
s = null;
xSector = ySector = 0;
return false;
}
if (map == Map.Internal)
{
s = map.InvalidSector;
xSector = ySector = 0;
return false;
}
if (index++ > 0)
{
if (++ySector > y2)
{
ySector = y1;
if (++xSector > x2)
{
xSector = x1;
s = map.InvalidSector;
return false;
}
}
}
s = map.GetRealSector(xSector, ySector);
return true;
}
public class NullEnumerable<T> : IPooledEnumerable<T>
{
public static readonly NullEnumerable<T> Instance = new();
private readonly IEnumerable<T> m_Empty = Enumerable.Empty<T>();
IEnumerator IEnumerable.GetEnumerator() => m_Empty.GetEnumerator();
public IEnumerator<T> GetEnumerator() => m_Empty.GetEnumerator();
public void Dispose()
{
}
}
public sealed class PooledEnumerable<T> : IPooledEnumerable<T>
{
private static readonly Queue<PooledEnumerable<T>> _Buffer = new(0x400);
private bool m_IsDisposed;
private List<T> m_Pool = new(0x40);
public PooledEnumerable(IEnumerable<T> pool)
{
m_Pool.AddRange(pool);
}
public void Dispose()
{
if (m_IsDisposed)
{
return;
}
m_IsDisposed = true;
m_Pool.Clear();
m_Pool.Capacity = Math.Max(m_Pool.Capacity, 0x100);
lock (((ICollection)_Buffer).SyncRoot)
{
_Buffer.Enqueue(this);
}
}
~PooledEnumerable()
{
Dispose();
}
IEnumerator IEnumerable.GetEnumerator() => m_Pool.GetEnumerator();
public IEnumerator<T> GetEnumerator() => m_Pool.GetEnumerator();
#pragma warning disable CA1000 // Do not declare static members on generic types
public static PooledEnumerable<T> Instantiate(
Map map, Rectangle2D bounds, Selector<T> selector
)
{
PooledEnumerable<T> e = null;
lock (((ICollection)_Buffer).SyncRoot)
{
if (_Buffer.Count > 0)
{
e = _Buffer.Dequeue();
}
}
var pool = EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds));
if (e == null)
{
return new PooledEnumerable<T>(pool);
}
e.m_Pool.AddRange(pool);
return e;
}
}
}