Skip to content

Commit

Permalink
Changed Dictionary<int, List<DtMeshTile>> to DtMeshTile[] to opti…
Browse files Browse the repository at this point in the history
…mize memory usage
  • Loading branch information
ikpil committed May 21, 2024
1 parent 9a03ade commit c7f03d0
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 129 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Changed `PolyQueryInvoker` to `DtActionPolyQuery`
- Changed `DtTileCacheBuilder` to a static class
- Changed `DtTileCacheLayerHeaderReader` to a static class
- Changed `Dictionary<int, List<DtMeshTile>>` to `DtMeshTile[]` to optimize memory usage

### Removed
- Nothing
Expand Down
1 change: 1 addition & 0 deletions src/DotRecast.Detour/DtMeshTile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class DtMeshTile
public DtLink[] links; // The tile links. [Size: dtMeshHeader::maxLinkCount]

public int flags; //< Tile flags. (See: #dtTileFlags)
public DtMeshTile next; //< The next free tile, or the next tile in the spatial grid.

public DtMeshTile(int index)
{
Expand Down
168 changes: 104 additions & 64 deletions src/DotRecast.Detour/DtNavMesh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public class DtNavMesh
private int m_tileLutSize; //< Tile hash lookup size (must be pot).
private int m_tileLutMask; // < Tile hash lookup mask.

private Dictionary<int, List<DtMeshTile>> m_posLookup; //< Tile hash lookup.
private LinkedList<DtMeshTile> m_nextFree; //< Freelist of tiles.
private DtMeshTile[] m_posLookup; //< Tile hash lookup.
private DtMeshTile m_nextFree; //< Freelist of tiles.
private DtMeshTile[] m_tiles; //< List of tiles.

/** The maximum number of vertices per navigation polygon. */
Expand All @@ -62,15 +62,16 @@ public DtStatus Init(DtNavMeshParams param, int maxVertsPerPoly)
if (0 == m_tileLutSize)
m_tileLutSize = 1;
m_tileLutMask = m_tileLutSize - 1;

m_tiles = new DtMeshTile[m_maxTiles];
m_posLookup = new Dictionary<int, List<DtMeshTile>>();
m_nextFree = new LinkedList<DtMeshTile>();
for (int i = 0; i < m_maxTiles; i++)
m_posLookup = new DtMeshTile[m_tileLutSize];
m_nextFree = null;
for (int i = m_maxTiles-1; i >= 0; --i)
{
m_tiles[i] = new DtMeshTile(i);
m_tiles[i].salt = 1;
m_nextFree.AddLast(m_tiles[i]);
m_tiles[i].next = m_nextFree;
m_nextFree = m_tiles[i];
}

return DtStatus.DT_SUCCESS;
Expand Down Expand Up @@ -384,15 +385,13 @@ public DtStatus AddTile(DtMeshData data, int flags, long lastRef, out long resul
DtMeshTile tile = null;
if (lastRef == 0)
{
// Make sure we could allocate a tile.
if (0 == m_nextFree.Count)
if (null != m_nextFree)
{
throw new Exception("Could not allocate a tile");
tile = m_nextFree;
m_nextFree = tile.next;
tile.next = null;
m_tileCount++;
}

tile = m_nextFree.First?.Value;
m_nextFree.RemoveFirst();
m_tileCount++;
}
else
{
Expand All @@ -405,14 +404,25 @@ public DtStatus AddTile(DtMeshData data, int flags, long lastRef, out long resul

// Try to find the specific tile id from the free list.
DtMeshTile target = m_tiles[tileIndex];
// Remove from freelist
if (!m_nextFree.Remove(target))
DtMeshTile prev = null;
tile = m_nextFree;

while (null != tile && tile != target)
{
// Could not find the correct location.
return DtStatus.DT_FAILURE | DtStatus.DT_OUT_OF_MEMORY;
prev = tile;
tile = tile.next;
}

tile = target;
// Could not find the correct location.
if (tile != target)
return DtStatus.DT_FAILURE | DtStatus.DT_OUT_OF_MEMORY;

// Remove from freelist
if (null == prev)
m_nextFree = tile.next;
else
prev.next = tile.next;

// Restore salt.
tile.salt = DecodePolyIdSalt(lastRef);
}
Expand All @@ -424,7 +434,10 @@ public DtStatus AddTile(DtMeshData data, int flags, long lastRef, out long resul
}

// Insert tile into the position lut.
GetTileListByPos(header.x, header.y).Add(tile);
int h = ComputeTileHash(header.x, header.y, m_tileLutMask);
tile.next = m_posLookup[h];
m_posLookup[h] = tile;


// Patch header pointers.
tile.data = data;
Expand All @@ -450,13 +463,19 @@ public DtStatus AddTile(DtMeshData data, int flags, long lastRef, out long resul
tile.flags = flags;

ConnectIntLinks(tile);

// Base off-mesh connections to their starting polygons and connect connections inside the tile.
BaseOffMeshLinks(tile);
ConnectExtOffMeshLinks(tile, tile, -1);

// Create connections with neighbour tiles.
const int MAX_NEIS = 32;
DtMeshTile[] neis = new DtMeshTile[MAX_NEIS];
int nneis;

// Connect with layers in current tile.
List<DtMeshTile> neis = GetTilesAt(header.x, header.y);
for (int j = 0; j < neis.Count; ++j)
nneis = GetTilesAt(header.x, header.y, neis, MAX_NEIS);
for (int j = 0; j < nneis; ++j)
{
if (neis[j] == tile)
{
Expand All @@ -472,8 +491,8 @@ public DtStatus AddTile(DtMeshData data, int flags, long lastRef, out long resul
// Connect with neighbour tiles.
for (int i = 0; i < 8; ++i)
{
neis = GetNeighbourTilesAt(header.x, header.y, i);
for (int j = 0; j < neis.Count; ++j)
nneis = GetNeighbourTilesAt(header.x, header.y, i, neis, MAX_NEIS);
for (int j = 0; j < nneis; ++j)
{
ConnectExtLinks(tile, neis[j], i);
ConnectExtLinks(neis[j], tile, DtUtils.OppositeTile(i));
Expand Down Expand Up @@ -516,30 +535,44 @@ public long RemoveTile(long refs)
}

// Remove tile from hash lookup.
GetTileListByPos(tile.data.header.x, tile.data.header.y).Remove(tile);
int h = ComputeTileHash(tile.data.header.x, tile.data.header.y, m_tileLutMask);
DtMeshTile prev = null;
DtMeshTile cur = m_posLookup[h];
while (null != cur)
{
if (cur == tile)
{
if (null != prev)
prev.next = cur.next;
else
m_posLookup[h] = cur.next;
break;
}

prev = cur;
cur = cur.next;
}

// Remove connections to neighbour tiles.
// Create connections with neighbour tiles.
const int MAX_NEIS = 32;
DtMeshTile[] neis = new DtMeshTile[MAX_NEIS];
int nneis = 0;

// Disconnect from other layers in current tile.
List<DtMeshTile> nneis = GetTilesAt(tile.data.header.x, tile.data.header.y);
foreach (DtMeshTile j in nneis)
nneis = GetTilesAt(tile.data.header.x, tile.data.header.y, neis, MAX_NEIS);
for (int j = 0; j < nneis; ++j)
{
if (j == tile)
{
continue;
}

UnconnectLinks(j, tile);
if (neis[j] == tile) continue;
UnconnectLinks(neis[j], tile);
}

// Disconnect from neighbour tiles.
for (int i = 0; i < 8; ++i)
{
nneis = GetNeighbourTilesAt(tile.data.header.x, tile.data.header.y, i);
foreach (DtMeshTile j in nneis)
nneis = GetNeighbourTilesAt(tile.data.header.x, tile.data.header.y, i, neis, MAX_NEIS);
for (int j = 0; j < nneis; ++j)
{
UnconnectLinks(j, tile);
UnconnectLinks(neis[j], tile);
}
}

Expand All @@ -557,7 +590,8 @@ public long RemoveTile(long refs)
}

// Add to free list.
m_nextFree.AddFirst(tile);
tile.next = m_nextFree;
m_nextFree = tile;
m_tileCount--;
return GetTileRef(tile);
}
Expand Down Expand Up @@ -1270,19 +1304,27 @@ private long FindNearestPolyInTile(DtMeshTile tile, RcVec3f center, RcVec3f half

DtMeshTile GetTileAt(int x, int y, int layer)
{
foreach (DtMeshTile tile in GetTileListByPos(x, y))
{
if (tile.data.header != null && tile.data.header.x == x && tile.data.header.y == y
&& tile.data.header.layer == layer)
// Find tile based on hash.
int h = ComputeTileHash(x, y, m_tileLutMask);
DtMeshTile tile = m_posLookup[h];
while (null != tile)
{
if (null != tile.data &&
null != tile.data.header &&
tile.data.header.x == x &&
tile.data.header.y == y &&
tile.data.header.layer == layer)
{
return tile;
}

tile = tile.next;
}

return null;
}

List<DtMeshTile> GetNeighbourTilesAt(int x, int y, int side)
int GetNeighbourTilesAt(int x, int y, int side, DtMeshTile[] tiles, int maxTiles)
{
int nx = x, ny = y;
switch (side)
Expand Down Expand Up @@ -1317,21 +1359,31 @@ List<DtMeshTile> GetNeighbourTilesAt(int x, int y, int side)
break;
}

return GetTilesAt(nx, ny);
return GetTilesAt(nx, ny, tiles, maxTiles);
}

public List<DtMeshTile> GetTilesAt(int x, int y)
public int GetTilesAt(int x, int y, DtMeshTile[] tiles, int maxTiles)
{
List<DtMeshTile> tiles = new List<DtMeshTile>();
foreach (DtMeshTile tile in GetTileListByPos(x, y))
int n = 0;

// Find tile based on hash.
int h = ComputeTileHash(x, y, m_tileLutMask);
DtMeshTile tile = m_posLookup[h];
while (null != tile)
{
if (tile.data.header != null && tile.data.header.x == x && tile.data.header.y == y)
if (null != tile.data &&
null != tile.data.header &&
tile.data.header.x == x &&
tile.data.header.y == y)
{
tiles.Add(tile);
if (n < maxTiles)
tiles[n++] = tile;
}

tile = tile.next;
}

return tiles;
return n;
}

public long GetTileRefAt(int x, int y, int layer)
Expand Down Expand Up @@ -1453,9 +1505,9 @@ public int GetTileCount()
return m_tileCount;
}

public int GetAvailableTileCount()
public bool IsAvailableTileCount()
{
return m_nextFree.Count;
return null != m_nextFree;
}

public DtStatus SetPolyFlags(long refs, int flags)
Expand Down Expand Up @@ -1613,18 +1665,6 @@ public RcVec3f GetPolyCenter(long refs)
return center;
}

private List<DtMeshTile> GetTileListByPos(int x, int z)
{
var tileHash = ComputeTileHash(x, z, m_tileLutMask);
if (!m_posLookup.TryGetValue(tileHash, out var tiles))
{
tiles = new List<DtMeshTile>();
m_posLookup.Add(tileHash, tiles);
}

return tiles;
}

public void ComputeBounds(out RcVec3f bmin, out RcVec3f bmax)
{
bmin = new RcVec3f(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
Expand Down
32 changes: 10 additions & 22 deletions src/DotRecast.Detour/DtNavMeshQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -764,39 +764,27 @@ public DtStatus QueryPolygons(RcVec3f center, RcVec3f halfExtents, IDtQueryFilte
// Find tiles the query touches.
RcVec3f bmin = RcVec3f.Subtract(center, halfExtents);
RcVec3f bmax = RcVec3f.Add(center, halfExtents);
foreach (var t in QueryTiles(center, halfExtents))
{
QueryPolygonsInTile(t, bmin, bmax, filter, query);
}

return DtStatus.DT_SUCCESS;
}

/**
* Finds tiles that overlap the search box.
*/
public IList<DtMeshTile> QueryTiles(RcVec3f center, RcVec3f halfExtents)
{
if (!center.IsFinite() || !halfExtents.IsFinite())
{
return RcImmutableArray<DtMeshTile>.Empty;
}

RcVec3f bmin = RcVec3f.Subtract(center, halfExtents);
RcVec3f bmax = RcVec3f.Add(center, halfExtents);
// Find tiles the query touches.
m_nav.CalcTileLoc(bmin, out var minx, out var miny);
m_nav.CalcTileLoc(bmax, out var maxx, out var maxy);

List<DtMeshTile> tiles = new List<DtMeshTile>();
const int MAX_NEIS = 32;
DtMeshTile[] neis = new DtMeshTile[MAX_NEIS];

for (int y = miny; y <= maxy; ++y)
{
for (int x = minx; x <= maxx; ++x)
{
tiles.AddRange(m_nav.GetTilesAt(x, y));
int nneis = m_nav.GetTilesAt(x, y, neis, MAX_NEIS);
for (int j = 0; j < nneis; ++j)
{
QueryPolygonsInTile(neis[j], bmin, bmax, filter, query);
}
}
}

return tiles;
return DtStatus.DT_SUCCESS;
}

/// @par
Expand Down
4 changes: 2 additions & 2 deletions src/DotRecast.Recast.Toolset/Tools/RcTileTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ public bool BuildTile(IInputGeomProvider geom, RcNavMeshBuildSettings settings,
tileTriCount = 0; // ...
tileMemUsage = 0; // ...

var availableTileCount = navMesh.GetAvailableTileCount();
if (0 >= availableTileCount)
var availableTile = navMesh.IsAvailableTileCount();
if (!availableTile)
{
return false;
}
Expand Down
Loading

0 comments on commit c7f03d0

Please sign in to comment.