Skip to content

Commit

Permalink
Tile layer memory consumption (#797)
Browse files Browse the repository at this point in the history
* tile layers now need less memory

* reducing loading time when using base64 encoded layers
  • Loading branch information
stallratte authored May 10, 2024
1 parent 0cdd5bc commit ca5c08d
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 100 deletions.
42 changes: 30 additions & 12 deletions Nez.Portable/Assets/Tiled/Runtime/Layer.Runtime.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Nez.Tiled
{
public partial class TmxLayer : ITmxLayer
{
/// <summary>
/// gets the TmxLayerTile at the x/y coordinates. Note that these are tile coordinates not world coordinates!
/// </summary>
/// <returns>The tile.</returns>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
public TmxLayerTile GetTile(int x, int y) => Tiles[x + y * Width];

/// <summary>
/// gets the TmxLayerTile at the x/y coordinates. Note that these are tile coordinates not world coordinates!
/// </summary>
/// <returns>The tile.</returns>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
public TmxLayerTile GetTile(int x, int y)
{
Tiles.TryGetValue(Grid[x + y * Width], out var tmxLayerTile);

return tmxLayerTile;
}

public TmxLayerTile GetTile(int index)
{
Tiles.TryGetValue(Grid[index], out var tmxLayerTile);

return tmxLayerTile;
}

/// <summary>
/// gets the TmxLayerTile at the given world position
Expand All @@ -27,7 +40,7 @@ public TmxLayerTile GetTileAtWorldPosition(Vector2 pos)
/// </summary>
public List<Rectangle> GetCollisionRectangles()
{
var checkedIndexes = new bool?[Tiles.Length];
var checkedIndexes = new bool?[Grid.Length];
var rectangles = new List<Rectangle>();
var startCol = -1;
var index = -1;
Expand Down Expand Up @@ -132,10 +145,13 @@ public List<TmxLayerTile> GetTilesIntersectingBounds(Rectangle bounds)
/// call this method or update the TmxLayerTile.tileset manually!
/// </summary>
/// <returns>The tile.</returns>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="tile">Tile.</param>
public TmxLayerTile SetTile(TmxLayerTile tile)
public TmxLayerTile SetTile(int x, int y, TmxLayerTile tile)
{
Tiles[tile.X + tile.Y * Width] = tile;
Grid[x + y * Width] = tile.RawGid;
Tiles.Add(tile.RawGid, tile);
tile.Tileset = Map.GetTilesetForTileGid(tile.Gid);

return tile;
Expand All @@ -146,6 +162,8 @@ public TmxLayerTile SetTile(TmxLayerTile tile)
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
public void RemoveTile(int x, int y) => Tiles[x + y * Width] = null;
public void RemoveTile(int x, int y) {
Grid[x + y * Width] = 0;
}
}
}
80 changes: 55 additions & 25 deletions Nez.Portable/Assets/Tiled/Runtime/TiledMapLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,22 +322,30 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL

var xData = xLayer.Element("data");
var encoding = (string)xData.Attribute("encoding");

layer.Tiles = new TmxLayerTile[width * height];

layer.Grid = new uint[width * height];
layer.Tiles = new Dictionary<uint, TmxLayerTile>();

if (encoding == "base64")
{
var decodedStream = new TmxBase64Data(xData);
var stream = decodedStream.Data;


var index = 0;
using (var br = new BinaryReader(stream))
{
for (var j = 0; j < height; j++)
{
for (var i = 0; i < width; i++)
{
var gid = br.ReadUInt32();
layer.Tiles[index++] = gid != 0 ? new TmxLayerTile(map, gid, i, j) : null;
using (var stream = decodedStream.Data) {
using (var br = new BinaryReader(stream)) {
const int uintSizeInBytes = sizeof(uint);

var buffer = new byte[uintSizeInBytes * 1024];
int bytesRead;

while ((bytesRead = br.Read(buffer, 0, buffer.Length)) > 0) {
var numberOfUIntValuesRead = bytesRead / uintSizeInBytes;

for (var i = 0; i < numberOfUIntValuesRead; i++) {
var gid = BitConverter.ToUInt32(buffer, i * uintSizeInBytes);
AddTile(layer, map, gid);
layer.Grid[index++] = gid;
}
}
}
}
Expand All @@ -346,13 +354,19 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
{
var csvData = xData.Value;
int k = 0;
foreach (var s in csvData.Split(','))

int startIndex = 0;
for (var i = 0; i < csvData.Length; i++)
{
var gid = uint.Parse(s.Trim());
var x = k % width;
var y = k / width;

layer.Tiles[k++] = gid != 0 ? new TmxLayerTile(map, gid, x, y) : null;
if (csvData[i] == ',')
{
var gid = ParseString(csvData, startIndex, i - startIndex);

AddTile(layer, map, gid);
layer.Grid[k++] = gid;

startIndex = i + 1;
}
}
}
else if (encoding == null)
Expand All @@ -361,11 +375,9 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
foreach (var e in xData.Elements("tile"))
{
var gid = (uint?)e.Attribute("gid") ?? 0;

var x = k % width;
var y = k / width;

layer.Tiles[k++] = gid != 0 ? new TmxLayerTile(map, gid, x, y) : null;

AddTile(layer, map, gid);
layer.Grid[k++] = gid;
}
}
else throw new Exception("TmxLayer: Unknown encoding.");
Expand All @@ -375,6 +387,24 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
return layer;
}

private static void AddTile(TmxLayer layer, TmxMap map, uint gid) {
if (gid != 0 && !layer.Tiles.ContainsKey(gid))
{
layer.Tiles.Add(gid, new TmxLayerTile(map, gid));
}
}

private static uint ParseString(string str, int startIndex, int length)
{
uint result = 0;
for (int i = startIndex; i < startIndex + length; i++)
{
if(char.IsDigit(str[i]))
result = result * 10u + (uint)(str[i] - '0');
}
return result;
}

public static TmxObjectGroup LoadTmxObjectGroup(this TmxObjectGroup group, TmxMap map, XElement xObjectGroup)
{
group.Map = map;
Expand Down Expand Up @@ -445,7 +475,7 @@ public static TmxObject LoadTmxObject(this TmxObject obj, TmxMap map, XElement x

if (xGid != null)
{
obj.Tile = new TmxLayerTile(map, (uint)xGid, Convert.ToInt32(Math.Round(obj.X)), Convert.ToInt32(Math.Round(obj.Y)));
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.ObjectType = TmxObjectType.Tile;
}
else if (xEllipse != null)
Expand Down Expand Up @@ -499,7 +529,7 @@ public static TmxObject LoadTmxObjectFromTemplate(this TmxObject obj, TmxMap map

if (xGid != null)
{
obj.Tile = new TmxLayerTile(map, (uint)xGid, Convert.ToInt32(Math.Round(obj.X)), Convert.ToInt32(Math.Round(obj.Y)));
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.ObjectType = TmxObjectType.Tile;
}
else if (xEllipse != null)
Expand Down
33 changes: 17 additions & 16 deletions Nez.Portable/Assets/Tiled/Runtime/TiledRendering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,16 @@ public static void RenderLayer(TmxLayer layer, Batcher batcher, Vector2 position
var color = Color.White;
color.A = (byte)(layer.Opacity * 255);

for (var i = 0; i < layer.Tiles.Length; i++)
for (var i = 0; i < layer.Grid.Length; i++)
{
var tile = layer.Tiles[i];
var tile = layer.GetTile(i);
if (tile == null)
continue;

RenderTile(tile, batcher, position,
var x = i % layer.Map.TileWidth;
var y = i / layer.Map.TileWidth;

RenderTile(tile,x ,y, batcher, position,
scale, tileWidth, tileHeight,
color, layerDepth, layer.Map.Orientation,
layer.Map.Width, layer.Map.Height);
Expand Down Expand Up @@ -118,7 +121,7 @@ public static void RenderLayer(TmxLayer layer, Batcher batcher, Vector2 position
{
var tile = layer.GetTile(x, y);
if (tile != null)
RenderTile(tile, batcher, position,
RenderTile(tile, x ,y, batcher, position,
scale, tileWidth, tileHeight,
color, layerDepth, layer.Map.Orientation,
layer.Map.Width, layer.Map.Height);
Expand Down Expand Up @@ -180,7 +183,7 @@ private static (Point, Point, RectangleF) GetLayerCullBounds(TmxLayer layer, Vec
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RenderTile(TmxLayerTile tile, Batcher batcher, Vector2 position,
public static void RenderTile(TmxLayerTile tile, int tileX, int tileY, Batcher batcher, Vector2 position,
Vector2 scale, float tileWidth, float tileHeight,
Color color, float layerDepth, OrientationType orientation,
int mapWidth, int mapHeight)
Expand All @@ -202,23 +205,23 @@ public static void RenderTile(TmxLayerTile tile, Batcher batcher, Vector2 positi
switch (orientation)
{
case OrientationType.Hexagonal:
bool isEvenRow = tile.Y % 2 == 0;
bool isEvenRow = tileY % 2 == 0;

if (isEvenRow)
{
tx = tile.X * tileWidth;
ty = tile.Y * tileHeight * 0.75f;
tx = tileX * tileWidth;
ty = tileY * tileHeight * 0.75f;
}
else
{
tx = (tileWidth / 2) + (tile.X * tileWidth);
ty = tile.Y * tileHeight * 0.75f;
tx = (tileWidth / 2) + (tileX * tileWidth);
ty = tileY * tileHeight * 0.75f;
}

break;
case OrientationType.Isometric:
tx = tile.X * tileWidth / 2 - tile.Y * tileWidth / 2 + (mapHeight - 1) * tileWidth / 2;
ty = tile.Y * tileHeight / 2 + tile.X * tileHeight / 2;
tx = tileX * tileWidth / 2 - tileY * tileWidth / 2 + (mapHeight - 1) * tileWidth / 2;
ty = tileY * tileHeight / 2 + tileX * tileHeight / 2;
break;
case OrientationType.Staggered:
throw new NotImplementedException(
Expand All @@ -228,8 +231,8 @@ public static void RenderTile(TmxLayerTile tile, Batcher batcher, Vector2 positi
case OrientationType.Unknown:
case OrientationType.Orthogonal:
default:
tx = tile.X * tileWidth;
ty = tile.Y * tileHeight;
tx = tileX * tileWidth;
ty = tileY * tileHeight;
break;
}

Expand Down Expand Up @@ -322,8 +325,6 @@ public static void RenderObjectGroup(TmxObjectGroup objGroup, Batcher batcher, V
batcher.DrawPixel(pos, objGroup.Color, (int)size);
goto default;
case TmxObjectType.Tile:
var tx = obj.Tile.X * objGroup.Map.TileWidth * scale.X;
var ty = obj.Tile.Y * objGroup.Map.TileHeight * scale.Y;

var spriteEffects = SpriteEffects.None;
if (obj.Tile.HorizontalFlip)
Expand Down
29 changes: 11 additions & 18 deletions Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,17 @@ public partial class TmxLayer : ITmxLayer
/// height in tiles for this layer. Always the same as the map height for fixed-size maps.
/// </summary>
public int Height;
public TmxLayerTile[] Tiles;

public uint[] Grid;
public Dictionary<uint, TmxLayerTile> Tiles;

/// <summary>
/// returns the TmxLayerTile with gid. This is a slow lookup so cache it!
/// </summary>
/// <param name="gid"></param>
/// <returns></returns>
public TmxLayerTile GetTileWithGid(int gid)
{
for (var i = 0; i < Tiles.Length; i++)
{
if (Tiles[i] != null && Tiles[i].Gid == gid)
return Tiles[i];
}
return null;
public TmxLayerTile GetTileWithGid(uint gid) {
Tiles.TryGetValue(gid, out var result);
return result;
}
}

Expand All @@ -52,10 +48,9 @@ public class TmxLayerTile
const uint FLIPPED_DIAGONALLY_FLAG = 0x20000000;

public TmxTileset Tileset;
// GID which still contains the flip flags.
public uint RawGid;
public int Gid;
public int X;
public int Y;
public Vector2 Position => new Vector2(X, Y);
public bool HorizontalFlip;
public bool VerticalFlip;
public bool DiagonalFlip;
Expand Down Expand Up @@ -88,12 +83,10 @@ public TmxTilesetTile TilesetTile
return Tileset.Tiles[_tilesetTileIndex.Value];
}
}

public TmxLayerTile(TmxMap map, uint id, int x, int y)
public TmxLayerTile(TmxMap map, uint rawGid)
{
X = x;
Y = y;
var rawGid = id;
RawGid = rawGid;

// Scan for tile flip bit flags
bool flip;
Expand Down
4 changes: 2 additions & 2 deletions Nez.Portable/Assets/Tiled/TiledTypes/TmxLayerTileExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ public static Edge GetHighestSlopeEdge(this TmxLayerTile self)
/// returns the nearest edge to worldPosition
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Edge GetNearestEdge(this TmxLayerTile self, int worldPosition)
public static Edge GetNearestEdge(this TmxLayerTile self, int x, int worldPosition)
{
var tileWidth = self.Tileset.Map.TileWidth;
var tileMiddleWorldPosition = self.X * tileWidth + tileWidth / 2;
var tileMiddleWorldPosition = x * tileWidth + tileWidth / 2;
return worldPosition < tileMiddleWorldPosition ? Edge.Left : Edge.Right;
}
}
Expand Down
Loading

0 comments on commit ca5c08d

Please sign in to comment.