egametang/ET · error · Exception
Out of storage
Error message
Out of storage
What it means
Thrown by DtTileCache.AddTile when the free-list of compressed tiles (m_nextFreeTile) is empty. The cache pre-allocates exactly maxTiles slots at construction and hands them out as tiles are added; once all are in use, no slot can be assigned and the add fails. It indicates the cache is at capacity, not a memory allocation failure.
Source
Thrown at Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs:264
// Make sure the location is free.
if (GetTileAt(header.tx, header.ty, header.tlayer) != null)
{
return 0;
}
// Allocate a tile.
DtCompressedTile tile = null;
if (m_nextFreeTile != null)
{
tile = m_nextFreeTile;
m_nextFreeTile = tile.next;
tile.next = null;
}
// Make sure we could allocate a tile.
if (tile == null)
{
throw new Exception("Out of storage");
}
// Insert tile into the position lut.
int h = DtNavMesh.ComputeTileHash(header.tx, header.ty, m_tileLutMask);
tile.next = m_posLookup[h];
m_posLookup[h] = tile;
// Init tile.
tile.header = header;
tile.data = data;
tile.compressed = Align4(buf.Position());
tile.flags = flags;
return GetTileRef(tile);
}
private int Align4(int i)
{View on GitHub (pinned to 5cab01f7a8)
Solutions
- Increase DtTileCacheParams.maxTiles to exceed the peak number of simultaneously live tiles (including obstacle-driven rebuilds).
- Ensure every load path has a matching RemoveTile on unload so the free-list is replenished.
- Drive DtTileCache.Update each frame so pending add/remove requests settle before the next batch of AddTile calls.
Example fix
// before: add without ever removing foreach (var data in loaded) cache.AddTile(data, flags, 0); // after: remove the tile at the same (tx,ty,layer) before re-adding, and size maxTiles to peak load long oldRef = cache.GetTileRef(tx, ty, layer); if (oldRef != 0) cache.RemoveTile(oldRef); cache.AddTile(data, flags, 0);
Defensive patterns
Strategy: validation
Validate before calling
// Track live tile count against capacity before adding
if (liveTileCount >= cacheMaxTiles)
throw new InvalidOperationException(
$"DtTileCache at capacity ({liveTileCount}/{cacheMaxTiles}); " +
"remove a tile or raise maxTiles.");
cache.AddTile(data, flags, 0); Try / catch
try { return cache.AddTile(data, flags, 0); }
catch (Exception e) when (e.Message == "Out of storage")
{ /* evict least-recent tile, then retry once, or report capacity */ } Prevention
- Size maxTiles to peak concurrent tiles including obstacle rebuilds.
- Always pair AddTile with RemoveTile on unload.
- Call DtTileCache.Update each frame so pending requests settle.
When it happens
Trigger: Calling DtTileCache.AddTile(...) more than DtTileCacheParams.maxTiles times without matching RemoveTile calls; loading a tile set whose tile count exceeds the configured maxTiles; streaming new tiles while old ones are never evicted.
Common situations: maxTiles sized too small for the streamed world; obstacle/tile updates queuing many tiles before the update loop drains them; a tile loaded twice because RemoveTile was skipped on unload; mismatch between the maxTiles the cache was built with and the tile count the loader assumes.
Related errors
- Too few salt bits: {m_saltBits}
- Buffer too small
- Could not allocate a tile
- ERR_LocationLockOwnerMismatch
- btenv not found key: {key} {typeof(T).FullName}
AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13).
Data as JSON: /api/errors/5df43a91d2664a84.
Report an issue: GitHub.