fix: Refactors getting static tiles to use enumerators (#1611)

### Summary
* Refactors getting static/multi tiles to not use allocations.
* `TileList` is now only used during bootstrapping and uses rented buffers to eliminate extra allocations.
* Replaces `StaticTile[] GetStaticTiles` with:
    ```cs
        Map.StaticTileEnumerable GetStaticTiles(int x, int y);
        Map.StaticTileEnumerable GetStaticAndMultiTiles(int x, int y);
        Map.StaticTileEnumerable GetMultiTiles(int x, int y);
    ```
* Removes `Synchronized` and `lock` from TileMatrix. It is no longer considered a multi-thread safe system.
This commit is contained in:
Kamron Batman 2023-11-26 09:39:46 -08:00 • committed by GitHub
parent 3b1637b2da
commit f21c9687b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 359 additions and 512 deletions

View file

@ -51,7 +51,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
private readonly int m_SectorsWidth;
private readonly object tileLock = new();
private Region m_DefaultRegion;
private string m_Name;
@ -89,21 +88,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public int Season { get; set; }
public TileMatrix Tiles
{
get
{
if (m_Tiles == null)
{
lock (tileLock)
{
m_Tiles = new TileMatrix(this, m_FileIndex, MapID, Width, Height);
}
}
return m_Tiles;
}
}
public TileMatrix Tiles => m_Tiles ??= new TileMatrix(this, m_FileIndex, MapID, Width, Height);
public int MapID { get; }
@ -325,7 +310,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public void FixColumn(int x, int y)
{
var landTile = Tiles.GetLandTile(x, y);
var tiles = Tiles.GetStaticTiles(x, y, true);
GetAverageZ(x, y, out _, out var landAvg, out _);
@ -349,7 +333,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
z = landAvg;
}
foreach (var tile in tiles)
foreach (var tile in Tiles.GetStaticAndMultiTiles(x, y))
{
var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
@ -400,33 +384,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
STArrayPool<Item>.Shared.Return(items, true);
}
/* This could probably be re-implemented if necessary (perhaps via an ITile interface?).
public List<Tile> GetTilesAt( Point2D p, bool items, bool land, bool statics )
{
List<Tile> list = new List<Tile>();
if (this == Internal)
return list;
if (land)
list.Add( Tiles.GetLandTile( p.m_X, p.m_Y ) );
if (statics)
list.AddRange( Tiles.GetStaticTiles( p.m_X, p.m_Y, true ) );
if (items)
{
Sector sector = GetSector( p );
foreach ( Item item in sector.Items )
if (item.AtWorldPoint( p.m_X, p.m_Y ))
list.Add( new StaticTile( (ushort)item.ItemID, (sbyte) item.Z ) );
}
return list;
}
*/
/// <summary>
/// Gets the highest surface that is lower than <paramref name="p" />.
/// </summary>
@ -460,11 +417,8 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
}
}
var staticTiles = Tiles.GetStaticTiles(p.X, p.Y, true);
for (var i = 0; i < staticTiles.Length; i++)
foreach (var tile in Tiles.GetStaticAndMultiTiles(p.X, p.Y))
{
var tile = staticTiles[i];
var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
if (id.Surface || id.Wet)
@ -862,14 +816,14 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return p;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanFit(
Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true,
bool requireSurface = true
Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true, bool requireSurface = true
) => CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, checkMobiles, requireSurface);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanFit(
Point2D p, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true,
bool requireSurface = true
Point2D p, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, bool requireSurface = true
) => CanFit(p.m_X, p.m_Y, z, height, checkBlocksFit, checkMobiles, requireSurface);
public bool CanFit(
@ -903,29 +857,26 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
hasSurface = true;
}
var staticTiles = Tiles.GetStaticTiles(x, y, true);
bool surface, impassable;
for (var i = 0; i < staticTiles.Length; ++i)
foreach (var tile in Tiles.GetStaticAndMultiTiles(x, y))
{
var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue];
var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
surface = id.Surface;
impassable = id.Impassable;
if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + height > staticTiles[i].Z)
if ((surface || impassable) && tile.Z + id.CalcHeight > z && z + height > tile.Z)
{
return false;
}
if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight)
if (surface && !impassable && z == tile.Z + id.CalcHeight)
{
hasSurface = true;
}
}
var sector = GetSector(x, y);
var mobs = sector.Mobiles;
foreach (var item in sector.Items)
{
@ -1122,8 +1073,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return false;
*/
var statics = Tiles.GetStaticTiles(point.m_X, point.m_Y, true);
var contains = false;
var ltID = landTile.ID;
@ -1132,26 +1081,11 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
contains = ltID == InvalidLandTiles[j];
}
if (contains && statics.Length == 0)
{
foreach (Item item in GetItemsInRange(point, 0))
{
if (item.Visible)
{
contains = false;
break;
}
}
bool foundStatic = false;
if (contains)
{
return false;
}
}
for (var j = 0; j < statics.Length; ++j)
foreach (var t in Tiles.GetStaticAndMultiTiles(point.m_X, point.m_Y))
{
var t = statics[j];
foundStatic = true;
var id = TileData.ItemTable[t.ID & TileData.MaxItemValue];
@ -1168,6 +1102,23 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return false;
}
}
if (contains && !foundStatic)
{
foreach (Item item in GetItemsAt(point))
{
if (item.Visible)
{
contains = false;
break;
}
}
if (contains)
{
return false;
}
}
}
var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1);