Skip to content

Commit

Permalink
Tile layer memory consumption (prime31#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 and NowSayPillow committed Aug 2, 2024
1 parent 66470fd commit bde1ede
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 70 deletions.
29 changes: 6 additions & 23 deletions Nez.Portable/Assets/Tiled/Runtime/Layer.Runtime.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

Expand All @@ -15,7 +14,9 @@ public partial class TmxLayer : ITmxLayer
/// <param name="y">The y coordinate.</param>
public TmxLayerTile GetTile(int x, int y)
{
return GetTile(x + y * Width);
Tiles.TryGetValue(Grid[x + y * Width], out var tmxLayerTile);

return tmxLayerTile;
}

public TmxLayerTile GetTile(int index)
Expand Down Expand Up @@ -146,29 +147,11 @@ public List<TmxLayerTile> GetTilesIntersectingBounds(Rectangle bounds)
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="tile">Tile.</param>
[Obsolete("Please use SetTile with gid instead.")]
public TmxLayerTile SetTile(int x, int y, TmxLayerTile tile)
{
return SetTile(x, y, tile.Gid, tile.HorizontalFlip, tile.VerticalFlip, tile.DiagonalFlip);
}

/// <summary>
/// Sets the tile at position x, y.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="gid">Global Tile ID (without the flip flags)</param>
/// <param name="flipHorizontally">Should the tile be flipped horizontally?</param>
/// <param name="flipVertically">Should the tile be flipped vertically?</param>
/// <param name="flipDiagonally">Should the tile be flipped diagonally?</param>
/// <returns>The tile.</returns>
public TmxLayerTile SetTile(int x, int y, int gid, bool flipHorizontally = false, bool flipVertically = false, bool flipDiagonally = false)
{
if (gid == 0) return null;

uint rawGid = TmxLayerTile.GetRawGid(gid, flipHorizontally, flipVertically, flipDiagonally);

Grid[x + y * Width] = rawGid;
Grid[x + y * Width] = tile.RawGid;
Tiles.Add(tile.RawGid, tile);
tile.Tileset = Map.GetTilesetForTileGid(tile.Gid);

TmxLayerTile tileToSet;

Expand Down
62 changes: 26 additions & 36 deletions Nez.Portable/Assets/Tiled/Runtime/TiledMapLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,30 +323,26 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL

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

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

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

var index = 0;
using (var stream = decodedStream.Data)
{
using (var br = new BinaryReader(stream))
{
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)
{
while ((bytesRead = br.Read(buffer, 0, buffer.Length)) > 0) {
var numberOfUIntValuesRead = bytesRead / uintSizeInBytes;

for (var i = 0; i < numberOfUIntValuesRead; i++)
{

for (var i = 0; i < numberOfUIntValuesRead; i++) {
var gid = BitConverter.ToUInt32(buffer, i * uintSizeInBytes);
AddTile(layer, map, gid);
layer.Grid[index++] = gid;
Expand All @@ -358,38 +354,31 @@ public static TmxLayer LoadTmxLayer(this TmxLayer layer, TmxMap map, XElement xL
else if (encoding == "csv")
{
var csvData = xData.Value;
var k = 0;
var startIndex = 0;
uint gid;

for (var i = 0; i < csvData.Length; i++)
int k = 0;

int startIndex = 0;
for (var i = 0; i < csvData.Length; i++)
{
if (csvData[i] == ',')
{
gid = ParseString(csvData, startIndex, i - startIndex);

var gid = ParseString(csvData, startIndex, i - startIndex);
AddTile(layer, map, gid);
layer.Grid[k++] = gid;

layer.Grid[k++] = gid;
startIndex = i + 1;
}
}

// Add remaining uid
gid = ParseString(csvData, startIndex, csvData.Length - 1 - startIndex);

AddTile(layer, map, gid);
layer.Grid[k] = gid;
}
else if (encoding == null)
{
int k = 0;
foreach (var e in xData.Elements("tile"))
{
var gid = (uint?)e.Attribute("gid") ?? 0;

AddTile(layer, map, gid);
layer.Grid[k++] = gid;
layer.Grid[k++] = gid;
}
}
else throw new Exception("TmxLayer: Unknown encoding.");
Expand All @@ -399,25 +388,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))
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)
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]))
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 @@ -488,6 +476,7 @@ public static TmxObject LoadTmxObject(this TmxObject obj, TmxMap map, XElement x

if (xGid != null)
{
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.ObjectType = TmxObjectType.Tile;
}
Expand Down Expand Up @@ -542,6 +531,7 @@ public static TmxObject LoadTmxObjectFromTemplate(this TmxObject obj, TmxMap map

if (xGid != null)
{
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.Tile = new TmxLayerTile(map, (uint)xGid);
obj.ObjectType = TmxObjectType.Tile;
}
Expand Down
22 changes: 17 additions & 5 deletions Nez.Portable/Assets/Tiled/TiledTypes/Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ public partial class TmxLayer : ITmxLayer
public uint[] Grid;
public Dictionary<uint, 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(uint gid) {
Tiles.TryGetValue(gid, out var result);
return result;
public TmxLayerTile GetTileWithGid(uint gid) {
Tiles.TryGetValue(gid, out var result);
return result;
Expand All @@ -47,11 +53,13 @@ public class TmxLayerTile
const uint FLIPPED_VERTICALLY_FLAG = 0x40000000;
const uint FLIPPED_DIAGONALLY_FLAG = 0x20000000;

public readonly TmxTileset Tileset;
public readonly int Gid;
public readonly bool HorizontalFlip;
public readonly bool VerticalFlip;
public readonly bool DiagonalFlip;
public TmxTileset Tileset;
// GID which still contains the flip flags.
public uint RawGid;
public int Gid;
public bool HorizontalFlip;
public bool VerticalFlip;
public bool DiagonalFlip;

int? _tilesetTileIndex;

Expand Down Expand Up @@ -82,8 +90,12 @@ public TmxTilesetTile TilesetTile
}
}

public TmxLayerTile(TmxMap map, uint rawGid)

public TmxLayerTile(TmxMap map, uint rawGid)
{
RawGid = rawGid;

// Scan for tile flip bit flags
bool flip;
flip = (rawGid & FLIPPED_HORIZONTALLY_FLAG) != 0;
Expand Down
33 changes: 27 additions & 6 deletions Nez.Portable/ECS/Components/Physics/TiledMapMover.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ public override string ToString()

/// <summary>
/// temporary storage for all the coordinates of tiles that intersect the bounds being checked
/// temporary storage for all the coordinates of tiles that intersect the bounds being checked
/// </summary>
List<Point> _collidingTilesCoordinates = new List<Point>();
List<Point> _collidingTilesCoordinates = new List<Point>();

/// <summary>
/// temporary storage to avoid having to pass it around
Expand Down Expand Up @@ -253,25 +255,28 @@ bool TestMapCollision(Rectangle collisionRect, Edge direction, CollisionState co
var shouldTestSlopes = side.IsVertical();
PopulateCollidingTiles(collisionRect, direction);

for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
{
var collidingTile = CollisionLayer.GetTile(_collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y);

if (collidingTile == null)
continue;

// disregard horizontal collisions with tiles on the same row as a slope if the last tile we were grounded on was a slope.
// the y collision response will push us up on the slope.
if (direction.IsHorizontal() && collisionState._lastGroundTile != null &&
collisionState._lastGroundTile.IsSlope() && IsSlopeCollisionRow(_collidingTilesCoordinates[i].Y))
collisionState._lastGroundTile.IsSlope() && IsSlopeCollisionRow(_collidingTilesCoordinates[i].Y))
continue;

if (TestTileCollision(collidingTile, _collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y, side, perpindicularPosition, leadingPosition,
if (TestTileCollision(collidingTile, _collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y,side, perpindicularPosition, leadingPosition,
shouldTestSlopes, out collisionResponse))
{
// store off our last ground tile if we collided below
if (direction == Edge.Bottom)
{
collisionState._lastGroundTile = collidingTile;
collisionState._lastGroundTile = collidingTile;
collisionState.IsGroundedOnOneWayPlatform = collisionState._lastGroundTile.IsOneWayPlatform();
}
Expand All @@ -285,13 +290,14 @@ bool TestMapCollision(Rectangle collisionRect, Edge direction, CollisionState co
// if grounded on a slope and intersecting a slope or if grounded on a wall and intersecting a tall slope we go sticky.
// tall slope here means one where the the slopeTopLeft/Right is 0, i.e. it connects to a wall
var isHighSlopeNearest = collidingTile.IsSlope() &&
collidingTile.GetNearestEdge(_collidingTilesCoordinates[i].X, perpindicularPosition) ==
collidingTile.GetHighestSlopeEdge();
collidingTile.GetNearestEdge(perpindicularPosition, _collidingTilesCoordinates[i].X) ==
collidingTile.GetHighestSlopeEdge();
if ((collisionState._lastGroundTile.IsSlope() && collidingTile.IsSlope()) ||
(!collisionState._lastGroundTile.IsSlope() && isHighSlopeNearest))
{
// store off our last ground tile if we collided below
collisionState._lastGroundTile = collidingTile;
collisionState._lastGroundTile = collidingTile;
return true;
}
}
Expand All @@ -307,10 +313,11 @@ bool TestMapCollision(Rectangle collisionRect, Edge direction, CollisionState co
/// <param name="rowY">the row to check</param>
bool IsSlopeCollisionRow(int rowY)
{
for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
for (var i = 0; i < _collidingTilesCoordinates.Count; i++)
{
var collidingTile = CollisionLayer.GetTile(_collidingTilesCoordinates[i].X, _collidingTilesCoordinates[i].Y);

if (collidingTile != null && collidingTile.IsSlope() && _collidingTilesCoordinates[i].Y == rowY)
return true;
}
Expand All @@ -325,11 +332,14 @@ bool IsSlopeCollisionRow(int rowY)
/// <param name="tile">Tile.</param>
/// /// <param name="x">x position of the tile.</param>
/// /// <param name="y">y position of the tile..</param>
/// /// <param name="x">x position of the tile.</param>
/// /// <param name="y">y position of the tile..</param>
/// <param name="edgeToTest">the opposite side of movement, the side the leading edge will collide with</param>
/// <param name="perpindicularPosition">Perpindicular position.</param>
/// <param name="leadingPosition">Leading position.</param>
/// <param name="shouldTestSlopes">Should test slopes.</param>
/// <param name="collisionResponse">Collision response.</param>
bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int perpindicularPosition, int leadingPosition,
bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int perpindicularPosition, int leadingPosition,
bool shouldTestSlopes, out int collisionResponse)
{
Expand All @@ -344,6 +354,7 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per

// our response should be the top of the platform
collisionResponse = TiledMap.TileToWorldPositionX(y);
collisionResponse = TiledMap.TileToWorldPositionX(y);
return _boxColliderBounds.Bottom <= collisionResponse;
}

Expand All @@ -353,7 +364,7 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
// and we were not intesecting the tile before moving.
// this prevents clipping through a tile when hitting its edge: -> |\
if (edgeToTest.IsHorizontal() && tile.IsSlope() &&
tile.GetNearestEdge(x, leadingPosition) == tile.GetHighestSlopeEdge())
tile.GetNearestEdge(leadingPosition, x) == tile.GetHighestSlopeEdge())
{
var moveDir = edgeToTest.OppositeEdge();
var leadingPositionPreMovement = _boxColliderBounds.GetSide(moveDir);
Expand All @@ -362,6 +373,8 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
var tileX = moveDir == Edge.Right
? TiledMap.TileToWorldPositionX(x)
: TiledMap.TileToWorldPositionX(x + 1);
? TiledMap.TileToWorldPositionX(x)
: TiledMap.TileToWorldPositionX(x + 1);

// using the edge before movement, we see if we were colliding before moving.
var wasCollidingBeforeMove = moveDir == Edge.Right
Expand All @@ -378,15 +391,19 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
switch (edgeToTest)
{
case Edge.Top:
collisionResponse = TiledMap.TileToWorldPositionY(y);
collisionResponse = TiledMap.TileToWorldPositionY(y);
break;
case Edge.Bottom:
collisionResponse = TiledMap.TileToWorldPositionY(y + 1);
collisionResponse = TiledMap.TileToWorldPositionY(y + 1);
break;
case Edge.Left:
collisionResponse = TiledMap.TileToWorldPositionX(x);
collisionResponse = TiledMap.TileToWorldPositionX(x);
break;
case Edge.Right:
collisionResponse = TiledMap.TileToWorldPositionX(x + 1);
collisionResponse = TiledMap.TileToWorldPositionX(x + 1);
break;
}
Expand All @@ -396,6 +413,8 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per

if (shouldTestSlopes)
{
var tileWorldX = TiledMap.TileToWorldPositionX(x);
var tileWorldY = TiledMap.TileToWorldPositionX(y);
var tileWorldX = TiledMap.TileToWorldPositionX(x);
var tileWorldY = TiledMap.TileToWorldPositionX(y);
var slope = tile.GetSlope();
Expand Down Expand Up @@ -430,6 +449,7 @@ bool TestTileCollision(TmxLayerTile tile, int x, int y, Edge edgeToTest, int per
/// <param name="direction">Direction.</param>
void PopulateCollidingTiles(Rectangle bounds, Edge direction)
{
_collidingTilesCoordinates.Clear();
_collidingTilesCoordinates.Clear();
var isHorizontal = direction.IsHorizontal();
var primaryAxis = isHorizontal ? Axis.X : Axis.Y;
Expand Down Expand Up @@ -458,6 +478,7 @@ void PopulateCollidingTiles(Rectangle bounds, Edge direction)
var col = isHorizontal ? primary : secondary;
var row = !isHorizontal ? primary : secondary;
_collidingTilesCoordinates.Add(new Point(col, row));
_collidingTilesCoordinates.Add(new Point(col, row));

#if DEBUG_MOVER
if(direction.IsHorizontal())
Expand Down

0 comments on commit bde1ede

Please sign in to comment.