From 453200bbb2f501ebd6b9102c02ae960aa2fa2b0f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 3 Aug 2024 13:51:38 +0200 Subject: [PATCH 1/6] Move to readers instead --- .../TmxSerializer/TmxSerializer.MapTests.cs | 19 +- DotTiled/Model/Map.cs | 2 +- DotTiled/Serialization/IMapReader.cs | 8 + DotTiled/Serialization/ITemplateReader.cs | 8 + DotTiled/Serialization/ITilesetReader.cs | 8 + .../Tmx}/ExtensionsXmlReader.cs | 0 DotTiled/Serialization/Tmx/Tmx.Chunk.cs | 28 ++ DotTiled/Serialization/Tmx/Tmx.Data.cs | 122 +++++++ DotTiled/Serialization/Tmx/Tmx.Helpers.cs | 26 ++ DotTiled/Serialization/Tmx/Tmx.Map.cs | 102 ++++++ DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs | 309 +++++++++++++++++ DotTiled/Serialization/Tmx/Tmx.Properties.cs | 54 +++ DotTiled/Serialization/Tmx/Tmx.TileLayer.cs | 148 ++++++++ DotTiled/Serialization/Tmx/Tmx.Tileset.cs | 316 ++++++++++++++++++ DotTiled/Serialization/Tmx/TmxMapReader.cs | 60 ++++ .../Serialization/Tmx/TsxTilesetReader.cs | 50 +++ .../Serialization/Tmx/TxTemplateReader.cs | 55 +++ 17 files changed, 1313 insertions(+), 2 deletions(-) create mode 100644 DotTiled/Serialization/IMapReader.cs create mode 100644 DotTiled/Serialization/ITemplateReader.cs create mode 100644 DotTiled/Serialization/ITilesetReader.cs rename DotTiled/{TmxSerializer => Serialization/Tmx}/ExtensionsXmlReader.cs (100%) create mode 100644 DotTiled/Serialization/Tmx/Tmx.Chunk.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.Data.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.Helpers.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.Map.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.Properties.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.TileLayer.cs create mode 100644 DotTiled/Serialization/Tmx/Tmx.Tileset.cs create mode 100644 DotTiled/Serialization/Tmx/TmxMapReader.cs create mode 100644 DotTiled/Serialization/Tmx/TsxTilesetReader.cs create mode 100644 DotTiled/Serialization/Tmx/TxTemplateReader.cs diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs index 7d513e1..5a54e7c 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs +++ b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs @@ -64,7 +64,24 @@ public partial class TmxSerializerMapTests externalTemplateResolver); // Act - var map = tmxSerializer.DeserializeMap(reader); + + static Template ResolveTemplate(string source) + { + using var xmlTemplateReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Template.{source}"); + using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); + return templateReader.ReadTemplate(); + } + + static Tileset ResolveTileset(string source) + { + using var xmlTilesetReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Tileset.{source}"); + using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); + return tilesetReader.ReadTileset(); + } + + var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate); + + var map = mapReader.ReadMap(); var raw = tmxSerializer.DeserializeMap(testDataFileText); // Assert diff --git a/DotTiled/Model/Map.cs b/DotTiled/Model/Map.cs index 99868b1..246f21c 100644 --- a/DotTiled/Model/Map.cs +++ b/DotTiled/Model/Map.cs @@ -60,5 +60,5 @@ public class Map // Any number of public List Tilesets { get; set; } = []; public List Layers { get; set; } = []; - // public List Groups { get; set; } = []; + public List Groups { get; set; } = []; } diff --git a/DotTiled/Serialization/IMapReader.cs b/DotTiled/Serialization/IMapReader.cs new file mode 100644 index 0000000..97c1fd7 --- /dev/null +++ b/DotTiled/Serialization/IMapReader.cs @@ -0,0 +1,8 @@ +using System; + +namespace DotTiled; + +public interface IMapReader : IDisposable +{ + Map ReadMap(); +} diff --git a/DotTiled/Serialization/ITemplateReader.cs b/DotTiled/Serialization/ITemplateReader.cs new file mode 100644 index 0000000..8de77bc --- /dev/null +++ b/DotTiled/Serialization/ITemplateReader.cs @@ -0,0 +1,8 @@ +using System; + +namespace DotTiled; + +public interface ITemplateReader : IDisposable +{ + Template ReadTemplate(); +} diff --git a/DotTiled/Serialization/ITilesetReader.cs b/DotTiled/Serialization/ITilesetReader.cs new file mode 100644 index 0000000..37f5257 --- /dev/null +++ b/DotTiled/Serialization/ITilesetReader.cs @@ -0,0 +1,8 @@ +using System; + +namespace DotTiled; + +public interface ITilesetReader : IDisposable +{ + Tileset ReadTileset(); +} diff --git a/DotTiled/TmxSerializer/ExtensionsXmlReader.cs b/DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs similarity index 100% rename from DotTiled/TmxSerializer/ExtensionsXmlReader.cs rename to DotTiled/Serialization/Tmx/ExtensionsXmlReader.cs diff --git a/DotTiled/Serialization/Tmx/Tmx.Chunk.cs b/DotTiled/Serialization/Tmx/Tmx.Chunk.cs new file mode 100644 index 0000000..9d06082 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.Chunk.cs @@ -0,0 +1,28 @@ +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static Chunk ReadChunk(XmlReader reader, DataEncoding? encoding, DataCompression? compression) + { + var x = reader.GetRequiredAttributeParseable("x"); + var y = reader.GetRequiredAttributeParseable("y"); + var width = reader.GetRequiredAttributeParseable("width"); + var height = reader.GetRequiredAttributeParseable("height"); + + var usesTileChildrenInsteadOfRawData = encoding is null; + if (usesTileChildrenInsteadOfRawData) + { + var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", reader); + var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags); + return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags }; + } + else + { + var globalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression); + var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags); + return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags }; + } + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Data.cs b/DotTiled/Serialization/Tmx/Tmx.Data.cs new file mode 100644 index 0000000..85598e0 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.Data.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static Data ReadData(XmlReader reader, bool usesChunks) + { + var encoding = reader.GetOptionalAttributeEnum("encoding", e => e switch + { + "csv" => DataEncoding.Csv, + "base64" => DataEncoding.Base64, + _ => throw new XmlException("Invalid encoding") + }); + var compression = reader.GetOptionalAttributeEnum("compression", c => c switch + { + "gzip" => DataCompression.GZip, + "zlib" => DataCompression.ZLib, + "zstd" => DataCompression.ZStd, + _ => throw new XmlException("Invalid compression") + }); + + if (usesChunks) + { + var chunks = reader + .ReadList("data", "chunk", (r) => ReadChunk(r, encoding, compression)) + .ToArray(); + return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks }; + } + + var usesTileChildrenInsteadOfRawData = encoding is null && compression is null; + if (usesTileChildrenInsteadOfRawData) + { + var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", reader); + var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags); + return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null }; + } + + var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression); + var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags); + return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null }; + } + + internal static (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs) + { + var clearedGlobalTileIDs = new uint[globalTileIDs.Length]; + var flippingFlags = new FlippingFlags[globalTileIDs.Length]; + for (var i = 0; i < globalTileIDs.Length; i++) + { + var gid = globalTileIDs[i]; + var flags = gid & 0xF0000000u; + flippingFlags[i] = (FlippingFlags)flags; + clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu; + } + + return (clearedGlobalTileIDs, flippingFlags); + } + + internal static uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader) + { + return reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable("gid") ?? 0).ToArray(); + } + + internal static uint[] ReadRawData(XmlReader reader, DataEncoding encoding, DataCompression? compression) + { + var data = reader.ReadElementContentAsString(); + if (encoding == DataEncoding.Csv) + return ParseCsvData(data); + + using var bytes = new MemoryStream(Convert.FromBase64String(data)); + if (compression is null) + return ReadMemoryStreamAsInt32Array(bytes); + + var decompressed = compression switch + { + DataCompression.GZip => DecompressGZip(bytes), + DataCompression.ZLib => DecompressZLib(bytes), + DataCompression.ZStd => throw new NotSupportedException("ZStd compression is not supported."), + _ => throw new XmlException("Invalid compression") + }; + + return decompressed; + } + + internal static uint[] ParseCsvData(string data) + { + var values = data + .Split((char[])['\n', '\r', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(uint.Parse) + .ToArray(); + return values; + } + + internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream) + { + var finalValues = new List(); + var int32Bytes = new byte[4]; + while (stream.Read(int32Bytes, 0, 4) == 4) + { + var value = BitConverter.ToUInt32(int32Bytes, 0); + finalValues.Add(value); + } + return finalValues.ToArray(); + } + + internal static uint[] DecompressGZip(MemoryStream stream) + { + using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress); + return ReadMemoryStreamAsInt32Array(decompressedStream); + } + + internal static uint[] DecompressZLib(MemoryStream stream) + { + using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress); + return ReadMemoryStreamAsInt32Array(decompressedStream); + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Helpers.cs b/DotTiled/Serialization/Tmx/Tmx.Helpers.cs new file mode 100644 index 0000000..bfc04f4 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.Helpers.cs @@ -0,0 +1,26 @@ +using System; + +namespace DotTiled; + +internal partial class Tmx +{ + private static class Helpers + { + public static void SetAtMostOnce(ref T? field, T value, string fieldName) + { + if (field is not null) + throw new InvalidOperationException($"{fieldName} already set"); + + field = value; + } + + public static void SetAtMostOnceUsingCounter(ref T? field, T value, string fieldName, ref int counter) + { + if (counter > 0) + throw new InvalidOperationException($"{fieldName} already set"); + + field = value; + counter++; + } + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Map.cs b/DotTiled/Serialization/Tmx/Tmx.Map.cs new file mode 100644 index 0000000..d43d0b1 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.Map.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static Map ReadMap(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + { + // Attributes + var version = reader.GetRequiredAttribute("version"); + var tiledVersion = reader.GetRequiredAttribute("tiledversion"); + var @class = reader.GetOptionalAttribute("class") ?? ""; + var orientation = reader.GetRequiredAttributeEnum("orientation", s => s switch + { + "orthogonal" => MapOrientation.Orthogonal, + "isometric" => MapOrientation.Isometric, + "staggered" => MapOrientation.Staggered, + "hexagonal" => MapOrientation.Hexagonal, + _ => throw new Exception($"Unknown orientation '{s}'") + }); + var renderOrder = reader.GetOptionalAttributeEnum("renderorder", s => s switch + { + "right-down" => RenderOrder.RightDown, + "right-up" => RenderOrder.RightUp, + "left-down" => RenderOrder.LeftDown, + "left-up" => RenderOrder.LeftUp, + _ => throw new Exception($"Unknown render order '{s}'") + }) ?? RenderOrder.RightDown; + var compressionLevel = reader.GetOptionalAttributeParseable("compressionlevel") ?? -1; + var width = reader.GetRequiredAttributeParseable("width"); + var height = reader.GetRequiredAttributeParseable("height"); + var tileWidth = reader.GetRequiredAttributeParseable("tilewidth"); + var tileHeight = reader.GetRequiredAttributeParseable("tileheight"); + var hexSideLength = reader.GetOptionalAttributeParseable("hexsidelength"); + var staggerAxis = reader.GetOptionalAttributeEnum("staggeraxis", s => s switch + { + "x" => StaggerAxis.X, + "y" => StaggerAxis.Y, + _ => throw new Exception($"Unknown stagger axis '{s}'") + }); + var staggerIndex = reader.GetOptionalAttributeEnum("staggerindex", s => s switch + { + "odd" => StaggerIndex.Odd, + "even" => StaggerIndex.Even, + _ => throw new Exception($"Unknown stagger index '{s}'") + }); + var parallaxOriginX = reader.GetOptionalAttributeParseable("parallaxoriginx") ?? 0.0f; + var parallaxOriginY = reader.GetOptionalAttributeParseable("parallaxoriginy") ?? 0.0f; + var backgroundColor = reader.GetOptionalAttributeClass("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture); + var nextLayerID = reader.GetRequiredAttributeParseable("nextlayerid"); + var nextObjectID = reader.GetRequiredAttributeParseable("nextobjectid"); + var infinite = (reader.GetOptionalAttributeParseable("infinite") ?? 0) == 1; + + // At most one of + Dictionary? properties = null; + + // Any number of + List layers = []; + List tilesets = []; + + reader.ProcessChildren("map", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver)), + "layer" => () => layers.Add(ReadTileLayer(r, dataUsesChunks: infinite)), + "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver)), + "imagelayer" => () => layers.Add(ReadImageLayer(r)), + "group" => () => layers.Add(ReadGroup(r, externalTemplateResolver)), + _ => r.Skip + }); + + return new Map + { + Version = version, + TiledVersion = tiledVersion, + Class = @class, + Orientation = orientation, + RenderOrder = renderOrder, + CompressionLevel = compressionLevel, + Width = width, + Height = height, + TileWidth = tileWidth, + TileHeight = tileHeight, + HexSideLength = hexSideLength, + StaggerAxis = staggerAxis, + StaggerIndex = staggerIndex, + ParallaxOriginX = parallaxOriginX, + ParallaxOriginY = parallaxOriginY, + BackgroundColor = backgroundColor, + NextLayerID = nextLayerID, + NextObjectID = nextObjectID, + Infinite = infinite, + Properties = properties, + Tilesets = tilesets, + Layers = layers + }; + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs new file mode 100644 index 0000000..2c52429 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static ObjectLayer ReadObjectLayer(XmlReader reader, Func externalTemplateResolver) + { + // Attributes + var id = reader.GetRequiredAttributeParseable("id"); + var name = reader.GetOptionalAttribute("name") ?? ""; + var @class = reader.GetOptionalAttribute("class") ?? ""; + var x = reader.GetOptionalAttributeParseable("x") ?? 0; + var y = reader.GetOptionalAttributeParseable("y") ?? 0; + var width = reader.GetOptionalAttributeParseable("width"); + var height = reader.GetOptionalAttributeParseable("height"); + var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; + var visible = reader.GetOptionalAttributeParseable("visible") ?? true; + var tintColor = reader.GetOptionalAttributeClass("tintcolor"); + var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; + var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; + var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; + var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; + var color = reader.GetOptionalAttributeClass("color"); + var drawOrder = reader.GetOptionalAttributeEnum("draworder", s => s switch + { + "topdown" => DrawOrder.TopDown, + "index" => DrawOrder.Index, + _ => throw new Exception($"Unknown draw order '{s}'") + }) ?? DrawOrder.TopDown; + + // Elements + Dictionary? properties = null; + List objects = []; + + reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "object" => () => objects.Add(ReadObject(r, externalTemplateResolver)), + _ => r.Skip + }); + + return new ObjectLayer + { + ID = id, + Name = name, + Class = @class, + X = x, + Y = y, + Width = width, + Height = height, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Color = color, + Properties = properties, + DrawOrder = drawOrder, + Objects = objects + }; + } + + internal static Object ReadObject(XmlReader reader, Func externalTemplateResolver) + { + // Attributes + var template = reader.GetOptionalAttribute("template"); + + uint? idDefault = null; + string nameDefault = ""; + string typeDefault = ""; + float xDefault = 0f; + float yDefault = 0f; + float widthDefault = 0f; + float heightDefault = 0f; + float rotationDefault = 0f; + uint? gidDefault = null; + bool visibleDefault = true; + Dictionary? propertiesDefault = null; + + // Perform template copy first + if (template is not null) + { + var resolvedTemplate = externalTemplateResolver(template); + var templObj = resolvedTemplate.Object; + + idDefault = templObj.ID; + nameDefault = templObj.Name; + typeDefault = templObj.Type; + xDefault = templObj.X; + yDefault = templObj.Y; + widthDefault = templObj.Width; + heightDefault = templObj.Height; + rotationDefault = templObj.Rotation; + gidDefault = templObj.GID; + visibleDefault = templObj.Visible; + propertiesDefault = templObj.Properties; + } + + var id = reader.GetOptionalAttributeParseable("id") ?? idDefault; + var name = reader.GetOptionalAttribute("name") ?? nameDefault; + var type = reader.GetOptionalAttribute("type") ?? typeDefault; + var x = reader.GetOptionalAttributeParseable("x") ?? xDefault; + var y = reader.GetOptionalAttributeParseable("y") ?? yDefault; + var width = reader.GetOptionalAttributeParseable("width") ?? widthDefault; + var height = reader.GetOptionalAttributeParseable("height") ?? heightDefault; + var rotation = reader.GetOptionalAttributeParseable("rotation") ?? rotationDefault; + var gid = reader.GetOptionalAttributeParseable("gid") ?? gidDefault; + var visible = reader.GetOptionalAttributeParseable("visible") ?? visibleDefault; + + // Elements + Object? obj = null; + int propertiesCounter = 0; + Dictionary? properties = propertiesDefault; + + reader.ProcessChildren("object", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, MergeProperties(properties, ReadProperties(r)), "Properties", ref propertiesCounter), + "ellipse" => () => Helpers.SetAtMostOnce(ref obj, ReadEllipseObject(r), "Object marker"), + "point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r), "Object marker"), + "polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r), "Object marker"), + "polyline" => () => Helpers.SetAtMostOnce(ref obj, ReadPolylineObject(r), "Object marker"), + "text" => () => Helpers.SetAtMostOnce(ref obj, ReadTextObject(r), "Object marker"), + _ => throw new Exception($"Unknown object marker '{elementName}'") + }); + + if (obj is null) + { + obj = new RectangleObject { ID = id }; + reader.Skip(); + } + + obj.Name = name; + obj.Type = type; + obj.X = x; + obj.Y = y; + obj.Width = width; + obj.Height = height; + obj.Rotation = rotation; + obj.GID = gid; + obj.Visible = visible; + obj.Template = template; + obj.Properties = properties; + + return obj; + } + + internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) + { + if (baseProperties is null) + return overrideProperties ?? new Dictionary(); + + if (overrideProperties is null) + return baseProperties; + + var result = new Dictionary(baseProperties); + foreach (var (key, value) in overrideProperties) + { + if (!result.TryGetValue(key, out var baseProp)) + { + result[key] = value; + continue; + } + else + { + if (value is ClassProperty classProp) + { + ((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); + } + else + { + result[key] = value; + } + } + } + + return result; + } + + internal static EllipseObject ReadEllipseObject(XmlReader reader) + { + reader.Skip(); + return new EllipseObject { }; + } + + internal static PointObject ReadPointObject(XmlReader reader) + { + reader.Skip(); + return new PointObject { }; + } + + internal static PolygonObject ReadPolygonObject(XmlReader reader) + { + // Attributes + var points = reader.GetRequiredAttributeParseable>("points", s => + { + // Takes on format "x1,y1 x2,y2 x3,y3 ..." + var coords = s.Split(' '); + return coords.Select(c => + { + var xy = c.Split(','); + return new Vector2(float.Parse(xy[0], CultureInfo.InvariantCulture), float.Parse(xy[1], CultureInfo.InvariantCulture)); + }).ToList(); + }); + + reader.ReadStartElement("polygon"); + return new PolygonObject { Points = points }; + } + + internal static PolylineObject ReadPolylineObject(XmlReader reader) + { + // Attributes + var points = reader.GetRequiredAttributeParseable>("points", s => + { + // Takes on format "x1,y1 x2,y2 x3,y3 ..." + var coords = s.Split(' '); + return coords.Select(c => + { + var xy = c.Split(','); + return new Vector2(float.Parse(xy[0], CultureInfo.InvariantCulture), float.Parse(xy[1], CultureInfo.InvariantCulture)); + }).ToList(); + }); + + reader.ReadStartElement("polyline"); + return new PolylineObject { Points = points }; + } + + internal static TextObject ReadTextObject(XmlReader reader) + { + // Attributes + var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif"; + var pixelSize = reader.GetOptionalAttributeParseable("pixelsize") ?? 16; + var wrap = reader.GetOptionalAttributeParseable("wrap") ?? false; + var color = reader.GetOptionalAttributeClass("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture); + var bold = reader.GetOptionalAttributeParseable("bold") ?? false; + var italic = reader.GetOptionalAttributeParseable("italic") ?? false; + var underline = reader.GetOptionalAttributeParseable("underline") ?? false; + var strikeout = reader.GetOptionalAttributeParseable("strikeout") ?? false; + var kerning = reader.GetOptionalAttributeParseable("kerning") ?? true; + var hAlign = reader.GetOptionalAttributeEnum("halign", s => s switch + { + "left" => TextHorizontalAlignment.Left, + "center" => TextHorizontalAlignment.Center, + "right" => TextHorizontalAlignment.Right, + "justify" => TextHorizontalAlignment.Justify, + _ => throw new Exception($"Unknown horizontal alignment '{s}'") + }) ?? TextHorizontalAlignment.Left; + var vAlign = reader.GetOptionalAttributeEnum("valign", s => s switch + { + "top" => TextVerticalAlignment.Top, + "center" => TextVerticalAlignment.Center, + "bottom" => TextVerticalAlignment.Bottom, + _ => throw new Exception($"Unknown vertical alignment '{s}'") + }) ?? TextVerticalAlignment.Top; + + // Elements + var text = reader.ReadElementContentAsString("text", ""); + + return new TextObject + { + FontFamily = fontFamily, + PixelSize = pixelSize, + Wrap = wrap, + Color = color, + Bold = bold, + Italic = italic, + Underline = underline, + Strikeout = strikeout, + Kerning = kerning, + HorizontalAlignment = hAlign, + VerticalAlignment = vAlign, + Text = text + }; + } + + internal static Template ReadTemplate(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + { + // No attributes + + // At most one of + Tileset? tileset = null; + + // Should contain exactly one of + Object? obj = null; + + reader.ProcessChildren("template", (r, elementName) => elementName switch + { + "tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(r, externalTilesetResolver, externalTemplateResolver), "Tileset"), + "object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(r, externalTemplateResolver), "Object"), + _ => r.Skip + }); + + if (obj is null) + throw new NotSupportedException("Template must contain exactly one object"); + + return new Template + { + Tileset = tileset, + Object = obj + }; + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Properties.cs b/DotTiled/Serialization/Tmx/Tmx.Properties.cs new file mode 100644 index 0000000..5609a85 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.Properties.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static Dictionary ReadProperties(XmlReader reader) + { + return reader.ReadList("properties", "property", (r) => + { + var name = r.GetRequiredAttribute("name"); + var type = r.GetOptionalAttributeEnum("type", (s) => s switch + { + "string" => PropertyType.String, + "int" => PropertyType.Int, + "float" => PropertyType.Float, + "bool" => PropertyType.Bool, + "color" => PropertyType.Color, + "file" => PropertyType.File, + "object" => PropertyType.Object, + "class" => PropertyType.Class, + _ => throw new XmlException("Invalid property type") + }) ?? PropertyType.String; + + IProperty property = type switch + { + PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") }, + PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Class => ReadClassProperty(r), + _ => throw new XmlException("Invalid property type") + }; + return (name, property); + }).ToDictionary(x => x.name, x => x.property); + } + + internal static ClassProperty ReadClassProperty(XmlReader reader) + { + var name = reader.GetRequiredAttribute("name"); + var propertyType = reader.GetRequiredAttribute("propertytype"); + + reader.ReadStartElement("property"); + var properties = ReadProperties(reader); + reader.ReadEndElement(); + + return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs b/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs new file mode 100644 index 0000000..e162542 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static TileLayer ReadTileLayer(XmlReader reader, bool dataUsesChunks) + { + var id = reader.GetRequiredAttributeParseable("id"); + var name = reader.GetOptionalAttribute("name") ?? ""; + var @class = reader.GetOptionalAttribute("class") ?? ""; + var x = reader.GetOptionalAttributeParseable("x") ?? 0; + var y = reader.GetOptionalAttributeParseable("y") ?? 0; + var width = reader.GetRequiredAttributeParseable("width"); + var height = reader.GetRequiredAttributeParseable("height"); + var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; + var visible = reader.GetOptionalAttributeParseable("visible") ?? true; + var tintColor = reader.GetOptionalAttributeClass("tintcolor"); + var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; + var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; + var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; + var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; + + Dictionary? properties = null; + Data? data = null; + + reader.ProcessChildren("layer", (r, elementName) => elementName switch + { + "data" => () => Helpers.SetAtMostOnce(ref data, ReadData(r, dataUsesChunks), "Data"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + _ => r.Skip + }); + + return new TileLayer + { + ID = id, + Name = name, + Class = @class, + X = x, + Y = y, + Width = width, + Height = height, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Data = data, + Properties = properties + }; + } + + internal static ImageLayer ReadImageLayer(XmlReader reader) + { + var id = reader.GetRequiredAttributeParseable("id"); + var name = reader.GetOptionalAttribute("name") ?? ""; + var @class = reader.GetOptionalAttribute("class") ?? ""; + var x = reader.GetOptionalAttributeParseable("x") ?? 0; + var y = reader.GetOptionalAttributeParseable("y") ?? 0; + var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; + var visible = reader.GetOptionalAttributeParseable("visible") ?? true; + var tintColor = reader.GetOptionalAttributeClass("tintcolor"); + var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; + var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; + var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; + var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; + var repeatX = reader.GetRequiredAttributeParseable("repeatx"); + var repeatY = reader.GetRequiredAttributeParseable("repeaty"); + + Dictionary? properties = null; + Image? image = null; + + reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch + { + "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + _ => r.Skip + }); + + return new ImageLayer + { + ID = id, + Name = name, + Class = @class, + X = x, + Y = y, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + Image = image, + RepeatX = repeatX, + RepeatY = repeatY + }; + } + + internal static Group ReadGroup(XmlReader reader, Func externalTemplateResolver) + { + var id = reader.GetRequiredAttributeParseable("id"); + var name = reader.GetOptionalAttribute("name") ?? ""; + var @class = reader.GetOptionalAttribute("class") ?? ""; + var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; + var visible = reader.GetOptionalAttributeParseable("visible") ?? true; + var tintColor = reader.GetOptionalAttributeClass("tintcolor"); + var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; + var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; + var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; + var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; + + Dictionary? properties = null; + List layers = []; + + reader.ProcessChildren("group", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "layer" => () => layers.Add(ReadTileLayer(r, dataUsesChunks: false)), + "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver)), + "imagelayer" => () => layers.Add(ReadImageLayer(r)), + "group" => () => layers.Add(ReadGroup(r, externalTemplateResolver)), + _ => r.Skip + }); + + return new Group + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + Layers = layers + }; + } +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs new file mode 100644 index 0000000..1996bc2 --- /dev/null +++ b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace DotTiled; + +internal partial class Tmx +{ + internal static Tileset ReadTileset(XmlReader reader, Func? externalTilesetResolver, Func externalTemplateResolver) + { + // Attributes + var version = reader.GetOptionalAttribute("version"); + var tiledVersion = reader.GetOptionalAttribute("tiledversion"); + var firstGID = reader.GetOptionalAttributeParseable("firstgid"); + var source = reader.GetOptionalAttribute("source"); + var name = reader.GetOptionalAttribute("name"); + var @class = reader.GetOptionalAttribute("class") ?? ""; + var tileWidth = reader.GetOptionalAttributeParseable("tilewidth"); + var tileHeight = reader.GetOptionalAttributeParseable("tileheight"); + var spacing = reader.GetOptionalAttributeParseable("spacing"); + var margin = reader.GetOptionalAttributeParseable("margin"); + var tileCount = reader.GetOptionalAttributeParseable("tilecount"); + var columns = reader.GetOptionalAttributeParseable("columns"); + var objectAlignment = reader.GetOptionalAttributeEnum("objectalignment", s => s switch + { + "unspecified" => ObjectAlignment.Unspecified, + "topleft" => ObjectAlignment.TopLeft, + "top" => ObjectAlignment.Top, + "topright" => ObjectAlignment.TopRight, + "left" => ObjectAlignment.Left, + "center" => ObjectAlignment.Center, + "right" => ObjectAlignment.Right, + "bottomleft" => ObjectAlignment.BottomLeft, + "bottom" => ObjectAlignment.Bottom, + "bottomright" => ObjectAlignment.BottomRight, + _ => throw new Exception($"Unknown object alignment '{s}'") + }) ?? ObjectAlignment.Unspecified; + var renderSize = reader.GetOptionalAttributeEnum("rendersize", s => s switch + { + "tile" => TileRenderSize.Tile, + "grid" => TileRenderSize.Grid, + _ => throw new Exception($"Unknown render size '{s}'") + }) ?? TileRenderSize.Tile; + var fillMode = reader.GetOptionalAttributeEnum("fillmode", s => s switch + { + "stretch" => FillMode.Stretch, + "preserve-aspect-fit" => FillMode.PreserveAspectFit, + _ => throw new Exception($"Unknown fill mode '{s}'") + }) ?? FillMode.Stretch; + + // Elements + Image? image = null; + TileOffset? tileOffset = null; + Grid? grid = null; + Dictionary? properties = null; + List? wangsets = null; + Transformations? transformations = null; + List tiles = []; + + reader.ProcessChildren("tileset", (r, elementName) => elementName switch + { + "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), + "tileoffset" => () => Helpers.SetAtMostOnce(ref tileOffset, ReadTileOffset(r), "TileOffset"), + "grid" => () => Helpers.SetAtMostOnce(ref grid, ReadGrid(r), "Grid"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(r), "Wangsets"), + "transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(r), "Transformations"), + "tile" => () => tiles.Add(ReadTile(r, externalTemplateResolver)), + _ => r.Skip + }); + + // Check if tileset is referring to external file + if (source is not null) + { + if (externalTilesetResolver is null) + throw new InvalidOperationException("External tileset resolver is required to resolve external tilesets."); + + var resolvedTileset = externalTilesetResolver(source); + resolvedTileset.FirstGID = firstGID; + resolvedTileset.Source = null; + return resolvedTileset; + } + + return new Tileset + { + Version = version, + TiledVersion = tiledVersion, + FirstGID = firstGID, + Source = source, + Name = name, + Class = @class, + TileWidth = tileWidth, + TileHeight = tileHeight, + Spacing = spacing, + Margin = margin, + TileCount = tileCount, + Columns = columns, + ObjectAlignment = objectAlignment, + RenderSize = renderSize, + FillMode = fillMode, + Image = image, + TileOffset = tileOffset, + Grid = grid, + Properties = properties, + Wangsets = wangsets, + Transformations = transformations, + Tiles = tiles + }; + } + + internal static Image ReadImage(XmlReader reader) + { + // Attributes + var format = reader.GetOptionalAttributeEnum("format", s => s switch + { + "png" => ImageFormat.Png, + "jpg" => ImageFormat.Jpg, + "bmp" => ImageFormat.Bmp, + "gif" => ImageFormat.Gif, + _ => throw new Exception($"Unknown image format '{s}'") + }); + var source = reader.GetOptionalAttribute("source"); + var transparentColor = reader.GetOptionalAttributeClass("trans"); + var width = reader.GetOptionalAttributeParseable("width"); + var height = reader.GetOptionalAttributeParseable("height"); + + reader.ProcessChildren("image", (r, elementName) => elementName switch + { + "data" => throw new NotSupportedException("Embedded image data is not supported."), + _ => r.Skip + }); + + return new Image + { + Format = format, + Source = source, + TransparentColor = transparentColor, + Width = width, + Height = height, + }; + } + + internal static TileOffset ReadTileOffset(XmlReader reader) + { + // Attributes + var x = reader.GetOptionalAttributeParseable("x") ?? 0f; + var y = reader.GetOptionalAttributeParseable("y") ?? 0f; + + reader.ReadStartElement("tileoffset"); + return new TileOffset { X = x, Y = y }; + } + + internal static Grid ReadGrid(XmlReader reader) + { + // Attributes + var orientation = reader.GetOptionalAttributeEnum("orientation", s => s switch + { + "orthogonal" => GridOrientation.Orthogonal, + "isometric" => GridOrientation.Isometric, + _ => throw new Exception($"Unknown orientation '{s}'") + }) ?? GridOrientation.Orthogonal; + var width = reader.GetRequiredAttributeParseable("width"); + var height = reader.GetRequiredAttributeParseable("height"); + + reader.ReadStartElement("grid"); + return new Grid { Orientation = orientation, Width = width, Height = height }; + } + + internal static Transformations ReadTransformations(XmlReader reader) + { + // Attributes + var hFlip = reader.GetOptionalAttributeParseable("hflip") ?? false; + var vFlip = reader.GetOptionalAttributeParseable("vflip") ?? false; + var rotate = reader.GetOptionalAttributeParseable("rotate") ?? false; + var preferUntransformed = reader.GetOptionalAttributeParseable("preferuntransformed") ?? false; + + reader.ReadStartElement("transformations"); + return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed }; + } + + internal static Tile ReadTile(XmlReader reader, Func externalTemplateResolver) + { + // Attributes + var id = reader.GetRequiredAttributeParseable("id"); + var type = reader.GetOptionalAttribute("type") ?? ""; + var probability = reader.GetOptionalAttributeParseable("probability") ?? 0f; + var x = reader.GetOptionalAttributeParseable("x") ?? 0; + var y = reader.GetOptionalAttributeParseable("y") ?? 0; + var width = reader.GetOptionalAttributeParseable("width"); + var height = reader.GetOptionalAttributeParseable("height"); + + // Elements + Dictionary? properties = null; + Image? image = null; + ObjectLayer? objectLayer = null; + List? animation = null; + + reader.ProcessChildren("tile", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), + "objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(r, externalTemplateResolver), "ObjectLayer"), + "animation" => () => Helpers.SetAtMostOnce(ref animation, r.ReadList("animation", "frame", (ar) => + { + var tileID = ar.GetRequiredAttributeParseable("tileid"); + var duration = ar.GetRequiredAttributeParseable("duration"); + return new Frame { TileID = tileID, Duration = duration }; + }), "Animation"), + _ => r.Skip + }); + + return new Tile + { + ID = id, + Type = type, + Probability = probability, + X = x, + Y = y, + Width = width ?? image?.Width ?? 0, + Height = height ?? image?.Height ?? 0, + Properties = properties, + Image = image, + ObjectLayer = objectLayer, + Animation = animation + }; + } + + internal static List ReadWangsets(XmlReader reader) + { + return reader.ReadList("wangsets", "wangset", ReadWangset); + } + + internal static Wangset ReadWangset(XmlReader reader) + { + // Attributes + var name = reader.GetRequiredAttribute("name"); + var @class = reader.GetOptionalAttribute("class") ?? ""; + var tile = reader.GetRequiredAttributeParseable("tile"); + + // Elements + Dictionary? properties = null; + List wangColors = []; + List wangTiles = []; + + reader.ProcessChildren("wangset", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "wangcolor" => () => wangColors.Add(ReadWangColor(r)), + "wangtile" => () => wangTiles.Add(ReadWangTile(r)), + _ => r.Skip + }); + + if (wangColors.Count > 254) + throw new ArgumentException("Wangset can have at most 254 Wang colors."); + + return new Wangset + { + Name = name, + Class = @class, + Tile = tile, + Properties = properties, + WangColors = wangColors, + WangTiles = wangTiles + }; + } + + internal static WangColor ReadWangColor(XmlReader reader) + { + // Attributes + var name = reader.GetRequiredAttribute("name"); + var @class = reader.GetOptionalAttribute("class") ?? ""; + var color = reader.GetRequiredAttributeParseable("color"); + var tile = reader.GetRequiredAttributeParseable("tile"); + var probability = reader.GetOptionalAttributeParseable("probability") ?? 0f; + + // Elements + Dictionary? properties = null; + + reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch + { + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + _ => r.Skip + }); + + return new WangColor + { + Name = name, + Class = @class, + Color = color, + Tile = tile, + Probability = probability, + Properties = properties + }; + } + + internal static WangTile ReadWangTile(XmlReader reader) + { + // Attributes + var tileID = reader.GetRequiredAttributeParseable("tileid"); + var wangID = reader.GetRequiredAttributeParseable("wangid", s => + { + // Comma-separated list of indices (0-254) + var indices = s.Split(',').Select(i => byte.Parse(i)).ToArray(); + if (indices.Length > 8) + throw new ArgumentException("Wang ID can have at most 8 indices."); + return indices; + }); + + return new WangTile + { + TileID = tileID, + WangID = wangID + }; + } +} diff --git a/DotTiled/Serialization/Tmx/TmxMapReader.cs b/DotTiled/Serialization/Tmx/TmxMapReader.cs new file mode 100644 index 0000000..d7e631a --- /dev/null +++ b/DotTiled/Serialization/Tmx/TmxMapReader.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +namespace DotTiled; + +public class TmxMapReader : IMapReader +{ + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + + private readonly XmlReader _reader; + private bool disposedValue; + + public TmxMapReader(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + { + _reader = reader ?? throw new ArgumentNullException(nameof(reader)); + _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + + // Prepare reader + _reader.MoveToContent(); + } + + public Map ReadMap() + { + return Tmx.ReadMap(_reader, _externalTilesetResolver, _externalTemplateResolver); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + _reader.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~TmxTiledMapReader() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + System.GC.SuppressFinalize(this); + } +} diff --git a/DotTiled/Serialization/Tmx/TsxTilesetReader.cs b/DotTiled/Serialization/Tmx/TsxTilesetReader.cs new file mode 100644 index 0000000..c089129 --- /dev/null +++ b/DotTiled/Serialization/Tmx/TsxTilesetReader.cs @@ -0,0 +1,50 @@ +using System; +using System.Xml; + +namespace DotTiled; + +public class TsxTilesetReader : ITilesetReader +{ + // External resolvers + private readonly Func _externalTemplateResolver; + + private readonly XmlReader _reader; + private bool disposedValue; + + public TsxTilesetReader(XmlReader reader, Func externalTemplateResolver) + { + _reader = reader ?? throw new ArgumentNullException(nameof(reader)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + } + + public Tileset ReadTileset() => Tmx.ReadTileset(_reader, null, _externalTemplateResolver); + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~TsxTilesetReader() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + System.GC.SuppressFinalize(this); + } +} diff --git a/DotTiled/Serialization/Tmx/TxTemplateReader.cs b/DotTiled/Serialization/Tmx/TxTemplateReader.cs new file mode 100644 index 0000000..24b95f4 --- /dev/null +++ b/DotTiled/Serialization/Tmx/TxTemplateReader.cs @@ -0,0 +1,55 @@ +using System; +using System.Xml; + +namespace DotTiled; + +public class TxTemplateReader : ITemplateReader +{ + // Resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + + private readonly XmlReader _reader; + private bool disposedValue; + + public TxTemplateReader(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + { + _reader = reader ?? throw new ArgumentNullException(nameof(reader)); + _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + + // Prepare reader + _reader.MoveToContent(); + } + + public Template ReadTemplate() => Tmx.ReadTemplate(_reader, _externalTilesetResolver, _externalTemplateResolver); + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~TxTemplateReader() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + System.GC.SuppressFinalize(this); + } +} From bafbd3d6c7017b1f3d720cd25394ae5017b1a46e Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 3 Aug 2024 14:26:09 +0200 Subject: [PATCH 2/6] No more TmxSerializer, now different readers for each file format --- .../AssertData.cs} | 8 +- .../AssertImage.cs} | 4 +- .../AssertLayer.cs} | 24 +- DotTiled.Tests/Assert/AssertMap.cs | 40 +++ .../AssertObject.cs} | 20 +- .../AssertProperties.cs} | 4 +- .../AssertTileset.cs} | 50 +-- DotTiled.Tests/DotTiled.Tests.csproj | 2 +- .../TestData/Map/empty-map-base64-gzip.tmx | 0 .../TestData/Map/empty-map-base64-zlib.tmx | 0 .../Tmx}/TestData/Map/empty-map-base64.tmx | 0 .../Tmx}/TestData/Map/empty-map-csv.tmx | 0 .../Tmx}/TestData/Map/empty-map-properties.cs | 2 +- .../TestData/Map/empty-map-properties.tmx | 0 .../Tmx}/TestData/Map/empty-map.cs | 2 +- .../Tmx}/TestData/Map/map-with-group.cs | 2 +- .../Tmx}/TestData/Map/map-with-group.tmx | 0 .../TestData/Map/map-with-object-template.cs | 2 +- .../TestData/Map/map-with-object-template.tmx | 0 .../Tmx}/TestData/Map/simple-tileset-embed.cs | 2 +- .../TestData/Map/simple-tileset-embed.tmx | 0 .../Template/map-with-object-template.tx | 0 .../Tmx}/TestData/TestData.cs | 8 +- .../Serialization/Tmx/TmxMapReaderTests.cs | 150 +++++++++ .../TmxSerializer/TmxSerializer.MapTests.cs | 242 -------------- .../TmxSerializer/TmxSerializerTests.cs | 32 -- DotTiled/TmxSerializer/TmxSerializer.Chunk.cs | 28 -- DotTiled/TmxSerializer/TmxSerializer.Data.cs | 122 ------- .../TmxSerializer/TmxSerializer.Helpers.cs | 26 -- DotTiled/TmxSerializer/TmxSerializer.Map.cs | 102 ------ .../TmxSerializer.ObjectLayer.cs | 309 ----------------- .../TmxSerializer/TmxSerializer.Properties.cs | 54 --- .../TmxSerializer/TmxSerializer.TileLayer.cs | 148 --------- .../TmxSerializer/TmxSerializer.Tileset.cs | 313 ------------------ DotTiled/TmxSerializer/TmxSerializer.cs | 45 --- 35 files changed, 255 insertions(+), 1486 deletions(-) rename DotTiled.Tests/{TmxSerializer/TmxSerializer.DataTests.cs => Assert/AssertData.cs} (80%) rename DotTiled.Tests/{TmxSerializer/TmxSerializer.ImageTests.cs => Assert/AssertImage.cs} (79%) rename DotTiled.Tests/{TmxSerializer/TmxSerializer.LayerTests.cs => Assert/AssertLayer.cs} (66%) create mode 100644 DotTiled.Tests/Assert/AssertMap.cs rename DotTiled.Tests/{TmxSerializer/TmxSerializer.ObjectTests.cs => Assert/AssertObject.cs} (68%) rename DotTiled.Tests/{TmxSerializer/TmxSerializer.PropertiesTests.cs => Assert/AssertProperties.cs} (91%) rename DotTiled.Tests/{TmxSerializer/TmxSerializer.TilesetTests.cs => Assert/AssertTileset.cs} (68%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map-base64-gzip.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map-base64-zlib.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map-base64.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map-csv.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map-properties.cs (97%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map-properties.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/empty-map.cs (97%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/map-with-group.cs (98%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/map-with-group.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/map-with-object-template.cs (98%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/map-with-object-template.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/simple-tileset-embed.cs (97%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Map/simple-tileset-embed.tmx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/Template/map-with-object-template.tx (100%) rename DotTiled.Tests/{TmxSerializer => Serialization/Tmx}/TestData/TestData.cs (70%) create mode 100644 DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs delete mode 100644 DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs delete mode 100644 DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.Chunk.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.Data.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.Helpers.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.Map.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.Properties.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.Tileset.cs delete mode 100644 DotTiled/TmxSerializer/TmxSerializer.cs diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.DataTests.cs b/DotTiled.Tests/Assert/AssertData.cs similarity index 80% rename from DotTiled.Tests/TmxSerializer/TmxSerializer.DataTests.cs rename to DotTiled.Tests/Assert/AssertData.cs index e08d402..d4b54f0 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.DataTests.cs +++ b/DotTiled.Tests/Assert/AssertData.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxSerializerDataTests +public static partial class DotTiledAssert { - public static void AssertData(Data? actual, Data? expected) + internal static void AssertData(Data? expected, Data? actual) { if (expected is null) { @@ -24,11 +24,11 @@ public partial class TmxSerializerDataTests Assert.NotNull(actual.Chunks); Assert.Equal(expected.Chunks.Length, actual.Chunks.Length); for (var i = 0; i < expected.Chunks.Length; i++) - AssertChunk(actual.Chunks[i], expected.Chunks[i]); + AssertChunk(expected.Chunks[i], actual.Chunks[i]); } } - private static void AssertChunk(Chunk actual, Chunk expected) + private static void AssertChunk(Chunk expected, Chunk actual) { // Attributes Assert.Equal(expected.X, actual.X); diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.ImageTests.cs b/DotTiled.Tests/Assert/AssertImage.cs similarity index 79% rename from DotTiled.Tests/TmxSerializer/TmxSerializer.ImageTests.cs rename to DotTiled.Tests/Assert/AssertImage.cs index 7d00713..9943c46 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.ImageTests.cs +++ b/DotTiled.Tests/Assert/AssertImage.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxSerializerImageTests +public static partial class DotTiledAssert { - public static void AssertImage(Image? actual, Image? expected) + internal static void AssertImage(Image? expected, Image? actual) { if (expected is null) { diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs b/DotTiled.Tests/Assert/AssertLayer.cs similarity index 66% rename from DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs rename to DotTiled.Tests/Assert/AssertLayer.cs index c3289a4..57df04d 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs +++ b/DotTiled.Tests/Assert/AssertLayer.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxSerializerLayerTests +public static partial class DotTiledAssert { - public static void AssertLayer(BaseLayer? actual, BaseLayer? expected) + internal static void AssertLayer(BaseLayer? expected, BaseLayer? actual) { if (expected is null) { @@ -23,11 +23,11 @@ public partial class TmxSerializerLayerTests Assert.Equal(expected.ParallaxX, actual.ParallaxX); Assert.Equal(expected.ParallaxY, actual.ParallaxY); - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); - AssertLayer((dynamic)actual, (dynamic)expected); + AssertProperties(expected.Properties, actual.Properties); + AssertLayer((dynamic)expected, (dynamic)actual); } - private static void AssertLayer(TileLayer actual, TileLayer expected) + private static void AssertLayer(TileLayer expected, TileLayer actual) { // Attributes Assert.Equal(expected.Width, actual.Width); @@ -36,10 +36,10 @@ public partial class TmxSerializerLayerTests Assert.Equal(expected.Y, actual.Y); Assert.NotNull(actual.Data); - TmxSerializerDataTests.AssertData(actual.Data, expected.Data); + AssertData(expected.Data, actual.Data); } - private static void AssertLayer(ObjectLayer actual, ObjectLayer expected) + private static void AssertLayer(ObjectLayer expected, ObjectLayer actual) { // Attributes Assert.Equal(expected.DrawOrder, actual.DrawOrder); @@ -49,10 +49,10 @@ public partial class TmxSerializerLayerTests Assert.NotNull(actual.Objects); Assert.Equal(expected.Objects.Count, actual.Objects.Count); for (var i = 0; i < expected.Objects.Count; i++) - TmxSerializerObjectTests.AssertObject(actual.Objects[i], expected.Objects[i]); + AssertObject(expected.Objects[i], actual.Objects[i]); } - private static void AssertLayer(ImageLayer actual, ImageLayer expected) + private static void AssertLayer(ImageLayer expected, ImageLayer actual) { // Attributes Assert.Equal(expected.RepeatX, actual.RepeatX); @@ -61,15 +61,15 @@ public partial class TmxSerializerLayerTests Assert.Equal(expected.Y, actual.Y); Assert.NotNull(actual.Image); - TmxSerializerImageTests.AssertImage(actual.Image, expected.Image); + AssertImage(expected.Image, actual.Image); } - private static void AssertLayer(Group actual, Group expected) + private static void AssertLayer(Group expected, Group actual) { // Attributes Assert.NotNull(actual.Layers); Assert.Equal(expected.Layers.Count, actual.Layers.Count); for (var i = 0; i < expected.Layers.Count; i++) - AssertLayer(actual.Layers[i], expected.Layers[i]); + AssertLayer(expected.Layers[i], actual.Layers[i]); } } diff --git a/DotTiled.Tests/Assert/AssertMap.cs b/DotTiled.Tests/Assert/AssertMap.cs new file mode 100644 index 0000000..167d0ad --- /dev/null +++ b/DotTiled.Tests/Assert/AssertMap.cs @@ -0,0 +1,40 @@ +namespace DotTiled.Tests; + +public static partial class DotTiledAssert +{ + internal static void AssertMap(Map expected, Map actual) + { + // Attributes + Assert.Equal(expected.Version, actual.Version); + Assert.Equal(expected.TiledVersion, actual.TiledVersion); + Assert.Equal(expected.Class, actual.Class); + Assert.Equal(expected.Orientation, actual.Orientation); + Assert.Equal(expected.RenderOrder, actual.RenderOrder); + Assert.Equal(expected.CompressionLevel, actual.CompressionLevel); + Assert.Equal(expected.Width, actual.Width); + Assert.Equal(expected.Height, actual.Height); + Assert.Equal(expected.TileWidth, actual.TileWidth); + Assert.Equal(expected.TileHeight, actual.TileHeight); + Assert.Equal(expected.HexSideLength, actual.HexSideLength); + Assert.Equal(expected.StaggerAxis, actual.StaggerAxis); + Assert.Equal(expected.StaggerIndex, actual.StaggerIndex); + Assert.Equal(expected.ParallaxOriginX, actual.ParallaxOriginX); + Assert.Equal(expected.ParallaxOriginY, actual.ParallaxOriginY); + Assert.Equal(expected.BackgroundColor, actual.BackgroundColor); + Assert.Equal(expected.NextLayerID, actual.NextLayerID); + Assert.Equal(expected.NextObjectID, actual.NextObjectID); + Assert.Equal(expected.Infinite, actual.Infinite); + + AssertProperties(actual.Properties, expected.Properties); + + Assert.NotNull(actual.Tilesets); + Assert.Equal(expected.Tilesets.Count, actual.Tilesets.Count); + for (var i = 0; i < expected.Tilesets.Count; i++) + AssertTileset(actual.Tilesets[i], expected.Tilesets[i]); + + Assert.NotNull(actual.Layers); + Assert.Equal(expected.Layers.Count, actual.Layers.Count); + for (var i = 0; i < expected.Layers.Count; i++) + AssertLayer(actual.Layers[i], expected.Layers[i]); + } +} diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.ObjectTests.cs b/DotTiled.Tests/Assert/AssertObject.cs similarity index 68% rename from DotTiled.Tests/TmxSerializer/TmxSerializer.ObjectTests.cs rename to DotTiled.Tests/Assert/AssertObject.cs index c593074..68d74eb 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.ObjectTests.cs +++ b/DotTiled.Tests/Assert/AssertObject.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxSerializerObjectTests +public static partial class DotTiledAssert { - public static void AssertObject(Object actual, Object expected) + internal static void AssertObject(Object expected, Object actual) { // Attributes Assert.Equal(expected.ID, actual.ID); @@ -17,36 +17,36 @@ public partial class TmxSerializerObjectTests Assert.Equal(expected.Visible, actual.Visible); Assert.Equal(expected.Template, actual.Template); - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); - AssertObject((dynamic)actual, (dynamic)expected); + AssertProperties(actual.Properties, expected.Properties); + AssertObject((dynamic)expected, (dynamic)actual); } - private static void AssertObject(RectangleObject actual, RectangleObject expected) + private static void AssertObject(RectangleObject expected, RectangleObject actual) { Assert.True(true); // A rectangle object is the same as the abstract Object } - private static void AssertObject(EllipseObject actual, EllipseObject expected) + private static void AssertObject(EllipseObject expected, EllipseObject actual) { Assert.True(true); // An ellipse object is the same as the abstract Object } - private static void AssertObject(PointObject actual, PointObject expected) + private static void AssertObject(PointObject expected, PointObject actual) { Assert.True(true); // A point object is the same as the abstract Object } - private static void AssertObject(PolygonObject actual, PolygonObject expected) + private static void AssertObject(PolygonObject expected, PolygonObject actual) { Assert.Equal(expected.Points, actual.Points); } - private static void AssertObject(PolylineObject actual, PolylineObject expected) + private static void AssertObject(PolylineObject expected, PolylineObject actual) { Assert.Equal(expected.Points, actual.Points); } - private static void AssertObject(TextObject actual, TextObject expected) + private static void AssertObject(TextObject expected, TextObject actual) { // Attributes Assert.Equal(expected.FontFamily, actual.FontFamily); diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.PropertiesTests.cs b/DotTiled.Tests/Assert/AssertProperties.cs similarity index 91% rename from DotTiled.Tests/TmxSerializer/TmxSerializer.PropertiesTests.cs rename to DotTiled.Tests/Assert/AssertProperties.cs index e22e5b9..d0a8269 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.PropertiesTests.cs +++ b/DotTiled.Tests/Assert/AssertProperties.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxSerializerPropertiesTests +public static partial class DotTiledAssert { - public static void AssertProperties(Dictionary? actual, Dictionary? expected) + internal static void AssertProperties(Dictionary? expected, Dictionary? actual) { if (expected is null) { diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.TilesetTests.cs b/DotTiled.Tests/Assert/AssertTileset.cs similarity index 68% rename from DotTiled.Tests/TmxSerializer/TmxSerializer.TilesetTests.cs rename to DotTiled.Tests/Assert/AssertTileset.cs index 04bb54b..8421bd0 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.TilesetTests.cs +++ b/DotTiled.Tests/Assert/AssertTileset.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxSerializerTilesetTests +public static partial class DotTiledAssert { - public static void AssertTileset(Tileset actual, Tileset expected) + internal static void AssertTileset(Tileset expected, Tileset actual) { // Attributes Assert.Equal(expected.Version, actual.Version); @@ -22,28 +22,28 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.FillMode, actual.FillMode); // At most one of - TmxSerializerImageTests.AssertImage(actual.Image, expected.Image); - AssertTileOffset(actual.TileOffset, expected.TileOffset); - AssertGrid(actual.Grid, expected.Grid); - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); + AssertImage(expected.Image, actual.Image); + AssertTileOffset(expected.TileOffset, actual.TileOffset); + AssertGrid(expected.Grid, actual.Grid); + AssertProperties(expected.Properties, actual.Properties); // TODO: AssertTerrainTypes(actual.TerrainTypes, expected.TerrainTypes); if (expected.Wangsets is not null) { Assert.NotNull(actual.Wangsets); Assert.Equal(expected.Wangsets.Count, actual.Wangsets.Count); for (var i = 0; i < expected.Wangsets.Count; i++) - AssertWangset(actual.Wangsets[i], expected.Wangsets[i]); + AssertWangset(expected.Wangsets[i], actual.Wangsets[i]); } - AssertTransformations(actual.Transformations, expected.Transformations); + AssertTransformations(expected.Transformations, actual.Transformations); // Any number of Assert.NotNull(actual.Tiles); Assert.Equal(expected.Tiles.Count, actual.Tiles.Count); for (var i = 0; i < expected.Tiles.Count; i++) - AssertTile(actual.Tiles[i], expected.Tiles[i]); + AssertTile(expected.Tiles[i], actual.Tiles[i]); } - private static void AssertTileOffset(TileOffset? actual, TileOffset? expected) + private static void AssertTileOffset(TileOffset? expected, TileOffset? actual) { if (expected is null) { @@ -57,7 +57,7 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.Y, actual.Y); } - private static void AssertGrid(Grid? actual, Grid? expected) + private static void AssertGrid(Grid? expected, Grid? actual) { if (expected is null) { @@ -72,7 +72,7 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.Height, actual.Height); } - private static void AssertWangset(Wangset actual, Wangset expected) + private static void AssertWangset(Wangset expected, Wangset actual) { // Attributes Assert.Equal(expected.Name, actual.Name); @@ -80,19 +80,19 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.Tile, actual.Tile); // At most one of - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); + AssertProperties(expected.Properties, actual.Properties); if (expected.WangColors is not null) { Assert.NotNull(actual.WangColors); Assert.Equal(expected.WangColors.Count, actual.WangColors.Count); for (var i = 0; i < expected.WangColors.Count; i++) - AssertWangColor(actual.WangColors[i], expected.WangColors[i]); + AssertWangColor(expected.WangColors[i], actual.WangColors[i]); } for (var i = 0; i < expected.WangTiles.Count; i++) - AssertWangTile(actual.WangTiles[i], expected.WangTiles[i]); + AssertWangTile(expected.WangTiles[i], actual.WangTiles[i]); } - private static void AssertWangColor(WangColor actual, WangColor expected) + private static void AssertWangColor(WangColor expected, WangColor actual) { // Attributes Assert.Equal(expected.Name, actual.Name); @@ -101,17 +101,17 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.Tile, actual.Tile); Assert.Equal(expected.Probability, actual.Probability); - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); + AssertProperties(expected.Properties, actual.Properties); } - private static void AssertWangTile(WangTile actual, WangTile expected) + private static void AssertWangTile(WangTile expected, WangTile actual) { // Attributes Assert.Equal(expected.TileID, actual.TileID); Assert.Equal(expected.WangID, actual.WangID); } - private static void AssertTransformations(Transformations? actual, Transformations? expected) + private static void AssertTransformations(Transformations? expected, Transformations? actual) { if (expected is null) { @@ -127,7 +127,7 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.PreferUntransformed, actual.PreferUntransformed); } - private static void AssertTile(Tile actual, Tile expected) + private static void AssertTile(Tile expected, Tile actual) { // Attributes Assert.Equal(expected.ID, actual.ID); @@ -139,19 +139,19 @@ public partial class TmxSerializerTilesetTests Assert.Equal(expected.Height, actual.Height); // Elements - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); - TmxSerializerImageTests.AssertImage(actual.Image, expected.Image); - TmxSerializerLayerTests.AssertLayer(actual.ObjectLayer, expected.ObjectLayer); + AssertProperties(actual.Properties, expected.Properties); + AssertImage(actual.Image, expected.Image); + AssertLayer((BaseLayer?)actual.ObjectLayer, (BaseLayer?)expected.ObjectLayer); if (expected.Animation is not null) { Assert.NotNull(actual.Animation); Assert.Equal(expected.Animation.Count, actual.Animation.Count); for (var i = 0; i < expected.Animation.Count; i++) - AssertFrame(actual.Animation[i], expected.Animation[i]); + AssertFrame(expected.Animation[i], actual.Animation[i]); } } - private static void AssertFrame(Frame actual, Frame expected) + private static void AssertFrame(Frame expected, Frame actual) { // Attributes Assert.Equal(expected.TileID, actual.TileID); diff --git a/DotTiled.Tests/DotTiled.Tests.csproj b/DotTiled.Tests/DotTiled.Tests.csproj index 44236af..5e36559 100644 --- a/DotTiled.Tests/DotTiled.Tests.csproj +++ b/DotTiled.Tests/DotTiled.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-base64-gzip.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-gzip.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-base64-gzip.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-gzip.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-base64-zlib.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-zlib.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-base64-zlib.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-zlib.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-base64.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-base64.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-csv.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-csv.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-csv.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-csv.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-properties.cs b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs similarity index 97% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-properties.cs rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs index c87c183..795b920 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-properties.cs +++ b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs @@ -1,6 +1,6 @@ namespace DotTiled.Tests; -public partial class TmxSerializerMapTests +public partial class TmxMapReaderTests { private static Map EmptyMapWithProperties() => new Map { diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-properties.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map-properties.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.cs b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs similarity index 97% rename from DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.cs rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs index e01516b..12cfc00 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/Map/empty-map.cs +++ b/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs @@ -1,6 +1,6 @@ namespace DotTiled.Tests; -public partial class TmxSerializerMapTests +public partial class TmxMapReaderTests { private static Map EmptyMapWithEncodingAndCompression(DataEncoding dataEncoding, DataCompression? compression) => new Map { diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.cs b/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs similarity index 98% rename from DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.cs rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs index 0bac69e..515312e 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.cs +++ b/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs @@ -1,6 +1,6 @@ namespace DotTiled.Tests; -public partial class TmxSerializerMapTests +public partial class TmxMapReaderTests { private static Map MapWithGroup() => new Map { diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs b/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs similarity index 98% rename from DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs index 31a658e..8f4459e 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs +++ b/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs @@ -1,6 +1,6 @@ namespace DotTiled.Tests; -public partial class TmxSerializerMapTests +public partial class TmxMapReaderTests { private static Map MapWithObjectTemplate() => new Map { diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/simple-tileset-embed.cs b/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs similarity index 97% rename from DotTiled.Tests/TmxSerializer/TestData/Map/simple-tileset-embed.cs rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs index ad27d8e..7dfb740 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/Map/simple-tileset-embed.cs +++ b/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs @@ -1,6 +1,6 @@ namespace DotTiled.Tests; -public partial class TmxSerializerMapTests +public partial class TmxMapReaderTests { private static Map SimpleMapWithEmbeddedTileset() => new Map { diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/simple-tileset-embed.tmx b/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.tmx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Map/simple-tileset-embed.tmx rename to DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.tmx diff --git a/DotTiled.Tests/TmxSerializer/TestData/Template/map-with-object-template.tx b/DotTiled.Tests/Serialization/Tmx/TestData/Template/map-with-object-template.tx similarity index 100% rename from DotTiled.Tests/TmxSerializer/TestData/Template/map-with-object-template.tx rename to DotTiled.Tests/Serialization/Tmx/TestData/Template/map-with-object-template.tx diff --git a/DotTiled.Tests/TmxSerializer/TestData/TestData.cs b/DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs similarity index 70% rename from DotTiled.Tests/TmxSerializer/TestData/TestData.cs rename to DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs index 7dbc2ad..667fec0 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/TestData.cs +++ b/DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs @@ -2,12 +2,12 @@ using System.Xml; namespace DotTiled.Tests; -public static class TmxSerializerTestData +public static class TmxMapReaderTestData { - public static XmlReader GetReaderFor(string testDataFile) + public static XmlReader GetXmlReaderFor(string testDataFile) { var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; - using var stream = typeof(TmxSerializerTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + using var stream = typeof(TmxMapReaderTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); using var stringReader = new StreamReader(stream); @@ -19,7 +19,7 @@ public static class TmxSerializerTestData public static string GetRawStringFor(string testDataFile) { var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; - using var stream = typeof(TmxSerializerTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + using var stream = typeof(TmxMapReaderTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); using var stringReader = new StreamReader(stream); diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs new file mode 100644 index 0000000..1924ac2 --- /dev/null +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -0,0 +1,150 @@ +using System.Xml; + +namespace DotTiled.Tests; + +public partial class TmxMapReaderTests +{ + [Fact] + public void TmxMapReaderConstructor_XmlReaderIsNull_ThrowsArgumentNullException() + { + // Arrange + XmlReader xmlReader = null!; + Func externalTilesetResolver = (_) => new Tileset(); + Func externalTemplateResolver = (_) => new Template { Object = new RectangleObject { } }; + + // Act + Action act = () => + { + using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + }; + + // Assert + Assert.Throws(act); + } + + [Fact] + public void TmxMapReaderConstructor_ExternalTilesetResolverIsNull_ThrowsArgumentNullException() + { + // Arrange + using var stringReader = new StringReader(""); + using var xmlReader = XmlReader.Create(stringReader); + Func externalTilesetResolver = null!; + Func externalTemplateResolver = (_) => new Template { Object = new RectangleObject { } }; + + // Act + Action act = () => + { + using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + }; + + // Assert + Assert.Throws(act); + } + + [Fact] + public void TmxMapReaderConstructor_ExternalTemplateResolverIsNull_ThrowsArgumentNullException() + { + // Arrange + using var stringReader = new StringReader(""); + using var xmlReader = XmlReader.Create(stringReader); + Func externalTilesetResolver = (_) => new Tileset(); + Func externalTemplateResolver = null!; + + // Act + Action act = () => + { + using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + }; + + // Assert + Assert.Throws(act); + } + + [Fact] + public void TmxMapReaderConstructor_NoneNull_DoesNotThrow() + { + // Arrange + using var stringReader = new StringReader(""); + using var xmlReader = XmlReader.Create(stringReader); + Func externalTilesetResolver = (_) => new Tileset(); + Func externalTemplateResolver = (_) => new Template { Object = new RectangleObject { } }; + + // Act + using var tmxMapReader = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + + // Assert + Assert.NotNull(tmxMapReader); + } + + public static IEnumerable DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data => + [ + ["Serialization.Tmx.TestData.Map.empty-map-csv.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], + ["Serialization.Tmx.TestData.Map.empty-map-base64.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], + ["Serialization.Tmx.TestData.Map.empty-map-base64-gzip.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], + ["Serialization.Tmx.TestData.Map.empty-map-base64-zlib.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], + ["Serialization.Tmx.TestData.Map.simple-tileset-embed.tmx", SimpleMapWithEmbeddedTileset()], + ["Serialization.Tmx.TestData.Map.empty-map-properties.tmx", EmptyMapWithProperties()], + ]; + + [Theory] + [MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] + public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) + { + // Arrange + using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); + static Template ResolveTemplate(string source) + { + using var xmlTemplateReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Template.{source}"); + using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); + return templateReader.ReadTemplate(); + } + static Tileset ResolveTileset(string source) + { + using var xmlTilesetReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Tileset.{source}"); + using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); + return tilesetReader.ReadTileset(); + } + using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate); + + // Act + var map = mapReader.ReadMap(); + + // Assert + Assert.NotNull(map); + DotTiledAssert.AssertMap(expectedMap, map); + } + + public static IEnumerable DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => + [ + ["Serialization.Tmx.TestData.Map.map-with-object-template.tmx", MapWithObjectTemplate()], + ["Serialization.Tmx.TestData.Map.map-with-group.tmx", MapWithGroup()], + ]; + + [Theory] + [MemberData(nameof(DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] + public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) + { + // Arrange + using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); + static Template ResolveTemplate(string source) + { + using var xmlTemplateReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Template.{source}"); + using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); + return templateReader.ReadTemplate(); + } + static Tileset ResolveTileset(string source) + { + using var xmlTilesetReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Tileset.{source}"); + using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); + return tilesetReader.ReadTileset(); + } + using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate); + + // Act + var map = mapReader.ReadMap(); + + // Assert + Assert.NotNull(map); + DotTiledAssert.AssertMap(expectedMap, map); + } +} diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs deleted file mode 100644 index 5a54e7c..0000000 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs +++ /dev/null @@ -1,242 +0,0 @@ -namespace DotTiled.Tests; - -public partial class TmxSerializerMapTests -{ - private static void AssertMap(Map actual, Map expected) - { - // Attributes - Assert.Equal(expected.Version, actual.Version); - Assert.Equal(expected.TiledVersion, actual.TiledVersion); - Assert.Equal(expected.Class, actual.Class); - Assert.Equal(expected.Orientation, actual.Orientation); - Assert.Equal(expected.RenderOrder, actual.RenderOrder); - Assert.Equal(expected.CompressionLevel, actual.CompressionLevel); - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); - Assert.Equal(expected.TileWidth, actual.TileWidth); - Assert.Equal(expected.TileHeight, actual.TileHeight); - Assert.Equal(expected.HexSideLength, actual.HexSideLength); - Assert.Equal(expected.StaggerAxis, actual.StaggerAxis); - Assert.Equal(expected.StaggerIndex, actual.StaggerIndex); - Assert.Equal(expected.ParallaxOriginX, actual.ParallaxOriginX); - Assert.Equal(expected.ParallaxOriginY, actual.ParallaxOriginY); - Assert.Equal(expected.BackgroundColor, actual.BackgroundColor); - Assert.Equal(expected.NextLayerID, actual.NextLayerID); - Assert.Equal(expected.NextObjectID, actual.NextObjectID); - Assert.Equal(expected.Infinite, actual.Infinite); - - TmxSerializerPropertiesTests.AssertProperties(actual.Properties, expected.Properties); - - Assert.NotNull(actual.Tilesets); - Assert.Equal(expected.Tilesets.Count, actual.Tilesets.Count); - for (var i = 0; i < expected.Tilesets.Count; i++) - TmxSerializerTilesetTests.AssertTileset(actual.Tilesets[i], expected.Tilesets[i]); - - Assert.NotNull(actual.Layers); - Assert.Equal(expected.Layers.Count, actual.Layers.Count); - for (var i = 0; i < expected.Layers.Count; i++) - TmxSerializerLayerTests.AssertLayer(actual.Layers[i], expected.Layers[i]); - } - - public static IEnumerable DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data => - [ - ["TmxSerializer.TestData.Map.empty-map-csv.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], - ["TmxSerializer.TestData.Map.empty-map-base64.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], - ["TmxSerializer.TestData.Map.empty-map-base64-gzip.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], - ["TmxSerializer.TestData.Map.empty-map-base64-zlib.tmx", EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], - ["TmxSerializer.TestData.Map.simple-tileset-embed.tmx", SimpleMapWithEmbeddedTileset()], - ["TmxSerializer.TestData.Map.empty-map-properties.tmx", EmptyMapWithProperties()], - ]; - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] - public void DeserializeMapFromXmlReader_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) - { - // Arrange - using var reader = TmxSerializerTestData.GetReaderFor(testDataFile); - var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); - Func externalTilesetResolver = (TmxSerializer serializer, string s) => - throw new NotSupportedException("External tilesets are not supported in this test"); - Func externalTemplateResolver = (TmxSerializer serializer, string s) => - throw new NotSupportedException("External templates are not supported in this test"); - var tmxSerializer = new TmxSerializer( - externalTilesetResolver, - externalTemplateResolver); - - // Act - - static Template ResolveTemplate(string source) - { - using var xmlTemplateReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Template.{source}"); - using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); - return templateReader.ReadTemplate(); - } - - static Tileset ResolveTileset(string source) - { - using var xmlTilesetReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Tileset.{source}"); - using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); - return tilesetReader.ReadTileset(); - } - - var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate); - - var map = mapReader.ReadMap(); - var raw = tmxSerializer.DeserializeMap(testDataFileText); - - // Assert - Assert.NotNull(map); - AssertMap(map, expectedMap); - - Assert.NotNull(raw); - AssertMap(raw, expectedMap); - - AssertMap(map, raw); - } - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] - public void DeserializeMapFromString_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) - { - // Arrange - var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); - Func externalTilesetResolver = (TmxSerializer serializer, string s) => - throw new NotSupportedException("External tilesets are not supported in this test"); - Func externalTemplateResolver = (TmxSerializer serializer, string s) => - throw new NotSupportedException("External templates are not supported in this test"); - var tmxSerializer = new TmxSerializer( - externalTilesetResolver, - externalTemplateResolver); - - // Act - var raw = tmxSerializer.DeserializeMap(testDataFileText); - - // Assert - Assert.NotNull(raw); - AssertMap(raw, expectedMap); - } - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] - public void DeserializeMapFromStringFromXmlReader_ValidXmlNoExternalTilesets_Equal(string testDataFile, Map expectedMap) - { - // Arrange - using var reader = TmxSerializerTestData.GetReaderFor(testDataFile); - var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); - Func externalTilesetResolver = (TmxSerializer serializer, string s) => - throw new NotSupportedException("External tilesets are not supported in this test"); - Func externalTemplateResolver = (TmxSerializer serializer, string s) => - throw new NotSupportedException("External templates are not supported in this test"); - var tmxSerializer = new TmxSerializer( - externalTilesetResolver, - externalTemplateResolver); - - // Act - var map = tmxSerializer.DeserializeMap(reader); - var raw = tmxSerializer.DeserializeMap(testDataFileText); - - // Assert - Assert.NotNull(map); - Assert.NotNull(raw); - - AssertMap(map, raw); - AssertMap(map, expectedMap); - AssertMap(raw, expectedMap); - } - - public static IEnumerable DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => - [ - ["TmxSerializer.TestData.Map.map-with-object-template.tmx", MapWithObjectTemplate()], - ["TmxSerializer.TestData.Map.map-with-group.tmx", MapWithGroup()], - ]; - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] - public void DeserializeMapFromXmlReader_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) - { - // Arrange - using var reader = TmxSerializerTestData.GetReaderFor(testDataFile); - Func externalTilesetResolver = (TmxSerializer serializer, string s) => - { - using var tilesetReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Tileset.{s}"); - return serializer.DeserializeTileset(tilesetReader); - }; - Func externalTemplateResolver = (TmxSerializer serializer, string s) => - { - using var templateReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Template.{s}"); - return serializer.DeserializeTemplate(templateReader); - }; - var tmxSerializer = new TmxSerializer( - externalTilesetResolver, - externalTemplateResolver); - - // Act - var map = tmxSerializer.DeserializeMap(reader); - - // Assert - Assert.NotNull(map); - AssertMap(map, expectedMap); - } - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] - public void DeserializeMapFromString_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) - { - // Arrange - var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); - Func externalTilesetResolver = (TmxSerializer serializer, string s) => - { - using var tilesetReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Tileset.{s}"); - return serializer.DeserializeTileset(tilesetReader); - }; - Func externalTemplateResolver = (TmxSerializer serializer, string s) => - { - using var templateReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Template.{s}"); - return serializer.DeserializeTemplate(templateReader); - }; - var tmxSerializer = new TmxSerializer( - externalTilesetResolver, - externalTemplateResolver); - - // Act - var map = tmxSerializer.DeserializeMap(testDataFileText); - - // Assert - Assert.NotNull(map); - AssertMap(map, expectedMap); - } - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] - public void DeserializeMapFromStringFromXmlReader_ValidXmlExternalTilesetsAndTemplates_Equal(string testDataFile, Map expectedMap) - { - // Arrange - using var reader = TmxSerializerTestData.GetReaderFor(testDataFile); - var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); - Func externalTilesetResolver = (TmxSerializer serializer, string s) => - { - using var tilesetReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Tileset.{s}"); - return serializer.DeserializeTileset(tilesetReader); - }; - Func externalTemplateResolver = (TmxSerializer serializer, string s) => - { - using var templateReader = TmxSerializerTestData.GetReaderFor($"TmxSerializer.TestData.Template.{s}"); - return serializer.DeserializeTemplate(templateReader); - }; - var tmxSerializer = new TmxSerializer( - externalTilesetResolver, - externalTemplateResolver); - - // Act - var map = tmxSerializer.DeserializeMap(reader); - var raw = tmxSerializer.DeserializeMap(testDataFileText); - - // Assert - Assert.NotNull(map); - Assert.NotNull(raw); - - AssertMap(map, raw); - AssertMap(map, expectedMap); - AssertMap(raw, expectedMap); - } -} diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs b/DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs deleted file mode 100644 index 0c27cf1..0000000 --- a/DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace DotTiled.Tests; - -public class TmxSerializerTests -{ - [Fact] - public void TmxSerializerConstructor_ExternalTilesetResolverIsNull_ThrowsArgumentNullException() - { - // Arrange - Func externalTilesetResolver = null!; - Func externalTemplateResolver = null!; - - // Act - Action act = () => _ = new TmxSerializer(externalTilesetResolver, externalTemplateResolver); - - // Assert - Assert.Throws(act); - } - - [Fact] - public void TmxSerializerConstructor_ExternalTilesetResolverIsNotNull_DoesNotThrow() - { - // Arrange - Func externalTilesetResolver = (_, _) => new Tileset(); - Func externalTemplateResolver = (_, _) => new Template { Object = new RectangleObject { } }; - - // Act - var tmxSerializer = new TmxSerializer(externalTilesetResolver, externalTemplateResolver); - - // Assert - Assert.NotNull(tmxSerializer); - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Chunk.cs b/DotTiled/TmxSerializer/TmxSerializer.Chunk.cs deleted file mode 100644 index 47a2cb2..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.Chunk.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private Chunk ReadChunk(XmlReader reader, DataEncoding? encoding, DataCompression? compression) - { - var x = reader.GetRequiredAttributeParseable("x"); - var y = reader.GetRequiredAttributeParseable("y"); - var width = reader.GetRequiredAttributeParseable("width"); - var height = reader.GetRequiredAttributeParseable("height"); - - var usesTileChildrenInsteadOfRawData = encoding is null; - if (usesTileChildrenInsteadOfRawData) - { - var globalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("chunk", reader); - var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags); - return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags }; - } - else - { - var globalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression); - var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(globalTileIDsWithFlippingFlags); - return new Chunk { X = x, Y = y, Width = width, Height = height, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags }; - } - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Data.cs b/DotTiled/TmxSerializer/TmxSerializer.Data.cs deleted file mode 100644 index 4725614..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.Data.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private Data ReadData(XmlReader reader, bool usesChunks) - { - var encoding = reader.GetOptionalAttributeEnum("encoding", e => e switch - { - "csv" => DataEncoding.Csv, - "base64" => DataEncoding.Base64, - _ => throw new XmlException("Invalid encoding") - }); - var compression = reader.GetOptionalAttributeEnum("compression", c => c switch - { - "gzip" => DataCompression.GZip, - "zlib" => DataCompression.ZLib, - "zstd" => DataCompression.ZStd, - _ => throw new XmlException("Invalid compression") - }); - - if (usesChunks) - { - var chunks = reader - .ReadList("data", "chunk", (r) => ReadChunk(r, encoding, compression)) - .ToArray(); - return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = null, Chunks = chunks }; - } - - var usesTileChildrenInsteadOfRawData = encoding is null && compression is null; - if (usesTileChildrenInsteadOfRawData) - { - var tileChildrenGlobalTileIDsWithFlippingFlags = ReadTileChildrenInWrapper("data", reader); - var (tileChildrenGlobalTileIDs, tileChildrenFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(tileChildrenGlobalTileIDsWithFlippingFlags); - return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = tileChildrenGlobalTileIDs, FlippingFlags = tileChildrenFlippingFlags, Chunks = null }; - } - - var rawDataGlobalTileIDsWithFlippingFlags = ReadRawData(reader, encoding!.Value, compression); - var (rawDataGlobalTileIDs, rawDataFlippingFlags) = ReadAndClearFlippingFlagsFromGIDs(rawDataGlobalTileIDsWithFlippingFlags); - return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = rawDataGlobalTileIDs, FlippingFlags = rawDataFlippingFlags, Chunks = null }; - } - - private (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs) - { - var clearedGlobalTileIDs = new uint[globalTileIDs.Length]; - var flippingFlags = new FlippingFlags[globalTileIDs.Length]; - for (var i = 0; i < globalTileIDs.Length; i++) - { - var gid = globalTileIDs[i]; - var flags = gid & 0xF0000000u; - flippingFlags[i] = (FlippingFlags)flags; - clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu; - } - - return (clearedGlobalTileIDs, flippingFlags); - } - - private uint[] ReadTileChildrenInWrapper(string wrapper, XmlReader reader) - { - return reader.ReadList(wrapper, "tile", (r) => r.GetOptionalAttributeParseable("gid") ?? 0).ToArray(); - } - - private uint[] ReadRawData(XmlReader reader, DataEncoding encoding, DataCompression? compression) - { - var data = reader.ReadElementContentAsString(); - if (encoding == DataEncoding.Csv) - return ParseCsvData(data); - - using var bytes = new MemoryStream(Convert.FromBase64String(data)); - if (compression is null) - return ReadMemoryStreamAsInt32Array(bytes); - - var decompressed = compression switch - { - DataCompression.GZip => DecompressGZip(bytes), - DataCompression.ZLib => DecompressZLib(bytes), - DataCompression.ZStd => throw new NotSupportedException("ZStd compression is not supported."), - _ => throw new XmlException("Invalid compression") - }; - - return decompressed; - } - - private uint[] ParseCsvData(string data) - { - var values = data - .Split((char[])['\n', '\r', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .Select(uint.Parse) - .ToArray(); - return values; - } - - private uint[] ReadMemoryStreamAsInt32Array(Stream stream) - { - var finalValues = new List(); - var int32Bytes = new byte[4]; - while (stream.Read(int32Bytes, 0, 4) == 4) - { - var value = BitConverter.ToUInt32(int32Bytes, 0); - finalValues.Add(value); - } - return finalValues.ToArray(); - } - - private uint[] DecompressGZip(MemoryStream stream) - { - using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress); - return ReadMemoryStreamAsInt32Array(decompressedStream); - } - - private uint[] DecompressZLib(MemoryStream stream) - { - using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress); - return ReadMemoryStreamAsInt32Array(decompressedStream); - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs b/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs deleted file mode 100644 index 14fcfaa..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private static class Helpers - { - public static void SetAtMostOnce(ref T? field, T value, string fieldName) - { - if (field is not null) - throw new InvalidOperationException($"{fieldName} already set"); - - field = value; - } - - public static void SetAtMostOnceUsingCounter(ref T? field, T value, string fieldName, ref int counter) - { - if (counter > 0) - throw new InvalidOperationException($"{fieldName} already set"); - - field = value; - counter++; - } - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Map.cs b/DotTiled/TmxSerializer/TmxSerializer.Map.cs deleted file mode 100644 index 799b217..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.Map.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private Map ReadMap(XmlReader reader) - { - // Attributes - var version = reader.GetRequiredAttribute("version"); - var tiledVersion = reader.GetRequiredAttribute("tiledversion"); - var @class = reader.GetOptionalAttribute("class") ?? ""; - var orientation = reader.GetRequiredAttributeEnum("orientation", s => s switch - { - "orthogonal" => MapOrientation.Orthogonal, - "isometric" => MapOrientation.Isometric, - "staggered" => MapOrientation.Staggered, - "hexagonal" => MapOrientation.Hexagonal, - _ => throw new Exception($"Unknown orientation '{s}'") - }); - var renderOrder = reader.GetOptionalAttributeEnum("renderorder", s => s switch - { - "right-down" => RenderOrder.RightDown, - "right-up" => RenderOrder.RightUp, - "left-down" => RenderOrder.LeftDown, - "left-up" => RenderOrder.LeftUp, - _ => throw new Exception($"Unknown render order '{s}'") - }) ?? RenderOrder.RightDown; - var compressionLevel = reader.GetOptionalAttributeParseable("compressionlevel") ?? -1; - var width = reader.GetRequiredAttributeParseable("width"); - var height = reader.GetRequiredAttributeParseable("height"); - var tileWidth = reader.GetRequiredAttributeParseable("tilewidth"); - var tileHeight = reader.GetRequiredAttributeParseable("tileheight"); - var hexSideLength = reader.GetOptionalAttributeParseable("hexsidelength"); - var staggerAxis = reader.GetOptionalAttributeEnum("staggeraxis", s => s switch - { - "x" => StaggerAxis.X, - "y" => StaggerAxis.Y, - _ => throw new Exception($"Unknown stagger axis '{s}'") - }); - var staggerIndex = reader.GetOptionalAttributeEnum("staggerindex", s => s switch - { - "odd" => StaggerIndex.Odd, - "even" => StaggerIndex.Even, - _ => throw new Exception($"Unknown stagger index '{s}'") - }); - var parallaxOriginX = reader.GetOptionalAttributeParseable("parallaxoriginx") ?? 0.0f; - var parallaxOriginY = reader.GetOptionalAttributeParseable("parallaxoriginy") ?? 0.0f; - var backgroundColor = reader.GetOptionalAttributeClass("backgroundcolor") ?? Color.Parse("#00000000", CultureInfo.InvariantCulture); - var nextLayerID = reader.GetRequiredAttributeParseable("nextlayerid"); - var nextObjectID = reader.GetRequiredAttributeParseable("nextobjectid"); - var infinite = (reader.GetOptionalAttributeParseable("infinite") ?? 0) == 1; - - // At most one of - Dictionary? properties = null; - - // Any number of - List layers = []; - List tilesets = []; - - reader.ProcessChildren("map", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "tileset" => () => tilesets.Add(ReadTileset(r)), - "layer" => () => layers.Add(ReadTileLayer(r, dataUsesChunks: infinite)), - "objectgroup" => () => layers.Add(ReadObjectLayer(r)), - "imagelayer" => () => layers.Add(ReadImageLayer(r)), - "group" => () => layers.Add(ReadGroup(r)), - _ => r.Skip - }); - - return new Map - { - Version = version, - TiledVersion = tiledVersion, - Class = @class, - Orientation = orientation, - RenderOrder = renderOrder, - CompressionLevel = compressionLevel, - Width = width, - Height = height, - TileWidth = tileWidth, - TileHeight = tileHeight, - HexSideLength = hexSideLength, - StaggerAxis = staggerAxis, - StaggerIndex = staggerIndex, - ParallaxOriginX = parallaxOriginX, - ParallaxOriginY = parallaxOriginY, - BackgroundColor = backgroundColor, - NextLayerID = nextLayerID, - NextObjectID = nextObjectID, - Infinite = infinite, - Properties = properties, - Tilesets = tilesets, - Layers = layers - }; - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs b/DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs deleted file mode 100644 index df97300..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs +++ /dev/null @@ -1,309 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Numerics; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private ObjectLayer ReadObjectLayer(XmlReader reader) - { - // Attributes - var id = reader.GetRequiredAttributeParseable("id"); - var name = reader.GetOptionalAttribute("name") ?? ""; - var @class = reader.GetOptionalAttribute("class") ?? ""; - var x = reader.GetOptionalAttributeParseable("x") ?? 0; - var y = reader.GetOptionalAttributeParseable("y") ?? 0; - var width = reader.GetOptionalAttributeParseable("width"); - var height = reader.GetOptionalAttributeParseable("height"); - var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; - var visible = reader.GetOptionalAttributeParseable("visible") ?? true; - var tintColor = reader.GetOptionalAttributeClass("tintcolor"); - var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; - var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; - var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; - var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; - var color = reader.GetOptionalAttributeClass("color"); - var drawOrder = reader.GetOptionalAttributeEnum("draworder", s => s switch - { - "topdown" => DrawOrder.TopDown, - "index" => DrawOrder.Index, - _ => throw new Exception($"Unknown draw order '{s}'") - }) ?? DrawOrder.TopDown; - - // Elements - Dictionary? properties = null; - List objects = []; - - reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "object" => () => objects.Add(ReadObject(r)), - _ => r.Skip - }); - - return new ObjectLayer - { - ID = id, - Name = name, - Class = @class, - X = x, - Y = y, - Width = width, - Height = height, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Color = color, - Properties = properties, - DrawOrder = drawOrder, - Objects = objects - }; - } - - private Object ReadObject(XmlReader reader) - { - // Attributes - var template = reader.GetOptionalAttribute("template"); - - uint? idDefault = null; - string nameDefault = ""; - string typeDefault = ""; - float xDefault = 0f; - float yDefault = 0f; - float widthDefault = 0f; - float heightDefault = 0f; - float rotationDefault = 0f; - uint? gidDefault = null; - bool visibleDefault = true; - Dictionary? propertiesDefault = null; - - // Perform template copy first - if (template is not null) - { - var resolvedTemplate = _externalTemplateResolver(this, template); - var templObj = resolvedTemplate.Object; - - idDefault = templObj.ID; - nameDefault = templObj.Name; - typeDefault = templObj.Type; - xDefault = templObj.X; - yDefault = templObj.Y; - widthDefault = templObj.Width; - heightDefault = templObj.Height; - rotationDefault = templObj.Rotation; - gidDefault = templObj.GID; - visibleDefault = templObj.Visible; - propertiesDefault = templObj.Properties; - } - - var id = reader.GetOptionalAttributeParseable("id") ?? idDefault; - var name = reader.GetOptionalAttribute("name") ?? nameDefault; - var type = reader.GetOptionalAttribute("type") ?? typeDefault; - var x = reader.GetOptionalAttributeParseable("x") ?? xDefault; - var y = reader.GetOptionalAttributeParseable("y") ?? yDefault; - var width = reader.GetOptionalAttributeParseable("width") ?? widthDefault; - var height = reader.GetOptionalAttributeParseable("height") ?? heightDefault; - var rotation = reader.GetOptionalAttributeParseable("rotation") ?? rotationDefault; - var gid = reader.GetOptionalAttributeParseable("gid") ?? gidDefault; - var visible = reader.GetOptionalAttributeParseable("visible") ?? visibleDefault; - - // Elements - Object? obj = null; - int propertiesCounter = 0; - Dictionary? properties = propertiesDefault; - - reader.ProcessChildren("object", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, MergeProperties(properties, ReadProperties(r)), "Properties", ref propertiesCounter), - "ellipse" => () => Helpers.SetAtMostOnce(ref obj, ReadEllipseObject(r), "Object marker"), - "point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r), "Object marker"), - "polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r), "Object marker"), - "polyline" => () => Helpers.SetAtMostOnce(ref obj, ReadPolylineObject(r), "Object marker"), - "text" => () => Helpers.SetAtMostOnce(ref obj, ReadTextObject(r), "Object marker"), - _ => throw new Exception($"Unknown object marker '{elementName}'") - }); - - if (obj is null) - { - obj = new RectangleObject { ID = id }; - reader.Skip(); - } - - obj.Name = name; - obj.Type = type; - obj.X = x; - obj.Y = y; - obj.Width = width; - obj.Height = height; - obj.Rotation = rotation; - obj.GID = gid; - obj.Visible = visible; - obj.Template = template; - obj.Properties = properties; - - return obj; - } - - private Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) - { - if (baseProperties is null) - return overrideProperties ?? new Dictionary(); - - if (overrideProperties is null) - return baseProperties; - - var result = new Dictionary(baseProperties); - foreach (var (key, value) in overrideProperties) - { - if (!result.TryGetValue(key, out var baseProp)) - { - result[key] = value; - continue; - } - else - { - if (value is ClassProperty classProp) - { - ((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); - } - else - { - result[key] = value; - } - } - } - - return result; - } - - private EllipseObject ReadEllipseObject(XmlReader reader) - { - reader.Skip(); - return new EllipseObject { }; - } - - private PointObject ReadPointObject(XmlReader reader) - { - reader.Skip(); - return new PointObject { }; - } - - private PolygonObject ReadPolygonObject(XmlReader reader) - { - // Attributes - var points = reader.GetRequiredAttributeParseable>("points", s => - { - // Takes on format "x1,y1 x2,y2 x3,y3 ..." - var coords = s.Split(' '); - return coords.Select(c => - { - var xy = c.Split(','); - return new Vector2(float.Parse(xy[0], CultureInfo.InvariantCulture), float.Parse(xy[1], CultureInfo.InvariantCulture)); - }).ToList(); - }); - - reader.ReadStartElement("polygon"); - return new PolygonObject { Points = points }; - } - - private PolylineObject ReadPolylineObject(XmlReader reader) - { - // Attributes - var points = reader.GetRequiredAttributeParseable>("points", s => - { - // Takes on format "x1,y1 x2,y2 x3,y3 ..." - var coords = s.Split(' '); - return coords.Select(c => - { - var xy = c.Split(','); - return new Vector2(float.Parse(xy[0], CultureInfo.InvariantCulture), float.Parse(xy[1], CultureInfo.InvariantCulture)); - }).ToList(); - }); - - reader.ReadStartElement("polyline"); - return new PolylineObject { Points = points }; - } - - private TextObject ReadTextObject(XmlReader reader) - { - // Attributes - var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif"; - var pixelSize = reader.GetOptionalAttributeParseable("pixelsize") ?? 16; - var wrap = reader.GetOptionalAttributeParseable("wrap") ?? false; - var color = reader.GetOptionalAttributeClass("color") ?? Color.Parse("#000000", CultureInfo.InvariantCulture); - var bold = reader.GetOptionalAttributeParseable("bold") ?? false; - var italic = reader.GetOptionalAttributeParseable("italic") ?? false; - var underline = reader.GetOptionalAttributeParseable("underline") ?? false; - var strikeout = reader.GetOptionalAttributeParseable("strikeout") ?? false; - var kerning = reader.GetOptionalAttributeParseable("kerning") ?? true; - var hAlign = reader.GetOptionalAttributeEnum("halign", s => s switch - { - "left" => TextHorizontalAlignment.Left, - "center" => TextHorizontalAlignment.Center, - "right" => TextHorizontalAlignment.Right, - "justify" => TextHorizontalAlignment.Justify, - _ => throw new Exception($"Unknown horizontal alignment '{s}'") - }) ?? TextHorizontalAlignment.Left; - var vAlign = reader.GetOptionalAttributeEnum("valign", s => s switch - { - "top" => TextVerticalAlignment.Top, - "center" => TextVerticalAlignment.Center, - "bottom" => TextVerticalAlignment.Bottom, - _ => throw new Exception($"Unknown vertical alignment '{s}'") - }) ?? TextVerticalAlignment.Top; - - // Elements - var text = reader.ReadElementContentAsString("text", ""); - - return new TextObject - { - FontFamily = fontFamily, - PixelSize = pixelSize, - Wrap = wrap, - Color = color, - Bold = bold, - Italic = italic, - Underline = underline, - Strikeout = strikeout, - Kerning = kerning, - HorizontalAlignment = hAlign, - VerticalAlignment = vAlign, - Text = text - }; - } - - private Template ReadTemplate(XmlReader reader) - { - // No attributes - - // At most one of - Tileset? tileset = null; - - // Should contain exactly one of - Object? obj = null; - - reader.ProcessChildren("template", (r, elementName) => elementName switch - { - "tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(r), "Tileset"), - "object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(r), "Object"), - _ => r.Skip - }); - - if (obj is null) - throw new NotSupportedException("Template must contain exactly one object"); - - return new Template - { - Tileset = tileset, - Object = obj - }; - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Properties.cs b/DotTiled/TmxSerializer/TmxSerializer.Properties.cs deleted file mode 100644 index 1e7cd7e..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.Properties.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private Dictionary ReadProperties(XmlReader reader) - { - return reader.ReadList("properties", "property", (r) => - { - var name = r.GetRequiredAttribute("name"); - var type = r.GetOptionalAttributeEnum("type", (s) => s switch - { - "string" => PropertyType.String, - "int" => PropertyType.Int, - "float" => PropertyType.Float, - "bool" => PropertyType.Bool, - "color" => PropertyType.Color, - "file" => PropertyType.File, - "object" => PropertyType.Object, - "class" => PropertyType.Class, - _ => throw new XmlException("Invalid property type") - }) ?? PropertyType.String; - - IProperty property = type switch - { - PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") }, - PropertyType.Int => new IntProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, - PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, - PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, - PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, - PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") }, - PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, - PropertyType.Class => ReadClassProperty(r), - _ => throw new XmlException("Invalid property type") - }; - return (name, property); - }).ToDictionary(x => x.name, x => x.property); - } - - private ClassProperty ReadClassProperty(XmlReader reader) - { - var name = reader.GetRequiredAttribute("name"); - var propertyType = reader.GetRequiredAttribute("propertytype"); - - reader.ReadStartElement("property"); - var properties = ReadProperties(reader); - reader.ReadEndElement(); - - return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs b/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs deleted file mode 100644 index bd23d1f..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private TileLayer ReadTileLayer(XmlReader reader, bool dataUsesChunks) - { - var id = reader.GetRequiredAttributeParseable("id"); - var name = reader.GetOptionalAttribute("name") ?? ""; - var @class = reader.GetOptionalAttribute("class") ?? ""; - var x = reader.GetOptionalAttributeParseable("x") ?? 0; - var y = reader.GetOptionalAttributeParseable("y") ?? 0; - var width = reader.GetRequiredAttributeParseable("width"); - var height = reader.GetRequiredAttributeParseable("height"); - var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; - var visible = reader.GetOptionalAttributeParseable("visible") ?? true; - var tintColor = reader.GetOptionalAttributeClass("tintcolor"); - var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; - var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; - var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; - var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; - - Dictionary? properties = null; - Data? data = null; - - reader.ProcessChildren("layer", (r, elementName) => elementName switch - { - "data" => () => Helpers.SetAtMostOnce(ref data, ReadData(r, dataUsesChunks), "Data"), - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - _ => r.Skip - }); - - return new TileLayer - { - ID = id, - Name = name, - Class = @class, - X = x, - Y = y, - Width = width, - Height = height, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Data = data, - Properties = properties - }; - } - - private ImageLayer ReadImageLayer(XmlReader reader) - { - var id = reader.GetRequiredAttributeParseable("id"); - var name = reader.GetOptionalAttribute("name") ?? ""; - var @class = reader.GetOptionalAttribute("class") ?? ""; - var x = reader.GetOptionalAttributeParseable("x") ?? 0; - var y = reader.GetOptionalAttributeParseable("y") ?? 0; - var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; - var visible = reader.GetOptionalAttributeParseable("visible") ?? true; - var tintColor = reader.GetOptionalAttributeClass("tintcolor"); - var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; - var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; - var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; - var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; - var repeatX = reader.GetRequiredAttributeParseable("repeatx"); - var repeatY = reader.GetRequiredAttributeParseable("repeaty"); - - Dictionary? properties = null; - Image? image = null; - - reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch - { - "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - _ => r.Skip - }); - - return new ImageLayer - { - ID = id, - Name = name, - Class = @class, - X = x, - Y = y, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Properties = properties, - Image = image, - RepeatX = repeatX, - RepeatY = repeatY - }; - } - - private Group ReadGroup(XmlReader reader) - { - var id = reader.GetRequiredAttributeParseable("id"); - var name = reader.GetOptionalAttribute("name") ?? ""; - var @class = reader.GetOptionalAttribute("class") ?? ""; - var opacity = reader.GetOptionalAttributeParseable("opacity") ?? 1.0f; - var visible = reader.GetOptionalAttributeParseable("visible") ?? true; - var tintColor = reader.GetOptionalAttributeClass("tintcolor"); - var offsetX = reader.GetOptionalAttributeParseable("offsetx") ?? 0.0f; - var offsetY = reader.GetOptionalAttributeParseable("offsety") ?? 0.0f; - var parallaxX = reader.GetOptionalAttributeParseable("parallaxx") ?? 1.0f; - var parallaxY = reader.GetOptionalAttributeParseable("parallaxy") ?? 1.0f; - - Dictionary? properties = null; - List layers = []; - - reader.ProcessChildren("group", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "layer" => () => layers.Add(ReadTileLayer(r, dataUsesChunks: false)), - "objectgroup" => () => layers.Add(ReadObjectLayer(r)), - "imagelayer" => () => layers.Add(ReadImageLayer(r)), - "group" => () => layers.Add(ReadGroup(r)), - _ => r.Skip - }); - - return new Group - { - ID = id, - Name = name, - Class = @class, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Properties = properties, - Layers = layers - }; - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Tileset.cs b/DotTiled/TmxSerializer/TmxSerializer.Tileset.cs deleted file mode 100644 index c16e037..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.Tileset.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private Tileset ReadTileset(XmlReader reader) - { - // Attributes - var version = reader.GetOptionalAttribute("version"); - var tiledVersion = reader.GetOptionalAttribute("tiledversion"); - var firstGID = reader.GetOptionalAttributeParseable("firstgid"); - var source = reader.GetOptionalAttribute("source"); - var name = reader.GetOptionalAttribute("name"); - var @class = reader.GetOptionalAttribute("class") ?? ""; - var tileWidth = reader.GetOptionalAttributeParseable("tilewidth"); - var tileHeight = reader.GetOptionalAttributeParseable("tileheight"); - var spacing = reader.GetOptionalAttributeParseable("spacing"); - var margin = reader.GetOptionalAttributeParseable("margin"); - var tileCount = reader.GetOptionalAttributeParseable("tilecount"); - var columns = reader.GetOptionalAttributeParseable("columns"); - var objectAlignment = reader.GetOptionalAttributeEnum("objectalignment", s => s switch - { - "unspecified" => ObjectAlignment.Unspecified, - "topleft" => ObjectAlignment.TopLeft, - "top" => ObjectAlignment.Top, - "topright" => ObjectAlignment.TopRight, - "left" => ObjectAlignment.Left, - "center" => ObjectAlignment.Center, - "right" => ObjectAlignment.Right, - "bottomleft" => ObjectAlignment.BottomLeft, - "bottom" => ObjectAlignment.Bottom, - "bottomright" => ObjectAlignment.BottomRight, - _ => throw new Exception($"Unknown object alignment '{s}'") - }) ?? ObjectAlignment.Unspecified; - var renderSize = reader.GetOptionalAttributeEnum("rendersize", s => s switch - { - "tile" => TileRenderSize.Tile, - "grid" => TileRenderSize.Grid, - _ => throw new Exception($"Unknown render size '{s}'") - }) ?? TileRenderSize.Tile; - var fillMode = reader.GetOptionalAttributeEnum("fillmode", s => s switch - { - "stretch" => FillMode.Stretch, - "preserve-aspect-fit" => FillMode.PreserveAspectFit, - _ => throw new Exception($"Unknown fill mode '{s}'") - }) ?? FillMode.Stretch; - - // Elements - Image? image = null; - TileOffset? tileOffset = null; - Grid? grid = null; - Dictionary? properties = null; - List? wangsets = null; - Transformations? transformations = null; - List tiles = []; - - reader.ProcessChildren("tileset", (r, elementName) => elementName switch - { - "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), - "tileoffset" => () => Helpers.SetAtMostOnce(ref tileOffset, ReadTileOffset(r), "TileOffset"), - "grid" => () => Helpers.SetAtMostOnce(ref grid, ReadGrid(r), "Grid"), - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(r), "Wangsets"), - "transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(r), "Transformations"), - "tile" => () => tiles.Add(ReadTile(r)), - _ => r.Skip - }); - - // Check if tileset is referring to external file - if (source is not null) - { - var resolvedTileset = _externalTilesetResolver(this, source); - resolvedTileset.FirstGID = firstGID; - resolvedTileset.Source = null; - return resolvedTileset; - } - - return new Tileset - { - Version = version, - TiledVersion = tiledVersion, - FirstGID = firstGID, - Source = source, - Name = name, - Class = @class, - TileWidth = tileWidth, - TileHeight = tileHeight, - Spacing = spacing, - Margin = margin, - TileCount = tileCount, - Columns = columns, - ObjectAlignment = objectAlignment, - RenderSize = renderSize, - FillMode = fillMode, - Image = image, - TileOffset = tileOffset, - Grid = grid, - Properties = properties, - Wangsets = wangsets, - Transformations = transformations, - Tiles = tiles - }; - } - - private Image ReadImage(XmlReader reader) - { - // Attributes - var format = reader.GetOptionalAttributeEnum("format", s => s switch - { - "png" => ImageFormat.Png, - "jpg" => ImageFormat.Jpg, - "bmp" => ImageFormat.Bmp, - "gif" => ImageFormat.Gif, - _ => throw new Exception($"Unknown image format '{s}'") - }); - var source = reader.GetOptionalAttribute("source"); - var transparentColor = reader.GetOptionalAttributeClass("trans"); - var width = reader.GetOptionalAttributeParseable("width"); - var height = reader.GetOptionalAttributeParseable("height"); - - reader.ProcessChildren("image", (r, elementName) => elementName switch - { - "data" => throw new NotSupportedException("Embedded image data is not supported."), - _ => r.Skip - }); - - return new Image - { - Format = format, - Source = source, - TransparentColor = transparentColor, - Width = width, - Height = height, - }; - } - - private TileOffset ReadTileOffset(XmlReader reader) - { - // Attributes - var x = reader.GetOptionalAttributeParseable("x") ?? 0f; - var y = reader.GetOptionalAttributeParseable("y") ?? 0f; - - reader.ReadStartElement("tileoffset"); - return new TileOffset { X = x, Y = y }; - } - - private Grid ReadGrid(XmlReader reader) - { - // Attributes - var orientation = reader.GetOptionalAttributeEnum("orientation", s => s switch - { - "orthogonal" => GridOrientation.Orthogonal, - "isometric" => GridOrientation.Isometric, - _ => throw new Exception($"Unknown orientation '{s}'") - }) ?? GridOrientation.Orthogonal; - var width = reader.GetRequiredAttributeParseable("width"); - var height = reader.GetRequiredAttributeParseable("height"); - - reader.ReadStartElement("grid"); - return new Grid { Orientation = orientation, Width = width, Height = height }; - } - - private Transformations ReadTransformations(XmlReader reader) - { - // Attributes - var hFlip = reader.GetOptionalAttributeParseable("hflip") ?? false; - var vFlip = reader.GetOptionalAttributeParseable("vflip") ?? false; - var rotate = reader.GetOptionalAttributeParseable("rotate") ?? false; - var preferUntransformed = reader.GetOptionalAttributeParseable("preferuntransformed") ?? false; - - reader.ReadStartElement("transformations"); - return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed }; - } - - private Tile ReadTile(XmlReader reader) - { - // Attributes - var id = reader.GetRequiredAttributeParseable("id"); - var type = reader.GetOptionalAttribute("type") ?? ""; - var probability = reader.GetOptionalAttributeParseable("probability") ?? 0f; - var x = reader.GetOptionalAttributeParseable("x") ?? 0; - var y = reader.GetOptionalAttributeParseable("y") ?? 0; - var width = reader.GetOptionalAttributeParseable("width"); - var height = reader.GetOptionalAttributeParseable("height"); - - // Elements - Dictionary? properties = null; - Image? image = null; - ObjectLayer? objectLayer = null; - List? animation = null; - - reader.ProcessChildren("tile", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), - "objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(r), "ObjectLayer"), - "animation" => () => Helpers.SetAtMostOnce(ref animation, r.ReadList("animation", "frame", (ar) => - { - var tileID = ar.GetRequiredAttributeParseable("tileid"); - var duration = ar.GetRequiredAttributeParseable("duration"); - return new Frame { TileID = tileID, Duration = duration }; - }), "Animation"), - _ => r.Skip - }); - - return new Tile - { - ID = id, - Type = type, - Probability = probability, - X = x, - Y = y, - Width = width ?? image?.Width ?? 0, - Height = height ?? image?.Height ?? 0, - Properties = properties, - Image = image, - ObjectLayer = objectLayer, - Animation = animation - }; - } - - private List ReadWangsets(XmlReader reader) - { - return reader.ReadList("wangsets", "wangset", ReadWangset); - } - - private Wangset ReadWangset(XmlReader reader) - { - // Attributes - var name = reader.GetRequiredAttribute("name"); - var @class = reader.GetOptionalAttribute("class") ?? ""; - var tile = reader.GetRequiredAttributeParseable("tile"); - - // Elements - Dictionary? properties = null; - List wangColors = []; - List wangTiles = []; - - reader.ProcessChildren("wangset", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "wangcolor" => () => wangColors.Add(ReadWangColor(r)), - "wangtile" => () => wangTiles.Add(ReadWangTile(r)), - _ => r.Skip - }); - - if (wangColors.Count > 254) - throw new ArgumentException("Wangset can have at most 254 Wang colors."); - - return new Wangset - { - Name = name, - Class = @class, - Tile = tile, - Properties = properties, - WangColors = wangColors, - WangTiles = wangTiles - }; - } - - private WangColor ReadWangColor(XmlReader reader) - { - // Attributes - var name = reader.GetRequiredAttribute("name"); - var @class = reader.GetOptionalAttribute("class") ?? ""; - var color = reader.GetRequiredAttributeParseable("color"); - var tile = reader.GetRequiredAttributeParseable("tile"); - var probability = reader.GetOptionalAttributeParseable("probability") ?? 0f; - - // Elements - Dictionary? properties = null; - - reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch - { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - _ => r.Skip - }); - - return new WangColor - { - Name = name, - Class = @class, - Color = color, - Tile = tile, - Probability = probability, - Properties = properties - }; - } - - private WangTile ReadWangTile(XmlReader reader) - { - // Attributes - var tileID = reader.GetRequiredAttributeParseable("tileid"); - var wangID = reader.GetRequiredAttributeParseable("wangid", s => - { - // Comma-separated list of indices (0-254) - var indices = s.Split(',').Select(i => byte.Parse(i)).ToArray(); - if (indices.Length > 8) - throw new ArgumentException("Wang ID can have at most 8 indices."); - return indices; - }); - - return new WangTile - { - TileID = tileID, - WangID = wangID - }; - } -} diff --git a/DotTiled/TmxSerializer/TmxSerializer.cs b/DotTiled/TmxSerializer/TmxSerializer.cs deleted file mode 100644 index 5f5c604..0000000 --- a/DotTiled/TmxSerializer/TmxSerializer.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Xml; - -namespace DotTiled; - -public partial class TmxSerializer -{ - private readonly Func _externalTilesetResolver; - private readonly Func _externalTemplateResolver; - - public TmxSerializer( - Func externalTilesetResolver, - Func externalTemplateResolver - ) - { - _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); - _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); - } - - public Map DeserializeMap(XmlReader reader) - { - reader.ReadToFollowing("map"); - return ReadMap(reader); - } - - public Map DeserializeMap(string xml) - { - using var stringReader = new StringReader(xml); - using var reader = XmlReader.Create(stringReader); - return DeserializeMap(reader); - } - - public Tileset DeserializeTileset(XmlReader reader) - { - reader.ReadToFollowing("tileset"); - return ReadTileset(reader); - } - - public Template DeserializeTemplate(XmlReader reader) - { - reader.ReadToFollowing("template"); - return ReadTemplate(reader); - } -} From 3e8f3fbfb9d413dd7fdbad307ad9c6bad3e0360f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 3 Aug 2024 21:07:31 +0200 Subject: [PATCH 3/6] Some benchmarking and README updates --- DotTiled.Benchmark/DotTiled.Benchmark.csproj | 1 + DotTiled.Benchmark/Program.cs | 76 ++++++++++++++-- README.md | 96 ++++++-------------- 3 files changed, 98 insertions(+), 75 deletions(-) diff --git a/DotTiled.Benchmark/DotTiled.Benchmark.csproj b/DotTiled.Benchmark/DotTiled.Benchmark.csproj index 3fc12d3..739d619 100644 --- a/DotTiled.Benchmark/DotTiled.Benchmark.csproj +++ b/DotTiled.Benchmark/DotTiled.Benchmark.csproj @@ -9,6 +9,7 @@ + diff --git a/DotTiled.Benchmark/Program.cs b/DotTiled.Benchmark/Program.cs index c44b79b..1abb982 100644 --- a/DotTiled.Benchmark/Program.cs +++ b/DotTiled.Benchmark/Program.cs @@ -1,26 +1,81 @@ using System; +using System.Collections.Immutable; using System.Security.Cryptography; +using System.Text; +using System.Xml; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; +using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; namespace MyBenchmarks { - public class MapLoader + [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] + [CategoriesColumn] + [Orderer(SummaryOrderPolicy.FastestToSlowest)] + public class MapLoading { - public MapLoader() + private string _tmxPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\Tmx\TestData\Map\empty-map-csv.tmx"; + private string _tmxContents = ""; + + public MapLoading() { + _tmxContents = System.IO.File.ReadAllText(_tmxPath); } - [Benchmark] - public DotTiled.Map LoadWithDotTiled() + [BenchmarkCategory("MapFromInMemoryTmxString")] + [Benchmark(Baseline = true, Description = "DotTiled")] + public DotTiled.Map LoadWithDotTiledFromInMemoryString() { - throw new NotImplementedException(); + using var stringReader = new StringReader(_tmxContents); + using var xmlReader = XmlReader.Create(stringReader); + using var mapReader = new DotTiled.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception()); + return mapReader.ReadMap(); } - [Benchmark] - public TiledLib.Map LoadWithTiledLib() + [BenchmarkCategory("MapFromTmxFile")] + [Benchmark(Baseline = true, Description = "DotTiled")] + public DotTiled.Map LoadWithDotTiledFromFile() { - throw new NotImplementedException(); + using var fileStream = System.IO.File.OpenRead(_tmxPath); + using var xmlReader = XmlReader.Create(fileStream); + using var mapReader = new DotTiled.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception()); + return mapReader.ReadMap(); + } + + [BenchmarkCategory("MapFromInMemoryTmxString")] + [Benchmark(Description = "TiledLib")] + public TiledLib.Map LoadWithTiledLibFromInMemoryString() + { + using var memStream = new MemoryStream(Encoding.UTF8.GetBytes(_tmxContents)); + return TiledLib.Map.FromStream(memStream); + } + + [BenchmarkCategory("MapFromTmxFile")] + [Benchmark(Description = "TiledLib")] + public TiledLib.Map LoadWithTiledLibFromFile() + { + using var fileStream = System.IO.File.OpenRead(_tmxPath); + var map = TiledLib.Map.FromStream(fileStream); + return map; + } + + [BenchmarkCategory("MapFromInMemoryTmxString")] + [Benchmark(Description = "TiledCSPlus")] + public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromInMemoryString() + { + using var memStream = new MemoryStream(Encoding.UTF8.GetBytes(_tmxContents)); + return new TiledCSPlus.TiledMap(memStream); + } + + [BenchmarkCategory("MapFromTmxFile")] + [Benchmark(Description = "TiledCSPlus")] + public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromFile() + { + using var fileStream = System.IO.File.OpenRead(_tmxPath); + return new TiledCSPlus.TiledMap(fileStream); } } @@ -28,7 +83,10 @@ namespace MyBenchmarks { public static void Main(string[] args) { - //var summary = BenchmarkRunner.Run(); + var config = BenchmarkDotNet.Configs.DefaultConfig.Instance + .WithOptions(ConfigOptions.DisableOptimizationsValidator) + .AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default); + var summary = BenchmarkRunner.Run(config); } } } diff --git a/README.md b/README.md index e0a1074..1614421 100644 --- a/README.md +++ b/README.md @@ -17,32 +17,48 @@ DotTiled is a simple and easy-to-use library for loading, saving, and managing [ # Feature coverage -**TBD**: Add a table displaying the features that DotTiled supports, and which features are not yet supported, and perhaps why they are not supported (yet or ever). - # Alternative libraries and comparison Other similar libraries exist, and you may want to consider them for your project as well: -| Library | Actively maintained | Supported formats | Feature coverage | Tiled version compatibility | Docs | License | Benchmark rank* | -| --- | --- | --- | --- | --- | --- | --- | --- | -| **DotTiled** | ✅ | `.tmx` `.tsx` `.tmj` `.tsj` `.tx` | | | Usage, API, XML docs | | | -| [TiledLib](https://github.com/Ragath/TiledLib.Net) |✅| | | | | | | -| [TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus) |✅| | | | | | | -| [TiledSharp](https://github.com/marshallward/TiledSharp) |❌| | | | | | | -| [TiledCS](https://github.com/TheBoneJarmer/TiledCS) |❌| | | | | | | -| [TiledNet](https://github.com/napen123/Tiled.Net) |❌| | | | | | | +| **Comparison** | **DotTiled** | [TiledLib](https://github.com/Ragath/TiledLib.Net) | [TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus) | [TiledSharp](https://github.com/marshallward/TiledSharp) | [TiledCS](https://github.com/TheBoneJarmer/TiledCS) | [TiledNet](https://github.com/napen123/Tiled.Net) | +|--------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| +| Actively maintained | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| Docs | Usage,
XML Docs | Usage | Usage, API,
XML Docs | Usage, API | Usage, XML Docs | Usage, XML Docs | +| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | +| Benchmark (time)* | 1.00 | 1.81 | 2.12 | - | - | - | +| Benchmark (memory)* | 1.00 | 1.42 | 2.03 | - | - | - | +| *Feature coverage
comparison* | | | | | | | +| `.tmx` (XML format) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | > [!NOTE] -> *Benchmark rank is based on the libraries' speed and memory usage when loading different types of maps and tilesets. Further explanations and details can be found in the below collapsible section. +> *Both benchmark time and memory ratios are relative to DotTiled. Lower is better. Benchmark (time) refers to the execution time of loading the same map from an in-memory string that contains XML data in the `.tmx` format. Benchmark (memory) refers to the memory allocated during that loading process. For further details on the benchmark results, see the collapsible section below.
-Comparison and benchmark details +Benchmark details -**TODO: Add table displaying feature availability** +#### Benchmark results -**TODO: Add table displaying benchmark results** +The following benchmark results were gathered using the `DotTiled.Benchmark` project which uses [BenchmarkDotNet](https://benchmarkdotnet.org/) to compare the performance of DotTiled with other similar libraries. The benchmark results are grouped by category and show the mean execution time, memory consumption metrics, and ratio to DotTiled. + +``` +BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update) +12th Gen Intel Core i7-12700K, 1 CPU, 20 logical and 12 physical cores +.NET SDK 8.0.202 + [Host] : .NET 8.0.3 (8.0.324.11423), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.3 (8.0.324.11423), X64 RyuJIT AVX2 +``` +| Method | Categories | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | +|------------ |------------------------- |----------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:| +| DotTiled | MapFromInMemoryTmxString | 2.991 μs | 0.0266 μs | 0.0236 μs | 1.00 | 0.00 | 1.2817 | 0.0610 | 16.37 KB | 1.00 | +| TiledLib | MapFromInMemoryTmxString | 5.405 μs | 0.0466 μs | 0.0413 μs | 1.81 | 0.02 | 1.8158 | 0.1068 | 23.32 KB | 1.42 | +| TiledCSPlus | MapFromInMemoryTmxString | 6.354 μs | 0.0703 μs | 0.0587 μs | 2.12 | 0.03 | 2.5940 | 0.1831 | 33.23 KB | 2.03 | +| | | | | | | | | | | | +| DotTiled | MapFromTmxFile | 28.570 μs | 0.1216 μs | 0.1137 μs | 1.00 | 0.00 | 1.0376 | - | 13.88 KB | 1.00 | +| TiledCSPlus | MapFromTmxFile | 33.377 μs | 0.1086 μs | 0.1016 μs | 1.17 | 0.01 | 2.8076 | 0.1221 | 36.93 KB | 2.66 | +| TiledLib | MapFromTmxFile | 36.077 μs | 0.1900 μs | 0.1777 μs | 1.26 | 0.01 | 2.0752 | 0.1221 | 27.1 KB | 1.95 |
@@ -57,55 +73,3 @@ DotTiled is available as a NuGet package. You can install it by using the NuGet ```pwsh dotnet add package DotTiled ``` - - -### Constructing a `TmxSerializer` - -There are few details to be aware of for your `TmxSerializer`: - -```csharp -// A map may or may not contain tilesets that are stored in external -// files. To deserialize a map, you must provide a way to resolve such -// tilesets. -Func tilesetResolver = - (TmxSerializer serializer, string path) => - { - string tilesetXml = fileSystem.ReadAllText(path); - return serializer.DeserializeTileset(tilesetXml); - }; - -// A map may or may not contain objects that reference template files. -// To deserialize a map, you must provide a way to resolve such templates. -Func templateResolver = - (TmxSerializer serializer, string path) => - { - string templateXml = fileSystem.ReadAllText(path); - return serializer.DeserializeTemplate(templateXml); - }; - -var tmxSerializer = new TmxSerializer(tilesetResolver, templateResolver); -``` - -### Loading a `.tmx` map - -The `TmxSerializer` has several overloads for `DeserializeMap` that allow you to load a map from a number of different sources. - -```csharp -string mapXml = fileSystem.ReadAllText("path/to/map.tmx"); -Map mapFromRaw = tmxSerializer.DeserializeMap(mapXml); // From raw XML string in memory - -using var reader = fileSystem.OpenXmlReader("path/to/map.tmx"); -Map mapFromReader = tmxSerializer.DeserializeMap(reader); // From XML reader -``` - -### Loading a `.tsx` tileset - -Similar to maps, the `TmxSerializer` has several overloads for `DeserializeTileset` that allow you to load a tileset from a number of different sources. - -```csharp -string tilesetXml = fileSystem.ReadAllText("path/to/tileset.tsx"); -Tileset tileset = tmxSerializer.DeserializeTileset(tilesetXml); // From raw XML string in memory - -using var reader = fileSystem.OpenXmlReader("path/to/tileset.tsx"); -Tileset tileset = tmxSerializer.DeserializeTileset(reader); // From XML reader -``` \ No newline at end of file From 2799fe1fd820f0b11be34cdb3f011be33cd951e7 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 3 Aug 2024 21:29:59 +0200 Subject: [PATCH 4/6] Prepare some feature coverage --- README.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1614421..d191c60 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,27 @@ DotTiled is a simple and easy-to-use library for loading, saving, and managing [ Other similar libraries exist, and you may want to consider them for your project as well: -| **Comparison** | **DotTiled** | [TiledLib](https://github.com/Ragath/TiledLib.Net) | [TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus) | [TiledSharp](https://github.com/marshallward/TiledSharp) | [TiledCS](https://github.com/TheBoneJarmer/TiledCS) | [TiledNet](https://github.com/napen123/Tiled.Net) | -|--------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| -| Actively maintained | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| Docs | Usage,
XML Docs | Usage | Usage, API,
XML Docs | Usage, API | Usage, XML Docs | Usage, XML Docs | -| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | -| Benchmark (time)* | 1.00 | 1.81 | 2.12 | - | - | - | -| Benchmark (memory)* | 1.00 | 1.42 | 2.03 | - | - | - | -| *Feature coverage
comparison* | | | | | | | -| `.tmx` (XML format) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **Comparison** | **DotTiled** | [TiledLib](https://github.com/Ragath/TiledLib.Net) | [TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus) | [TiledSharp](https://github.com/marshallward/TiledSharp) | [TiledCS](https://github.com/TheBoneJarmer/TiledCS) | [TiledNet](https://github.com/napen123/Tiled.Net) | +|---------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| +| Actively maintained | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| Docs | Usage,
XML Docs | Usage | Usage, API,
XML Docs | Usage, API | Usage, XML Docs | Usage, XML Docs | +| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | +| Benchmark (time)* | 1.00 | 1.81 | 2.12 | - | - | - | +| Benchmark (memory)* | 1.00 | 1.42 | 2.03 | - | - | - | +| *Feature coverage
comparison*|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| `.tmx` (Map XML format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| `.tsx` (Tileset XML format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| `.tx` (Template XML format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| `.tmj` (Map JSON format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| `.tsj` (Tileset JSON format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| `.tj` (Template JSON format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Load from string (implies file) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Load from file |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| External tilesets |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Template files |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Property custom types |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Hierarchical layers (groups) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Infinite maps |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| > [!NOTE] > *Both benchmark time and memory ratios are relative to DotTiled. Lower is better. Benchmark (time) refers to the execution time of loading the same map from an in-memory string that contains XML data in the `.tmx` format. Benchmark (memory) refers to the memory allocated during that loading process. For further details on the benchmark results, see the collapsible section below. From e190da7cdffe43a4a97b6ac1c36dd970af2278ef Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 3 Aug 2024 21:36:07 +0200 Subject: [PATCH 5/6] Update README --- README.md | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index d191c60..0d98501 100644 --- a/README.md +++ b/README.md @@ -4,37 +4,35 @@ DotTiled is a simple and easy-to-use library for loading, saving, and managing [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort. -- [Feature coverage](#feature-coverage) - [Alternative libraries and comparison + benchmarks](#alternative-libraries-and-comparison) - [Quickstart](#quickstart) - [Installing DotTiled](#installing-dottiled) - - [Constructing a `TmxSerializer`](#constructing-a-tmxserializer) - - [Loading a `.tmx` map](#loading-a-tmx-map) - - [Loading a `.tsx` tileset](#loading-a-tsx-tileset) - - [Constructing a `TmjSerializer`](#constructing-a-tmjserializer) - - [Loading a `.tmj` map](#loading-a-tmj-map) - - [Loading a `.tsj` tileset](#loading-a-tsj-tileset) - -# Feature coverage # Alternative libraries and comparison Other similar libraries exist, and you may want to consider them for your project as well: -| **Comparison** | **DotTiled** | [TiledLib](https://github.com/Ragath/TiledLib.Net) | [TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus) | [TiledSharp](https://github.com/marshallward/TiledSharp) | [TiledCS](https://github.com/TheBoneJarmer/TiledCS) | [TiledNet](https://github.com/napen123/Tiled.Net) | +|**Comparison**|**DotTiled**|[TiledLib](https://github.com/Ragath/TiledLib.Net)|[TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus)|[TiledSharp](https://github.com/marshallward/TiledSharp)|[TiledCS](https://github.com/TheBoneJarmer/TiledCS)|[TiledNet](https://github.com/napen123/Tiled.Net)| |---------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| | Actively maintained | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| Docs | Usage,
XML Docs | Usage | Usage, API,
XML Docs | Usage, API | Usage, XML Docs | Usage, XML Docs | | License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | | Benchmark (time)* | 1.00 | 1.81 | 2.12 | - | - | - | | Benchmark (memory)* | 1.00 | 1.42 | 2.03 | - | - | - | -| *Feature coverage
comparison*|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| `.tmx` (Map XML format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| `.tsx` (Tileset XML format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| `.tx` (Template XML format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| `.tmj` (Map JSON format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| `.tsj` (Tileset JSON format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| `.tj` (Template JSON format) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Docs |Usage,
XML Docs|Usage|Usage, API,
XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs| +| *Feature coverage
comparison below*|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| + +> [!NOTE] +> *Both benchmark time and memory ratios are relative to DotTiled. Lower is better. Benchmark (time) refers to the execution time of loading the same map from an in-memory string that contains XML data in the `.tmx` format. Benchmark (memory) refers to the memory allocated during that loading process. For further details on the benchmark results, see the collapsible section below. + +
+ +Feature coverage comparison + + +| **Comparison**|**DotTiled**|[TiledLib](https://github.com/Ragath/TiledLib.Net)|[TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus)|[TiledSharp](https://github.com/marshallward/TiledSharp)|[TiledCS](https://github.com/TheBoneJarmer/TiledCS)|[TiledNet](https://github.com/napen123/Tiled.Net)| +|---------------------------------|:-:|:-:|:-:|:-:|:-:|:-:| +| Full XML support `.tmx` |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| +| Full JSON support `.tmj` |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| | Load from string (implies file) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| | Load from file |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| | External tilesets |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| @@ -43,16 +41,13 @@ Other similar libraries exist, and you may want to consider them for your projec | Hierarchical layers (groups) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| | Infinite maps |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -> [!NOTE] -> *Both benchmark time and memory ratios are relative to DotTiled. Lower is better. Benchmark (time) refers to the execution time of loading the same map from an in-memory string that contains XML data in the `.tmx` format. Benchmark (memory) refers to the memory allocated during that loading process. For further details on the benchmark results, see the collapsible section below. +
Benchmark details -#### Benchmark results - The following benchmark results were gathered using the `DotTiled.Benchmark` project which uses [BenchmarkDotNet](https://benchmarkdotnet.org/) to compare the performance of DotTiled with other similar libraries. The benchmark results are grouped by category and show the mean execution time, memory consumption metrics, and ratio to DotTiled. ``` From 38f0687e6da183f2c28adb32a04125c9b27ff1c8 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 4 Aug 2024 16:57:47 +0200 Subject: [PATCH 6/6] Some more README updates --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d98501..2fec131 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ DotTiled is a simple and easy-to-use library for loading, saving, and managing [Tiled maps and tilesets](https://mapeditor.org) in your .NET projects. After [TiledCS](https://github.com/TheBoneJarmer/TiledCS) unfortunately became unmaintained (since 2022), I aimed to create a new library that could fill its shoes. DotTiled is the result of that effort. +DotTiled is designed to be a lightweight and efficient library that provides a simple API for loading and managing Tiled maps and tilesets. It is built with performance in mind and aims to be as fast and memory-efficient as possible. Targeting `netstandard2.0` and `net8.0` allows DotTiled to be used in popular game engines like Unity and Godot, as well as in popular game development frameworks like MonoGame. + - [Alternative libraries and comparison + benchmarks](#alternative-libraries-and-comparison) - [Quickstart](#quickstart) - [Installing DotTiled](#installing-dottiled) @@ -15,10 +17,11 @@ Other similar libraries exist, and you may want to consider them for your projec |**Comparison**|**DotTiled**|[TiledLib](https://github.com/Ragath/TiledLib.Net)|[TiledCSPlus](https://github.com/nolemretaWxd/TiledCSPlus)|[TiledSharp](https://github.com/marshallward/TiledSharp)|[TiledCS](https://github.com/TheBoneJarmer/TiledCS)|[TiledNet](https://github.com/napen123/Tiled.Net)| |---------------------------------|:-----------------------:|:--------:|:-----------:|:----------:|:-------:|:------:| | Actively maintained | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | | Benchmark (time)* | 1.00 | 1.81 | 2.12 | - | - | - | | Benchmark (memory)* | 1.00 | 1.42 | 2.03 | - | - | - | +| .NET Targets | `net8.0`
`netstandard2.0` |`net6.0`
`net7.0`|`netstandard2.1`|`netstandard2.0`|`netstandard2.0`|`net45`| | Docs |Usage,
XML Docs|Usage|Usage, API,
XML Docs|Usage, API|Usage, XML Docs|Usage, XML Docs| +| License | MIT | MIT | MIT | Apache-2.0 | MIT | BSD 3-Clause | | *Feature coverage
comparison below*|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| > [!NOTE]