From 30318e6194c69d5af64be03fb79cd321ceeb06e2 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 27 Jul 2024 23:53:04 +0200 Subject: [PATCH 01/39] Initial README update, rebase and amend this commit --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 81fe9e5..f5692b5 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# DotTiled \ No newline at end of file +# 📚 DotTiled + +DotTiled is a simple, lightweight, and easy-to-use library for loading, saving, and managing Tiled maps and tilesets 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. + +## Quickstart + +### Loading a map with external tilesets + +```csharp +using DotTiled; + +TmxSerializer tmxSerializer; + +// Tiled can store tilesets in external files or in a map itself +// When they are stored externally, you need to provide a way +// to resolve them given their source path +Func externalTilesetResolver = (string path) => +{ + // Load the tileset from however you want + var tilesetXml = fileSystem.ReadAllText(path); + var xmlStringReader = new StringReader(tilesetXml); + var xmlReader = XmlReader.Create(xmlStringReader); + var tmxSerializer = tmxSerializer.DeserializeTileset(xmlReader); +}; + +``` \ No newline at end of file From 6349471a583a0b83dbdd277144a6630397cf8985 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 27 Jul 2024 23:53:05 +0200 Subject: [PATCH 02/39] Further README updates --- .../TmxSerializer/TestData/TestData.cs | 10 ++++ .../TmxSerializer/TmxSerializer.MapTests.cs | 5 ++ DotTiled/TmxSerializer/TmxSerializer.cs | 8 ++++ README.md | 46 +++++++++++++------ 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/DotTiled.Tests/TmxSerializer/TestData/TestData.cs b/DotTiled.Tests/TmxSerializer/TestData/TestData.cs index e2e36c9..7dbc2ad 100644 --- a/DotTiled.Tests/TmxSerializer/TestData/TestData.cs +++ b/DotTiled.Tests/TmxSerializer/TestData/TestData.cs @@ -15,4 +15,14 @@ public static class TmxSerializerTestData var xmlStringReader = new StringReader(xml); return XmlReader.Create(xmlStringReader); } + + public static string GetRawStringFor(string testDataFile) + { + var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; + using var stream = typeof(TmxSerializerTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); + + using var stringReader = new StreamReader(stream); + return stringReader.ReadToEnd(); + } } diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs index ec3f0e8..ceeefe4 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs +++ b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs @@ -54,14 +54,19 @@ public partial class TmxSerializerMapTests { // Arrange using var reader = TmxSerializerTestData.GetReaderFor(testDataFile); + var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); Func externalTilesetResolver = (string s) => throw new NotSupportedException("External tilesets are not supported in this test"); var tmxSerializer = new TmxSerializer(externalTilesetResolver); // Act var map = tmxSerializer.DeserializeMap(reader); + var raw = tmxSerializer.DeserializeMap(testDataFileText); // Assert Assert.NotNull(map); AssertMap(map, expectedMap); + + Assert.NotNull(raw); + AssertMap(raw, expectedMap); } } diff --git a/DotTiled/TmxSerializer/TmxSerializer.cs b/DotTiled/TmxSerializer/TmxSerializer.cs index 9ced612..a2ae79e 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Xml; namespace DotTiled; @@ -17,4 +18,11 @@ public partial class TmxSerializer 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); + } } diff --git a/README.md b/README.md index f5692b5..9151f6f 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,46 @@ # 📚 DotTiled -DotTiled is a simple, lightweight, and easy-to-use library for loading, saving, and managing Tiled maps and tilesets 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 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. + +Other similar libraries exist, and you may want to consider them as well: +| Library | Active | Formats | Docs | License | Benchmark* Rank | +| --- | --- | --- | --- | --- | --- | +| **DotTiled** | ✅ | `.tmx`, `.tsx` | Usage, API, XML docs | MIT | 1 | +| [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) | ❌ | | | | | ## Quickstart -### Loading a map with external tilesets +Here are a few examples to get you started with DotTiled. + +### Loading a `.tmx` map ```csharp -using DotTiled; +var tmxSerializer = new TmxSerializer(); -TmxSerializer tmxSerializer; - -// Tiled can store tilesets in external files or in a map itself -// When they are stored externally, you need to provide a way -// to resolve them given their source path +// 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 externalTilesetResolver = (string path) => { - // Load the tileset from however you want - var tilesetXml = fileSystem.ReadAllText(path); - var xmlStringReader = new StringReader(tilesetXml); - var xmlReader = XmlReader.Create(xmlStringReader); - var tmxSerializer = tmxSerializer.DeserializeTileset(xmlReader); + string tilesetXml = fileSystem.ReadAllText(path); + return tmxSerializer.DeserializeTileset(tilesetXml); }; +string mapXml = fileSystem.ReadAllText("path/to/map.tmx"); +Map map = tmxSerializer.DeserializeMap(mapXml, externalTilesetResolver); +``` + +### Loading a `.tsx` tileset + +```csharp +var tmxSerializer = new TmxSerializer(); + +string tilesetXml = fileSystem.ReadAllText("path/to/tileset.tsx"); +Tileset tileset = tmxSerializer.DeserializeTileset(tilesetXml); ``` \ No newline at end of file From 5193ab5b61a464478571cbc1f7e0e00cfd3d7893 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 27 Jul 2024 23:53:05 +0200 Subject: [PATCH 03/39] Add Group to Model and to TmxSerializer --- DotTiled/Model/Layers/BaseLayer.cs | 2 - DotTiled/Model/Layers/Group.cs | 11 +++++ DotTiled/Model/Layers/ImageLayer.cs | 2 + DotTiled/Model/Layers/ObjectLayer.cs | 2 + DotTiled/Model/Layers/TileLayer.cs | 2 + DotTiled/TmxSerializer/TmxSerializer.Map.cs | 1 + .../TmxSerializer/TmxSerializer.TileLayer.cs | 43 +++++++++++++++++++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 DotTiled/Model/Layers/Group.cs diff --git a/DotTiled/Model/Layers/BaseLayer.cs b/DotTiled/Model/Layers/BaseLayer.cs index 950a6f1..0f10a95 100644 --- a/DotTiled/Model/Layers/BaseLayer.cs +++ b/DotTiled/Model/Layers/BaseLayer.cs @@ -8,8 +8,6 @@ public abstract class BaseLayer public required uint ID { get; set; } public string Name { get; set; } = ""; public string Class { get; set; } = ""; - public uint X { get; set; } = 0; - public uint Y { get; set; } = 0; public float Opacity { get; set; } = 1.0f; public bool Visible { get; set; } = true; public Color? TintColor { get; set; } diff --git a/DotTiled/Model/Layers/Group.cs b/DotTiled/Model/Layers/Group.cs new file mode 100644 index 0000000..f58a83c --- /dev/null +++ b/DotTiled/Model/Layers/Group.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace DotTiled; + +public class Group : BaseLayer +{ + // Uses same attributes as BaseLayer + + // Any number of + public List Layers { get; set; } = []; +} diff --git a/DotTiled/Model/Layers/ImageLayer.cs b/DotTiled/Model/Layers/ImageLayer.cs index 7d0b141..6489c22 100644 --- a/DotTiled/Model/Layers/ImageLayer.cs +++ b/DotTiled/Model/Layers/ImageLayer.cs @@ -3,6 +3,8 @@ namespace DotTiled; public class ImageLayer : BaseLayer { // Attributes + public uint X { get; set; } = 0; + public uint Y { get; set; } = 0; public required bool RepeatX { get; set; } public required bool RepeatY { get; set; } diff --git a/DotTiled/Model/Layers/ObjectLayer.cs b/DotTiled/Model/Layers/ObjectLayer.cs index acf96a5..253b616 100644 --- a/DotTiled/Model/Layers/ObjectLayer.cs +++ b/DotTiled/Model/Layers/ObjectLayer.cs @@ -11,6 +11,8 @@ public enum DrawOrder public class ObjectLayer : BaseLayer { // Attributes + public uint X { get; set; } = 0; + public uint Y { get; set; } = 0; public uint? Width { get; set; } public uint? Height { get; set; } public required Color? Color { get; set; } diff --git a/DotTiled/Model/Layers/TileLayer.cs b/DotTiled/Model/Layers/TileLayer.cs index de4b25d..7692266 100644 --- a/DotTiled/Model/Layers/TileLayer.cs +++ b/DotTiled/Model/Layers/TileLayer.cs @@ -3,6 +3,8 @@ namespace DotTiled; public class TileLayer : BaseLayer { // Attributes + public uint X { get; set; } = 0; + public uint Y { get; set; } = 0; public required uint Width { get; set; } public required uint Height { get; set; } diff --git a/DotTiled/TmxSerializer/TmxSerializer.Map.cs b/DotTiled/TmxSerializer/TmxSerializer.Map.cs index bf3432b..799b217 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.Map.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.Map.cs @@ -69,6 +69,7 @@ public partial class TmxSerializer "layer" => () => layers.Add(ReadTileLayer(r, dataUsesChunks: infinite)), "objectgroup" => () => layers.Add(ReadObjectLayer(r)), "imagelayer" => () => layers.Add(ReadImageLayer(r)), + "group" => () => layers.Add(ReadGroup(r)), _ => r.Skip }); diff --git a/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs b/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs index b6853c8..bd23d1f 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.TileLayer.cs @@ -102,4 +102,47 @@ public partial class TmxSerializer 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 + }; + } } From 0f6db5254d9cf994ba5189002a28f7d96829cf0f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 27 Jul 2024 23:53:05 +0200 Subject: [PATCH 04/39] Add object Templates to Model and TmxSerializer --- DotTiled.Tests/DotTiled.Tests.csproj | 2 +- .../TestData/Map/map-with-group.cs | 94 +++++++++++ .../TestData/Map/map-with-group.tmx | 26 +++ .../TestData/Map/map-with-object-template.cs | 125 ++++++++++++++ .../TestData/Map/map-with-object-template.tmx | 35 ++++ .../Template/map-with-object-template.tx | 14 ++ .../TmxSerializer/TmxSerializer.LayerTests.cs | 17 +- .../TmxSerializer/TmxSerializer.MapTests.cs | 159 +++++++++++++++++- .../TmxSerializer/TmxSerializerTests.cs | 10 +- DotTiled/Model/Layers/ObjectLayer.cs | 4 +- DotTiled/Model/Layers/Objects/Object.cs | 2 +- DotTiled/Model/Template.cs | 8 + .../TmxSerializer/TmxSerializer.Helpers.cs | 9 + .../TmxSerializer.ObjectLayer.cs | 147 +++++++++++++--- .../TmxSerializer/TmxSerializer.Tileset.cs | 2 +- DotTiled/TmxSerializer/TmxSerializer.cs | 21 ++- 16 files changed, 631 insertions(+), 44 deletions(-) create mode 100644 DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.cs create mode 100644 DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.tmx create mode 100644 DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs create mode 100644 DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.tmx create mode 100644 DotTiled.Tests/TmxSerializer/TestData/Template/map-with-object-template.tx create mode 100644 DotTiled/Model/Template.cs diff --git a/DotTiled.Tests/DotTiled.Tests.csproj b/DotTiled.Tests/DotTiled.Tests.csproj index c110013..44236af 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/map-with-group.cs b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.cs new file mode 100644 index 0000000..0bac69e --- /dev/null +++ b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.cs @@ -0,0 +1,94 @@ +namespace DotTiled.Tests; + +public partial class TmxSerializerMapTests +{ + private static Map MapWithGroup() => new Map + { + Version = "1.10", + TiledVersion = "1.11.0", + Orientation = MapOrientation.Orthogonal, + RenderOrder = RenderOrder.RightDown, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + NextLayerID = 5, + NextObjectID = 2, + Layers = [ + new TileLayer + { + ID = 4, + Name = "Tile Layer 2", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = [ + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + }, + new Group + { + ID = 3, + Name = "Group 1", + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = [ + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + }, + new ObjectLayer + { + ID = 2, + Name = "Object Layer 1", + Objects = [ + new RectangleObject + { + ID = 1, + Name = "Name", + X = 35.5f, + Y = 26, + Width = 64.5f, + Height = 64.5f, + } + ] + } + ] + } + ] + }; +} diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.tmx b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.tmx new file mode 100644 index 0000000..61191c4 --- /dev/null +++ b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-group.tmx @@ -0,0 +1,26 @@ + + + + +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + + + +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + + + + + diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs new file mode 100644 index 0000000..31a658e --- /dev/null +++ b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.cs @@ -0,0 +1,125 @@ +namespace DotTiled.Tests; + +public partial class TmxSerializerMapTests +{ + private static Map MapWithObjectTemplate() => new Map + { + Version = "1.10", + TiledVersion = "1.11.0", + Orientation = MapOrientation.Orthogonal, + RenderOrder = RenderOrder.RightDown, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + NextLayerID = 3, + NextObjectID = 3, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + GlobalTileIDs = [ + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0, + 0,0,0,0,0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + }, + new ObjectLayer + { + ID = 2, + Name = "Object Layer 1", + Objects = [ + new RectangleObject + { + ID = 1, + Template = "map-with-object-template.tx", + Name = "Thingy 2", + X = 94.5749f, + Y = 33.6842f, + Width = 37.0156f, + Height = 37.0156f, + Properties = new Dictionary + { + ["Bool"] = new BoolProperty { Name = "Bool", Value = true }, + ["TestClassInTemplate"] = new ClassProperty + { + Name = "TestClassInTemplate", + PropertyType = "TestClass", + Properties = new Dictionary + { + ["Amount"] = new FloatProperty { Name = "Amount", Value = 37 }, + ["Name"] = new StringProperty { Name = "Name", Value = "I am here" } + } + } + } + }, + new RectangleObject + { + ID = 2, + Template = "map-with-object-template.tx", + Name = "Thingy", + X = 29.7976f, + Y = 33.8693f, + Width = 37.0156f, + Height = 37.0156f, + Properties = new Dictionary + { + ["Bool"] = new BoolProperty { Name = "Bool", Value = true }, + ["TestClassInTemplate"] = new ClassProperty + { + Name = "TestClassInTemplate", + PropertyType = "TestClass", + Properties = new Dictionary + { + ["Amount"] = new FloatProperty { Name = "Amount", Value = 4.2f }, + ["Name"] = new StringProperty { Name = "Name", Value = "Hello there" } + } + } + } + }, + new RectangleObject + { + ID = 3, + Template = "map-with-object-template.tx", + Name = "Thingy 3", + X = 5, + Y = 5, + Width = 37.0156f, + Height = 37.0156f, + Properties = new Dictionary + { + ["Bool"] = new BoolProperty { Name = "Bool", Value = true }, + ["TestClassInTemplate"] = new ClassProperty + { + Name = "TestClassInTemplate", + PropertyType = "TestClass", + Properties = new Dictionary + { + ["Amount"] = new FloatProperty { Name = "Amount", Value = 4.2f }, + ["Name"] = new StringProperty { Name = "Name", Value = "I am here 3" } + } + } + } + } + ] + } + ] + }; +} diff --git a/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.tmx b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.tmx new file mode 100644 index 0000000..83716a0 --- /dev/null +++ b/DotTiled.Tests/TmxSerializer/TestData/Map/map-with-object-template.tmx @@ -0,0 +1,35 @@ + + + + +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DotTiled.Tests/TmxSerializer/TestData/Template/map-with-object-template.tx b/DotTiled.Tests/TmxSerializer/TestData/Template/map-with-object-template.tx new file mode 100644 index 0000000..3039be2 --- /dev/null +++ b/DotTiled.Tests/TmxSerializer/TestData/Template/map-with-object-template.tx @@ -0,0 +1,14 @@ + + diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs b/DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs index 5e36b95..c3289a4 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs +++ b/DotTiled.Tests/TmxSerializer/TmxSerializer.LayerTests.cs @@ -15,8 +15,6 @@ public partial class TmxSerializerLayerTests Assert.Equal(expected.ID, actual.ID); Assert.Equal(expected.Name, actual.Name); Assert.Equal(expected.Class, actual.Class); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); Assert.Equal(expected.Opacity, actual.Opacity); Assert.Equal(expected.Visible, actual.Visible); Assert.Equal(expected.TintColor, actual.TintColor); @@ -34,6 +32,8 @@ public partial class TmxSerializerLayerTests // Attributes Assert.Equal(expected.Width, actual.Width); Assert.Equal(expected.Height, actual.Height); + Assert.Equal(expected.X, actual.X); + Assert.Equal(expected.Y, actual.Y); Assert.NotNull(actual.Data); TmxSerializerDataTests.AssertData(actual.Data, expected.Data); @@ -43,6 +43,8 @@ public partial class TmxSerializerLayerTests { // Attributes Assert.Equal(expected.DrawOrder, actual.DrawOrder); + Assert.Equal(expected.X, actual.X); + Assert.Equal(expected.Y, actual.Y); Assert.NotNull(actual.Objects); Assert.Equal(expected.Objects.Count, actual.Objects.Count); @@ -55,8 +57,19 @@ public partial class TmxSerializerLayerTests // Attributes Assert.Equal(expected.RepeatX, actual.RepeatX); Assert.Equal(expected.RepeatY, actual.RepeatY); + Assert.Equal(expected.X, actual.X); + Assert.Equal(expected.Y, actual.Y); Assert.NotNull(actual.Image); TmxSerializerImageTests.AssertImage(actual.Image, expected.Image); } + + private static void AssertLayer(Group actual, Group expected) + { + // 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]); + } } diff --git a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs index ceeefe4..7d513e1 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs +++ b/DotTiled.Tests/TmxSerializer/TmxSerializer.MapTests.cs @@ -50,13 +50,18 @@ public partial class TmxSerializerMapTests [Theory] [MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] - public void DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) + public void DeserializeMapFromXmlReader_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange using var reader = TmxSerializerTestData.GetReaderFor(testDataFile); var testDataFileText = TmxSerializerTestData.GetRawStringFor(testDataFile); - Func externalTilesetResolver = (string s) => throw new NotSupportedException("External tilesets are not supported in this test"); - var tmxSerializer = new TmxSerializer(externalTilesetResolver); + 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); @@ -68,5 +73,153 @@ public partial class TmxSerializerMapTests 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 index 3d1730e..0c27cf1 100644 --- a/DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs +++ b/DotTiled.Tests/TmxSerializer/TmxSerializerTests.cs @@ -6,10 +6,11 @@ public class TmxSerializerTests public void TmxSerializerConstructor_ExternalTilesetResolverIsNull_ThrowsArgumentNullException() { // Arrange - Func externalTilesetResolver = null!; + Func externalTilesetResolver = null!; + Func externalTemplateResolver = null!; // Act - Action act = () => _ = new TmxSerializer(externalTilesetResolver); + Action act = () => _ = new TmxSerializer(externalTilesetResolver, externalTemplateResolver); // Assert Assert.Throws(act); @@ -19,10 +20,11 @@ public class TmxSerializerTests public void TmxSerializerConstructor_ExternalTilesetResolverIsNotNull_DoesNotThrow() { // Arrange - Func externalTilesetResolver = _ => new Tileset(); + Func externalTilesetResolver = (_, _) => new Tileset(); + Func externalTemplateResolver = (_, _) => new Template { Object = new RectangleObject { } }; // Act - var tmxSerializer = new TmxSerializer(externalTilesetResolver); + var tmxSerializer = new TmxSerializer(externalTilesetResolver, externalTemplateResolver); // Assert Assert.NotNull(tmxSerializer); diff --git a/DotTiled/Model/Layers/ObjectLayer.cs b/DotTiled/Model/Layers/ObjectLayer.cs index 253b616..ca3be60 100644 --- a/DotTiled/Model/Layers/ObjectLayer.cs +++ b/DotTiled/Model/Layers/ObjectLayer.cs @@ -15,8 +15,8 @@ public class ObjectLayer : BaseLayer public uint Y { get; set; } = 0; public uint? Width { get; set; } public uint? Height { get; set; } - public required Color? Color { get; set; } - public required DrawOrder DrawOrder { get; set; } = DrawOrder.TopDown; + public Color? Color { get; set; } + public DrawOrder DrawOrder { get; set; } = DrawOrder.TopDown; // Elements public required List Objects { get; set; } diff --git a/DotTiled/Model/Layers/Objects/Object.cs b/DotTiled/Model/Layers/Objects/Object.cs index 2684188..b3313d7 100644 --- a/DotTiled/Model/Layers/Objects/Object.cs +++ b/DotTiled/Model/Layers/Objects/Object.cs @@ -5,7 +5,7 @@ namespace DotTiled; public abstract class Object { // Attributes - public required uint ID { get; set; } + public uint? ID { get; set; } public string Name { get; set; } = ""; public string Type { get; set; } = ""; public float X { get; set; } = 0f; diff --git a/DotTiled/Model/Template.cs b/DotTiled/Model/Template.cs new file mode 100644 index 0000000..11ae128 --- /dev/null +++ b/DotTiled/Model/Template.cs @@ -0,0 +1,8 @@ +namespace DotTiled; + +public class Template +{ + // At most one of (if the template is a tile object) + public Tileset? Tileset { get; set; } + public required Object Object { get; set; } +} diff --git a/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs b/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs index 9676f4b..14fcfaa 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.Helpers.cs @@ -13,5 +13,14 @@ public partial class TmxSerializer 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.ObjectLayer.cs b/DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs index 8772c04..df97300 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.ObjectLayer.cs @@ -71,30 +71,63 @@ public partial class TmxSerializer private Object ReadObject(XmlReader reader) { // Attributes - var id = reader.GetRequiredAttributeParseable("id"); - var name = reader.GetOptionalAttribute("name") ?? ""; - var type = reader.GetOptionalAttribute("type") ?? ""; - var x = reader.GetOptionalAttributeParseable("x") ?? 0f; - var y = reader.GetOptionalAttributeParseable("y") ?? 0f; - var width = reader.GetOptionalAttributeParseable("width") ?? 0f; - var height = reader.GetOptionalAttributeParseable("height") ?? 0f; - var rotation = reader.GetOptionalAttributeParseable("rotation") ?? 0f; - var gid = reader.GetOptionalAttributeParseable("gid"); - var visible = reader.GetOptionalAttributeParseable("visible") ?? true; 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; - Dictionary? properties = null; + int propertiesCounter = 0; + Dictionary? properties = propertiesDefault; reader.ProcessChildren("object", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "ellipse" => () => Helpers.SetAtMostOnce(ref obj, ReadEllipseObject(r, id), "Object marker"), - "point" => () => Helpers.SetAtMostOnce(ref obj, ReadPointObject(r, id), "Object marker"), - "polygon" => () => Helpers.SetAtMostOnce(ref obj, ReadPolygonObject(r, id), "Object marker"), - "polyline" => () => Helpers.SetAtMostOnce(ref obj, ReadPolylineObject(r, id), "Object marker"), - "text" => () => Helpers.SetAtMostOnce(ref obj, ReadTextObject(r, id), "Object marker"), + "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}'") }); @@ -119,19 +152,51 @@ public partial class TmxSerializer return obj; } - private EllipseObject ReadEllipseObject(XmlReader reader, uint id) + private Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) { - reader.Skip(); - return new EllipseObject { ID = id }; + 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 PointObject ReadPointObject(XmlReader reader, uint id) + private EllipseObject ReadEllipseObject(XmlReader reader) { reader.Skip(); - return new PointObject { ID = id }; + return new EllipseObject { }; } - private PolygonObject ReadPolygonObject(XmlReader reader, uint id) + private PointObject ReadPointObject(XmlReader reader) + { + reader.Skip(); + return new PointObject { }; + } + + private PolygonObject ReadPolygonObject(XmlReader reader) { // Attributes var points = reader.GetRequiredAttributeParseable>("points", s => @@ -146,10 +211,10 @@ public partial class TmxSerializer }); reader.ReadStartElement("polygon"); - return new PolygonObject { ID = id, Points = points }; + return new PolygonObject { Points = points }; } - private PolylineObject ReadPolylineObject(XmlReader reader, uint id) + private PolylineObject ReadPolylineObject(XmlReader reader) { // Attributes var points = reader.GetRequiredAttributeParseable>("points", s => @@ -164,10 +229,10 @@ public partial class TmxSerializer }); reader.ReadStartElement("polyline"); - return new PolylineObject { ID = id, Points = points }; + return new PolylineObject { Points = points }; } - private TextObject ReadTextObject(XmlReader reader, uint id) + private TextObject ReadTextObject(XmlReader reader) { // Attributes var fontFamily = reader.GetOptionalAttribute("fontfamily") ?? "sans-serif"; @@ -200,7 +265,6 @@ public partial class TmxSerializer return new TextObject { - ID = id, FontFamily = fontFamily, PixelSize = pixelSize, Wrap = wrap, @@ -215,4 +279,31 @@ public partial class TmxSerializer 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.Tileset.cs b/DotTiled/TmxSerializer/TmxSerializer.Tileset.cs index 314b9e9..c16e037 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.Tileset.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.Tileset.cs @@ -73,7 +73,7 @@ public partial class TmxSerializer // Check if tileset is referring to external file if (source is not null) { - var resolvedTileset = _externalTilesetResolver(source); + var resolvedTileset = _externalTilesetResolver(this, source); resolvedTileset.FirstGID = firstGID; resolvedTileset.Source = null; return resolvedTileset; diff --git a/DotTiled/TmxSerializer/TmxSerializer.cs b/DotTiled/TmxSerializer/TmxSerializer.cs index a2ae79e..5f5c604 100644 --- a/DotTiled/TmxSerializer/TmxSerializer.cs +++ b/DotTiled/TmxSerializer/TmxSerializer.cs @@ -6,11 +6,16 @@ namespace DotTiled; public partial class TmxSerializer { - private readonly Func _externalTilesetResolver; + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; - public TmxSerializer(Func externalTilesetResolver) + 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) @@ -25,4 +30,16 @@ public partial class TmxSerializer 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 5adf9c8b6b6f8f16883fbf75d3ac635329fa7f1a Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 27 Jul 2024 23:53:05 +0200 Subject: [PATCH 05/39] Update README with more examples --- README.md | 82 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 9151f6f..0667896 100644 --- a/README.md +++ b/README.md @@ -4,43 +4,83 @@ 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. -Other similar libraries exist, and you may want to consider them as well: -| Library | Active | Formats | Docs | License | Benchmark* Rank | -| --- | --- | --- | --- | --- | --- | -| **DotTiled** | ✅ | `.tmx`, `.tsx` | Usage, API, XML docs | MIT | 1 | +Other similar libraries exist, and you may want to consider them for your project as well: +| Library | Active | Formats | Feature coverage | Docs | License | Benchmark Rank* | +| --- | --- | --- | --- | --- | --- | --- | +| **DotTiled** | ✅ | `.tmx` `.tsx`
`.tmj` `.tsj` | | 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) | ❌ | | | | | +| [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) | ❌ | | | | | | + +> [!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. + +
+ +Comparison and benchmark details + + +**TODO: Add table displaying feature availability** + +**TODO: Add table displaying benchmark results** + +
+ +[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline. However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. ## Quickstart -Here are a few examples to get you started with DotTiled. +Here are a few short examples to get you started with DotTiled. -### Loading a `.tmx` map +### Constructing your `TmxSerializer` + +There are few details to be aware of for your `TmxSerializer`: ```csharp -var tmxSerializer = new TmxSerializer(); - // 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 externalTilesetResolver = (string path) => -{ - string tilesetXml = fileSystem.ReadAllText(path); - return tmxSerializer.DeserializeTileset(tilesetXml); -}; +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 map = tmxSerializer.DeserializeMap(mapXml, externalTilesetResolver); +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 -```csharp -var tmxSerializer = new TmxSerializer(); +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); +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 9d614fbab5659ca2d18372872d6bdbd7ac74509e Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 27 Jul 2024 23:55:56 +0200 Subject: [PATCH 06/39] Update README with more examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0667896..db3fc9d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Other similar libraries exist, and you may want to consider them for your projec | Library | Active | Formats | Feature coverage | Docs | License | Benchmark Rank* | | --- | --- | --- | --- | --- | --- | --- | | **DotTiled** | ✅ | `.tmx` `.tsx`
`.tmj` `.tsj` | | Usage, API, XML docs | | | -| [TiledLib](https://github.com/Ragath/TiledLib.Net) | ✅ | | | | | +| [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) | ❌ | | | | | | From 765226c224fc09d95d1b27561630f05f967f4882 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 28 Jul 2024 00:06:47 +0200 Subject: [PATCH 07/39] Further expanded on README content --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db3fc9d..07359ad 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,24 @@ 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](#alternative-libraries-and-comparison) +- [Quickstart](#quickstart) + - [Constructing your `TmxSerializer`](#constructing-your-tmxserializer) + - [Loading a `.tmx` map](#loading-a-tmx-map) + - [Loading a `.tsx` tileset](#loading-a-tsx-tileset) + +# 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 | Active | Formats | Feature coverage | Docs | License | Benchmark Rank* | | --- | --- | --- | --- | --- | --- | --- | -| **DotTiled** | ✅ | `.tmx` `.tsx`
`.tmj` `.tsj` | | Usage, API, XML docs | | | +| **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) | ❌ | | | | | | @@ -30,7 +44,7 @@ Comparison and benchmark details [MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline. However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. -## Quickstart +# Quickstart Here are a few short examples to get you started with DotTiled. From 94e6a12badc17c016745f4bac098bd1eada6185d Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 28 Jul 2024 17:20:43 +0200 Subject: [PATCH 08/39] Further README work --- README.md | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 07359ad..e0a1074 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,15 @@ 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](#alternative-libraries-and-comparison) +- [Alternative libraries and comparison + benchmarks](#alternative-libraries-and-comparison) - [Quickstart](#quickstart) - - [Constructing your `TmxSerializer`](#constructing-your-tmxserializer) + - [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 @@ -19,14 +23,14 @@ 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: -| Library | Active | Formats | Feature coverage | 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) | ❌ | | | | | | +| 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) |❌| | | | | | | > [!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. @@ -42,13 +46,20 @@ Comparison and benchmark details -[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline. However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. +[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline (with the DotTiled.MonoGame.Pipeline NuGet). However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. # Quickstart -Here are a few short examples to get you started with DotTiled. +### Installing DotTiled -### Constructing your `TmxSerializer` +DotTiled is available as a NuGet package. You can install it by using the NuGet Package Manager UI in Visual Studio, or equivalent, or using the following command for the .NET CLI: + +```pwsh +dotnet add package DotTiled +``` + + +### Constructing a `TmxSerializer` There are few details to be aware of for your `TmxSerializer`: From 7e325ea95d52c2ba70ce61fda95099b8ee410ec5 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 28 Jul 2024 23:50:16 +0200 Subject: [PATCH 09/39] Add initial benchmarking project --- DotTiled.Benchmark/DotTiled.Benchmark.csproj | 19 +++++++++++ DotTiled.Benchmark/Program.cs | 34 ++++++++++++++++++++ DotTiled.sln | 6 ++++ 3 files changed, 59 insertions(+) create mode 100644 DotTiled.Benchmark/DotTiled.Benchmark.csproj create mode 100644 DotTiled.Benchmark/Program.cs diff --git a/DotTiled.Benchmark/DotTiled.Benchmark.csproj b/DotTiled.Benchmark/DotTiled.Benchmark.csproj new file mode 100644 index 0000000..3fc12d3 --- /dev/null +++ b/DotTiled.Benchmark/DotTiled.Benchmark.csproj @@ -0,0 +1,19 @@ +ïŧŋ + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/DotTiled.Benchmark/Program.cs b/DotTiled.Benchmark/Program.cs new file mode 100644 index 0000000..c44b79b --- /dev/null +++ b/DotTiled.Benchmark/Program.cs @@ -0,0 +1,34 @@ +ïŧŋusing System; +using System.Security.Cryptography; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; + +namespace MyBenchmarks +{ + public class MapLoader + { + public MapLoader() + { + } + + [Benchmark] + public DotTiled.Map LoadWithDotTiled() + { + throw new NotImplementedException(); + } + + [Benchmark] + public TiledLib.Map LoadWithTiledLib() + { + throw new NotImplementedException(); + } + } + + public class Program + { + public static void Main(string[] args) + { + //var summary = BenchmarkRunner.Run(); + } + } +} diff --git a/DotTiled.sln b/DotTiled.sln index 86b44df..421e996 100644 --- a/DotTiled.sln +++ b/DotTiled.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled", "DotTiled\DotTil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Tests", "DotTiled.Tests\DotTiled.Tests.csproj", "{C1311A5A-5206-467C-B323-B131CA11FDB8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Benchmark", "DotTiled.Benchmark\DotTiled.Benchmark.csproj", "{510F3077-8EA4-47D1-8D01-E2D538F1B899}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {C1311A5A-5206-467C-B323-B131CA11FDB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C1311A5A-5206-467C-B323-B131CA11FDB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1311A5A-5206-467C-B323-B131CA11FDB8}.Release|Any CPU.Build.0 = Release|Any CPU + {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Debug|Any CPU.Build.0 = Debug|Any CPU + {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.ActiveCfg = Release|Any CPU + {510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 453200bbb2f501ebd6b9102c02ae960aa2fa2b0f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 3 Aug 2024 13:51:38 +0200 Subject: [PATCH 10/39] 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 11/39] 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 12/39] 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 13/39] 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 14/39] 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 15/39] 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] From 0f05bd10aaa3620912dbe62e0d21db133036bbc0 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Tue, 6 Aug 2024 22:39:50 +0200 Subject: [PATCH 16/39] Start json reader --- .../Serialization/Tmj/TmjMapReaderTests.cs | 32 ++++ .../Tmj/ExtensionsUtf8JsonReader.cs | 158 ++++++++++++++++++ DotTiled/Serialization/Tmj/Tmj.Map.cs | 102 +++++++++++ DotTiled/Serialization/Tmj/TmjMapReader.cs | 60 +++++++ 4 files changed, 352 insertions(+) create mode 100644 DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs create mode 100644 DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Map.cs create mode 100644 DotTiled/Serialization/Tmj/TmjMapReader.cs diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs new file mode 100644 index 0000000..2462fcd --- /dev/null +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -0,0 +1,32 @@ +namespace DotTiled.Tests; + +public partial class TmjMapReaderTests +{ + [Fact] + public void Test1() + { + // Arrange + var jsonString = + """ + { + "backgroundcolor":"#656667", + "height":4, + "nextobjectid":1, + "nextlayerid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tileheight":32, + "tilewidth":32, + "version":"1", + "tiledversion":"1.0.3", + "width":4 + } + """; + + // Act + using var tmjMapReader = new TmjMapReader(jsonString); + + // Assert + var map = tmjMapReader.ReadMap(); + } +} diff --git a/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs b/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs new file mode 100644 index 0000000..a0cd7a6 --- /dev/null +++ b/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal abstract class JsonProperty(string propertyName) + { + internal string PropertyName { get; } = propertyName; + } + + internal class RequiredProperty(string propertyName, Action withValue) : JsonProperty(propertyName) + { + internal Action WithValue { get; } = withValue; + } + + internal class OptionalProperty(string propertyName, Action withValue, bool allowNull = false) : JsonProperty(propertyName) + { + internal Action WithValue { get; } = withValue; + internal bool AllowNull { get; } = allowNull; + } +} + +internal static class ExtensionsUtf8JsonReader +{ + private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) + { + while (toCheck != typeof(object)) + { + var cur = toCheck!.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; + if (generic == cur) + return true; + + toCheck = toCheck.BaseType!; + } + + return false; + } + + internal static void Require(this ref Utf8JsonReader reader, ProcessProperty process) + { + if (reader.TokenType == JsonTokenType.Null) + throw new JsonException("Value is required."); + + process(ref reader); + } + + internal static void MoveToContent(this ref Utf8JsonReader reader) + { + while (reader.Read() && reader.TokenType == JsonTokenType.Comment || + reader.TokenType == JsonTokenType.None) + ; + } + + internal delegate void ProcessProperty(ref Utf8JsonReader reader); + + internal static void ProcessJsonObject(this Utf8JsonReader reader, (string PropertyName, ProcessProperty Processor)[] processors) + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Expected start of object."); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + return; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Expected property name."); + + var propertyName = reader.GetString(); + reader.Read(); + + if (!processors.Any(x => x.PropertyName == propertyName)) + { + reader.Skip(); + continue; + } + + var processor = processors.First(x => x.PropertyName == propertyName).Processor; + processor(ref reader); + } + + throw new JsonException("Expected end of object."); + } + + delegate T UseReader(ref Utf8JsonReader reader); + + internal static void ProcessJsonObject(this Utf8JsonReader reader, Tmj.JsonProperty[] properties) + { + List processedProperties = []; + + bool CheckType(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader useReader) + { + return CheckRequire(ref reader, prop, (ref Utf8JsonReader r) => useReader(ref r)!) || CheckOptional(ref reader, prop, useReader); + } + + bool CheckRequire(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader useReader) + { + if (prop is Tmj.RequiredProperty requiredProp) + { + reader.Require((ref Utf8JsonReader r) => + { + requiredProp.WithValue(useReader(ref r)); + }); + return true; + } + return false; + } + + bool CheckOptional(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader useReader) + { + if (prop is Tmj.OptionalProperty optionalProp) + { + if (reader.TokenType == JsonTokenType.Null && !optionalProp.AllowNull) + throw new JsonException("Value cannot be null for optional property."); + else if (reader.TokenType == JsonTokenType.Null && optionalProp.AllowNull) + optionalProp.WithValue(default); + else + optionalProp.WithValue(useReader(ref reader)); + return true; + } + return false; + } + + ProcessJsonObject(reader, properties.Select(x => (x.PropertyName.ToLowerInvariant(), (ref Utf8JsonReader reader) => + { + var lowerInvariant = x.PropertyName.ToLowerInvariant(); + + if (processedProperties.Contains(lowerInvariant)) + throw new JsonException($"Property '{lowerInvariant}' was already processed."); + + processedProperties.Add(lowerInvariant); + + if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetString()!)) + return; + if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetInt32())) + return; + if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetUInt32())) + return; + if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetSingle())) + return; + + throw new NotSupportedException($"Unsupported property type '{x.GetType().GenericTypeArguments.First()}'."); + } + )).ToArray()); + + foreach (var property in properties) + { + if (IsSubclassOfRawGeneric(typeof(Tmj.RequiredProperty<>), property.GetType()) && !processedProperties.Contains(property.PropertyName.ToLowerInvariant())) + throw new JsonException($"Required property '{property.PropertyName}' was not found."); + } + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Map.cs b/DotTiled/Serialization/Tmj/Tmj.Map.cs new file mode 100644 index 0000000..ce3ab2a --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -0,0 +1,102 @@ +using System.Globalization; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Map ReadMap(ref Utf8JsonReader reader) + { + string version = default!; + string tiledVersion = default!; + string @class = ""; + MapOrientation orientation = default; + RenderOrder renderOrder = RenderOrder.RightDown; + int compressionLevel = -1; + uint width = 0; + uint height = 0; + uint tileWidth = 0; + uint tileHeight = 0; + uint? hexSideLength = null; + StaggerAxis? staggerAxis = null; + StaggerIndex? staggerIndex = null; + float parallaxOriginX = 0.0f; + float parallaxOriginY = 0.0f; + Color backgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture); + uint nextLayerID = 0; + uint nextObjectID = 0; + bool infinite = false; + + reader.ProcessJsonObject([ + new RequiredProperty("version", value => version = value), + new RequiredProperty("tiledVersion", value => tiledVersion = value), + new OptionalProperty("class", value => @class = value ?? ""), + new RequiredProperty("orientation", value => orientation = value switch + { + "orthogonal" => MapOrientation.Orthogonal, + "isometric" => MapOrientation.Isometric, + "staggered" => MapOrientation.Staggered, + "hexagonal" => MapOrientation.Hexagonal, + _ => throw new JsonException("Invalid orientation.") + }), + new OptionalProperty("renderOrder", value => renderOrder = value switch + { + "right-down" => RenderOrder.RightDown, + "right-up" => RenderOrder.RightUp, + "left-down" => RenderOrder.LeftDown, + "left-up" => RenderOrder.LeftUp, + _ => throw new JsonException("Invalid render order.") + }), + new OptionalProperty("compressionLevel", value => compressionLevel = value), + new RequiredProperty("width", value => width = value), + new RequiredProperty("height", value => height = value), + new RequiredProperty("tileWidth", value => tileWidth = value), + new RequiredProperty("tileHeight", value => tileHeight = value), + new OptionalProperty("hexSideLength", value => hexSideLength = value), + new OptionalProperty("staggerAxis", value => staggerAxis = value switch + { + "x" => StaggerAxis.X, + "y" => StaggerAxis.Y, + _ => throw new JsonException("Invalid stagger axis.") + }), + new OptionalProperty("staggerIndex", value => staggerIndex = value switch + { + "odd" => StaggerIndex.Odd, + "even" => StaggerIndex.Even, + _ => throw new JsonException("Invalid stagger index.") + }), + new OptionalProperty("parallaxOriginX", value => parallaxOriginX = value), + new OptionalProperty("parallaxOriginY", value => parallaxOriginY = value), + new OptionalProperty("backgroundColor", value => backgroundColor = Color.Parse(value!, CultureInfo.InvariantCulture)), + new RequiredProperty("nextLayerID", value => nextLayerID = value), + new RequiredProperty("nextObjectID", value => nextObjectID = value), + new OptionalProperty("infinite", value => infinite = value == 1) + ]); + + 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/Tmj/TmjMapReader.cs b/DotTiled/Serialization/Tmj/TmjMapReader.cs new file mode 100644 index 0000000..42919d6 --- /dev/null +++ b/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; + +namespace DotTiled; + +public class TmjMapReader : IMapReader +{ + private string _jsonString; + private bool disposedValue; + + public TmjMapReader(string jsonString) + { + _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); + } + + public Map ReadMap() + { + var bytes = Encoding.UTF8.GetBytes(_jsonString); + var options = new JsonReaderOptions + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip, + }; + var reader = new Utf8JsonReader(bytes, options); + reader.MoveToContent(); + + return Tmj.ReadMap(ref reader); + } + + 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 + // ~TmjMapReader() + // { + // // 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 2a10ebb4969c7431035bd0a62db44eb1f1064a2d Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 8 Aug 2024 23:04:05 +0200 Subject: [PATCH 17/39] Stuck on how to handle class properties --- .../Serialization/Tmj/TmjMapReaderTests.cs | 35 ++- .../Tmj/ExtensionsUtf8JsonReader.cs | 143 +++++------ DotTiled/Serialization/Tmj/Tmj.Map.cs | 223 ++++++++++++++++-- 3 files changed, 296 insertions(+), 105 deletions(-) diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 2462fcd..7e15bac 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -14,12 +14,45 @@ public partial class TmjMapReaderTests "nextobjectid":1, "nextlayerid":1, "orientation":"orthogonal", + "properties": [ + { + "name":"mapProperty1", + "type":"string", + "value":"one" + }, + { + "name":"mapProperty3", + "type":"string", + "value":"twoeee" + } + ], "renderorder":"right-down", "tileheight":32, "tilewidth":32, "version":"1", "tiledversion":"1.0.3", - "width":4 + "width":4, + "tilesets": [ + { + "columns":19, + "firstgid":1, + "image":"image/fishbaddie_parts.png", + "imageheight":480, + "imagewidth":640, + "margin":3, + "name":"", + "properties":[ + { + "name":"myProperty1", + "type":"string", + "value":"myProperty1_value" + }], + "spacing":1, + "tilecount":266, + "tileheight":32, + "tilewidth":32 + } + ] } """; diff --git a/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs b/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs index a0cd7a6..1c98bc6 100644 --- a/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs +++ b/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs @@ -14,40 +14,26 @@ internal partial class Tmj internal string PropertyName { get; } = propertyName; } - internal class RequiredProperty(string propertyName, Action withValue) : JsonProperty(propertyName) + internal delegate void UseReader(ref Utf8JsonReader reader); + + internal class RequiredProperty(string propertyName, UseReader useReader) : JsonProperty(propertyName) { - internal Action WithValue { get; } = withValue; + internal UseReader UseReader { get; } = useReader; } - internal class OptionalProperty(string propertyName, Action withValue, bool allowNull = false) : JsonProperty(propertyName) + internal class OptionalProperty(string propertyName, UseReader useReader, bool allowNull = true) : JsonProperty(propertyName) { - internal Action WithValue { get; } = withValue; + internal UseReader UseReader { get; } = useReader; internal bool AllowNull { get; } = allowNull; } } internal static class ExtensionsUtf8JsonReader { - private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) + internal static T Progress(ref this Utf8JsonReader reader, T value) { - while (toCheck != typeof(object)) - { - var cur = toCheck!.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; - if (generic == cur) - return true; - - toCheck = toCheck.BaseType!; - } - - return false; - } - - internal static void Require(this ref Utf8JsonReader reader, ProcessProperty process) - { - if (reader.TokenType == JsonTokenType.Null) - throw new JsonException("Value is required."); - - process(ref reader); + reader.Read(); + return value; } internal static void MoveToContent(this ref Utf8JsonReader reader) @@ -59,16 +45,15 @@ internal static class ExtensionsUtf8JsonReader internal delegate void ProcessProperty(ref Utf8JsonReader reader); - internal static void ProcessJsonObject(this Utf8JsonReader reader, (string PropertyName, ProcessProperty Processor)[] processors) + private static void ProcessJsonObject(this ref Utf8JsonReader reader, (string PropertyName, ProcessProperty Processor)[] processors) { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("Expected start of object."); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndObject) - return; + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("Expected property name."); @@ -77,7 +62,11 @@ internal static class ExtensionsUtf8JsonReader if (!processors.Any(x => x.PropertyName == propertyName)) { - reader.Skip(); + var depthBefore = reader.CurrentDepth; + + while (reader.TokenType != JsonTokenType.PropertyName || reader.CurrentDepth > depthBefore) + reader.Read(); + continue; } @@ -85,74 +74,66 @@ internal static class ExtensionsUtf8JsonReader processor(ref reader); } - throw new JsonException("Expected end of object."); + if (reader.TokenType != JsonTokenType.EndObject) + throw new JsonException("Expected end of object."); + + reader.Read(); } - delegate T UseReader(ref Utf8JsonReader reader); - - internal static void ProcessJsonObject(this Utf8JsonReader reader, Tmj.JsonProperty[] properties) + internal static void ProcessJsonObject(this ref Utf8JsonReader reader, Tmj.JsonProperty[] properties, string objectTypeName) { List processedProperties = []; - bool CheckType(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader useReader) + ProcessJsonObject(ref reader, properties.Select(x => (x.PropertyName, (ref Utf8JsonReader reader) => { - return CheckRequire(ref reader, prop, (ref Utf8JsonReader r) => useReader(ref r)!) || CheckOptional(ref reader, prop, useReader); - } + if (processedProperties.Contains(x.PropertyName)) + throw new JsonException($"Property '{x.PropertyName}' was already processed."); - bool CheckRequire(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader useReader) - { - if (prop is Tmj.RequiredProperty requiredProp) + processedProperties.Add(x.PropertyName); + + if (x is Tmj.RequiredProperty req) { - reader.Require((ref Utf8JsonReader r) => - { - requiredProp.WithValue(useReader(ref r)); - }); - return true; - } - return false; - } + if (reader.TokenType == JsonTokenType.Null) + throw new JsonException($"Required property '{req.PropertyName}' cannot be null when reading {objectTypeName}."); - bool CheckOptional(ref Utf8JsonReader reader, Tmj.JsonProperty prop, UseReader useReader) - { - if (prop is Tmj.OptionalProperty optionalProp) + req.UseReader(ref reader); + } + else if (x is Tmj.OptionalProperty opt) { - if (reader.TokenType == JsonTokenType.Null && !optionalProp.AllowNull) - throw new JsonException("Value cannot be null for optional property."); - else if (reader.TokenType == JsonTokenType.Null && optionalProp.AllowNull) - optionalProp.WithValue(default); - else - optionalProp.WithValue(useReader(ref reader)); - return true; + if (reader.TokenType == JsonTokenType.Null && !opt.AllowNull) + throw new JsonException($"Value cannot be null for optional property '{opt.PropertyName}' when reading {objectTypeName}."); + else if (reader.TokenType == JsonTokenType.Null && opt.AllowNull) + return; + + opt.UseReader(ref reader); } - return false; - } - - ProcessJsonObject(reader, properties.Select(x => (x.PropertyName.ToLowerInvariant(), (ref Utf8JsonReader reader) => - { - var lowerInvariant = x.PropertyName.ToLowerInvariant(); - - if (processedProperties.Contains(lowerInvariant)) - throw new JsonException($"Property '{lowerInvariant}' was already processed."); - - processedProperties.Add(lowerInvariant); - - if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetString()!)) - return; - if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetInt32())) - return; - if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetUInt32())) - return; - if (CheckType(ref reader, x, (ref Utf8JsonReader r) => r.GetSingle())) - return; - - throw new NotSupportedException($"Unsupported property type '{x.GetType().GenericTypeArguments.First()}'."); } )).ToArray()); foreach (var property in properties) { - if (IsSubclassOfRawGeneric(typeof(Tmj.RequiredProperty<>), property.GetType()) && !processedProperties.Contains(property.PropertyName.ToLowerInvariant())) - throw new JsonException($"Required property '{property.PropertyName}' was not found."); + if (property is Tmj.RequiredProperty && !processedProperties.Contains(property.PropertyName)) + throw new JsonException($"Required property '{property.PropertyName}' was not found when reading {objectTypeName}."); } } + + internal delegate void UseReader(ref Utf8JsonReader reader); + + internal static void ProcessJsonArray(this ref Utf8JsonReader reader, UseReader useReader) + { + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException("Expected start of array."); + + reader.Read(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + useReader(ref reader); + } + + if (reader.TokenType != JsonTokenType.EndArray) + throw new JsonException("Expected end of array."); + + reader.Read(); + } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Map.cs b/DotTiled/Serialization/Tmj/Tmj.Map.cs index ce3ab2a..c76c391 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Map.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Text.Json; namespace DotTiled; @@ -27,11 +29,18 @@ internal partial class Tmj uint nextObjectID = 0; bool infinite = false; + // At most one of + Dictionary? properties = null; + + // Any number of + List layers = []; + List tilesets = []; + reader.ProcessJsonObject([ - new RequiredProperty("version", value => version = value), - new RequiredProperty("tiledVersion", value => tiledVersion = value), - new OptionalProperty("class", value => @class = value ?? ""), - new RequiredProperty("orientation", value => orientation = value switch + new RequiredProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString()!)), + new RequiredProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString()!)), + new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true), + new RequiredProperty("orientation", (ref Utf8JsonReader reader) => orientation = reader.Progress(reader.GetString()) switch { "orthogonal" => MapOrientation.Orthogonal, "isometric" => MapOrientation.Isometric, @@ -39,7 +48,7 @@ internal partial class Tmj "hexagonal" => MapOrientation.Hexagonal, _ => throw new JsonException("Invalid orientation.") }), - new OptionalProperty("renderOrder", value => renderOrder = value switch + new OptionalProperty("renderorder", (ref Utf8JsonReader reader) => renderOrder = reader.Progress(reader.GetString()) switch { "right-down" => RenderOrder.RightDown, "right-up" => RenderOrder.RightUp, @@ -47,31 +56,34 @@ internal partial class Tmj "left-up" => RenderOrder.LeftUp, _ => throw new JsonException("Invalid render order.") }), - new OptionalProperty("compressionLevel", value => compressionLevel = value), - new RequiredProperty("width", value => width = value), - new RequiredProperty("height", value => height = value), - new RequiredProperty("tileWidth", value => tileWidth = value), - new RequiredProperty("tileHeight", value => tileHeight = value), - new OptionalProperty("hexSideLength", value => hexSideLength = value), - new OptionalProperty("staggerAxis", value => staggerAxis = value switch + new OptionalProperty("compressionlevel", (ref Utf8JsonReader reader) => compressionLevel = reader.Progress(reader.GetInt32())), + new RequiredProperty("width", (ref Utf8JsonReader reader) => width = reader.Progress(reader.GetUInt32())), + new RequiredProperty("height", (ref Utf8JsonReader reader) => height = reader.Progress(reader.GetUInt32())), + new RequiredProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())), + new RequiredProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())), + new OptionalProperty("hexsidelength", (ref Utf8JsonReader reader) => hexSideLength = reader.Progress(reader.GetUInt32())), + new OptionalProperty("staggeraxis", (ref Utf8JsonReader reader) => staggerAxis = reader.Progress(reader.GetString()) switch { "x" => StaggerAxis.X, "y" => StaggerAxis.Y, _ => throw new JsonException("Invalid stagger axis.") }), - new OptionalProperty("staggerIndex", value => staggerIndex = value switch + new OptionalProperty("staggerindex", (ref Utf8JsonReader reader) => staggerIndex = reader.Progress(reader.GetString()) switch { "odd" => StaggerIndex.Odd, "even" => StaggerIndex.Even, _ => throw new JsonException("Invalid stagger index.") }), - new OptionalProperty("parallaxOriginX", value => parallaxOriginX = value), - new OptionalProperty("parallaxOriginY", value => parallaxOriginY = value), - new OptionalProperty("backgroundColor", value => backgroundColor = Color.Parse(value!, CultureInfo.InvariantCulture)), - new RequiredProperty("nextLayerID", value => nextLayerID = value), - new RequiredProperty("nextObjectID", value => nextObjectID = value), - new OptionalProperty("infinite", value => infinite = value == 1) - ]); + new OptionalProperty("parallaxoriginx", (ref Utf8JsonReader reader) => parallaxOriginX = reader.Progress(reader.GetSingle())), + new OptionalProperty("parallaxoriginy", (ref Utf8JsonReader reader) => parallaxOriginY = reader.Progress(reader.GetSingle())), + new OptionalProperty("backgroundcolor", (ref Utf8JsonReader reader) => backgroundColor = Color.Parse(reader.Progress(reader.GetString()!), CultureInfo.InvariantCulture)), + new RequiredProperty("nextlayerid", (ref Utf8JsonReader reader) => nextLayerID = reader.Progress(reader.GetUInt32())), + new RequiredProperty("nextobjectid", (ref Utf8JsonReader reader) => nextObjectID = reader.Progress(reader.GetUInt32())), + new OptionalProperty("infinite", (ref Utf8JsonReader reader) => infinite = reader.Progress(reader.GetUInt32()) == 1), + + new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader)), + new OptionalProperty("tilesets", (ref Utf8JsonReader reader) => tilesets = ReadTilesets(ref reader)) + ], "map"); return new Map { @@ -94,9 +106,174 @@ internal partial class Tmj NextLayerID = nextLayerID, NextObjectID = nextObjectID, Infinite = infinite, - //Properties = properties, - //Tilesets = tilesets, - //Layers = layers + Properties = properties, + Tilesets = tilesets, + Layers = layers + }; + } + + internal static Dictionary ReadProperties(ref Utf8JsonReader reader) + { + var properties = new Dictionary(); + + reader.ProcessJsonArray((ref Utf8JsonReader reader) => + { + var property = ReadProperty(ref reader); + properties.Add(property.Name, property); + }); + + return properties; + } + + internal static IProperty ReadProperty(ref Utf8JsonReader reader) + { + string name = default!; + string type = default!; + IProperty property = null; + + reader.ProcessJsonObject([ + new RequiredProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString()!)), + new RequiredProperty("type", (ref Utf8JsonReader reader) => type = reader.Progress(reader.GetString()!)), + new RequiredProperty("value", (ref Utf8JsonReader reader) => + { + property = type switch + { + "string" => new StringProperty { Name = name, Value = reader.Progress(reader.GetString()!) }, + "int" => new IntProperty { Name = name, Value = reader.Progress(reader.GetInt32()) }, + "float" => new FloatProperty { Name = name, Value = reader.Progress(reader.GetSingle()) }, + "bool" => new BoolProperty { Name = name, Value = reader.Progress(reader.GetBoolean()) }, + "color" => new ColorProperty { Name = name, Value = Color.Parse(reader.Progress(reader.GetString()!), CultureInfo.InvariantCulture) }, + "file" => new FileProperty { Name = name, Value = reader.Progress(reader.GetString()!) }, + "object" => new ObjectProperty { Name = name, Value = reader.Progress(reader.GetUInt32()) }, + // "class" => ReadClassProperty(ref reader), + _ => throw new JsonException("Invalid property type.") + }; + }), + ], "property"); + + return property!; + } + + internal static List ReadTilesets(ref Utf8JsonReader reader) + { + var tilesets = new List(); + + reader.ProcessJsonArray((ref Utf8JsonReader reader) => + { + var tileset = ReadTileset(ref reader); + tilesets.Add(tileset); + }); + + return tilesets; + } + + internal static Tileset ReadTileset(ref Utf8JsonReader reader) + { + string? version = null; + string? tiledVersion = null; + uint? firstGID = null; + string? source = null; + string? name = null; + string @class = ""; + uint? tileWidth = null; + uint? tileHeight = null; + uint? spacing = null; + uint? margin = null; + uint? tileCount = null; + uint? columns = null; + ObjectAlignment objectAlignment = ObjectAlignment.Unspecified; + FillMode fillMode = FillMode.Stretch; + + string? image = null; + uint? imageWidth = null; + uint? imageHeight = null; + + Dictionary? properties = null; + + reader.ProcessJsonObject([ + new OptionalProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString())), + new OptionalProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString())), + new OptionalProperty("firstgid", (ref Utf8JsonReader reader) => firstGID = reader.Progress(reader.GetUInt32())), + new OptionalProperty("source", (ref Utf8JsonReader reader) => source = reader.Progress(reader.GetString())), + new OptionalProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString())), + new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true), + new OptionalProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())), + new OptionalProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())), + new OptionalProperty("spacing", (ref Utf8JsonReader reader) => spacing = reader.Progress(reader.GetUInt32())), + new OptionalProperty("margin", (ref Utf8JsonReader reader) => margin = reader.Progress(reader.GetUInt32())), + new OptionalProperty("tilecount", (ref Utf8JsonReader reader) => tileCount = reader.Progress(reader.GetUInt32())), + new OptionalProperty("columns", (ref Utf8JsonReader reader) => columns = reader.Progress(reader.GetUInt32())), + new OptionalProperty("objectalignment", (ref Utf8JsonReader reader) => objectAlignment = reader.Progress(reader.GetString()) 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 JsonException("Invalid object alignment.") + }), + new OptionalProperty("fillmode", (ref Utf8JsonReader reader) => fillMode = reader.Progress(reader.GetString()) switch + { + "stretch" => FillMode.Stretch, + "preserve-aspect-fit" => FillMode.PreserveAspectFit, + _ => throw new JsonException("Invalid fill mode.") + }), + + new OptionalProperty("image", (ref Utf8JsonReader reader) => image = reader.Progress(reader.GetString())), + new OptionalProperty("imagewidth", (ref Utf8JsonReader reader) => imageWidth = reader.Progress(reader.GetUInt32())), + new OptionalProperty("imageheight", (ref Utf8JsonReader reader) => imageHeight = reader.Progress(reader.GetUInt32())), + + new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader)) + ], "tileset"); + + Image? imageInstance = image is not null ? new Image + { + Format = ParseImageFormatFromSource(image), + Width = imageWidth, + Height = imageHeight, + Source = image + } : null; + + 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, + FillMode = fillMode, + Image = imageInstance, + Properties = properties + }; + } + + private static ImageFormat ParseImageFormatFromSource(string? source) + { + if (source is null) + throw new JsonException("Image source is required to determine image format."); + + var extension = Path.GetExtension(source); + return extension switch + { + ".png" => ImageFormat.Png, + ".jpg" => ImageFormat.Jpg, + ".jpeg" => ImageFormat.Jpg, + ".gif" => ImageFormat.Gif, + ".bmp" => ImageFormat.Bmp, + _ => throw new JsonException("Invalid image format.") }; } } From 5626614acdeda8947f46a29695c311e42243dd69 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Thu, 8 Aug 2024 23:55:26 +0200 Subject: [PATCH 18/39] Move over to JsonElement --- .../Tmj/ExtensionsJsonElement.cs | 97 +++++ DotTiled/Serialization/Tmj/Tmj.Map.cs | 400 +++++++++--------- DotTiled/Serialization/Tmj/TmjMapReader.cs | 13 +- 3 files changed, 291 insertions(+), 219 deletions(-) create mode 100644 DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs diff --git a/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs b/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs new file mode 100644 index 0000000..d7620e4 --- /dev/null +++ b/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json; + +namespace DotTiled; + +internal static class ExtensionsJsonElement +{ + internal static T GetRequiredProperty(this JsonElement element, string propertyName) + { + if (!element.TryGetProperty(propertyName, out var property)) + throw new JsonException($"Missing required property '{propertyName}'."); + + return property.GetValueAs(); + } + + internal static T GetOptionalProperty(this JsonElement element, string propertyName, T defaultValue) + { + if (!element.TryGetProperty(propertyName, out var property)) + return defaultValue; + + return property.GetValueAs(); + } + + internal static T GetValueAs(this JsonElement element) + { + string val = typeof(T) switch + { + Type t when t == typeof(string) => element.GetString()!, + Type t when t == typeof(int) => element.GetInt32().ToString(CultureInfo.InvariantCulture), + Type t when t == typeof(uint) => element.GetUInt32().ToString(CultureInfo.InvariantCulture), + Type t when t == typeof(float) => element.GetSingle().ToString(CultureInfo.InvariantCulture), + _ => throw new JsonException($"Unsupported type '{typeof(T)}'.") + }; + + return (T)Convert.ChangeType(val, typeof(T), CultureInfo.InvariantCulture); + } + + internal static T GetRequiredPropertyParseable(this JsonElement element, string propertyName) where T : IParsable + { + if (!element.TryGetProperty(propertyName, out var property)) + throw new JsonException($"Missing required property '{propertyName}'."); + + return T.Parse(property.GetString()!, CultureInfo.InvariantCulture); + } + + internal static T GetRequiredPropertyParseable(this JsonElement element, string propertyName, Func parser) + { + if (!element.TryGetProperty(propertyName, out var property)) + throw new JsonException($"Missing required property '{propertyName}'."); + + return parser(property.GetString()!); + } + + internal static T GetOptionalPropertyParseable(this JsonElement element, string propertyName, T defaultValue) where T : IParsable + { + if (!element.TryGetProperty(propertyName, out var property)) + return defaultValue; + + return T.Parse(property.GetString()!, CultureInfo.InvariantCulture); + } + + internal static T GetOptionalPropertyParseable(this JsonElement element, string propertyName, Func parser, T defaultValue) + { + if (!element.TryGetProperty(propertyName, out var property)) + return defaultValue; + + return parser(property.GetString()!); + } + + internal static T GetRequiredPropertyCustom(this JsonElement element, string propertyName, Func parser) + { + if (!element.TryGetProperty(propertyName, out var property)) + throw new JsonException($"Missing required property '{propertyName}'."); + + return parser(property); + } + + internal static T GetOptionalPropertyCustom(this JsonElement element, string propertyName, Func parser, T defaultValue) + { + if (!element.TryGetProperty(propertyName, out var property)) + return defaultValue; + + return parser(property); + } + + internal static List GetValueAsList(this JsonElement element, Func parser) + { + var list = new List(); + + foreach (var item in element.EnumerateArray()) + list.Add(parser(item)); + + return list; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Map.cs b/DotTiled/Serialization/Tmj/Tmj.Map.cs index c76c391..d4ea6f1 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Map.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -7,84 +7,59 @@ namespace DotTiled; internal partial class Tmj { - internal static Map ReadMap(ref Utf8JsonReader reader) + internal static Map ReadMap(JsonElement element) { - string version = default!; - string tiledVersion = default!; - string @class = ""; - MapOrientation orientation = default; - RenderOrder renderOrder = RenderOrder.RightDown; - int compressionLevel = -1; - uint width = 0; - uint height = 0; - uint tileWidth = 0; - uint tileHeight = 0; - uint? hexSideLength = null; - StaggerAxis? staggerAxis = null; - StaggerIndex? staggerIndex = null; - float parallaxOriginX = 0.0f; - float parallaxOriginY = 0.0f; - Color backgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture); - uint nextLayerID = 0; - uint nextObjectID = 0; - bool infinite = false; + var version = element.GetRequiredProperty("version"); + var tiledVersion = element.GetRequiredProperty("tiledversion"); + string @class = element.GetOptionalProperty("class", ""); + var orientation = element.GetRequiredPropertyParseable("orientation", s => s switch + { + "orthogonal" => MapOrientation.Orthogonal, + "isometric" => MapOrientation.Isometric, + "staggered" => MapOrientation.Staggered, + "hexagonal" => MapOrientation.Hexagonal, + _ => throw new JsonException($"Unknown orientation '{s}'") + }); + var renderOrder = element.GetOptionalPropertyParseable("renderorder", s => s switch + { + "right-down" => RenderOrder.RightDown, + "right-up" => RenderOrder.RightUp, + "left-down" => RenderOrder.LeftDown, + "left-up" => RenderOrder.LeftUp, + _ => throw new JsonException($"Unknown render order '{s}'") + }, RenderOrder.RightDown); + var compressionLevel = element.GetOptionalProperty("compressionlevel", -1); + var width = element.GetRequiredProperty("width"); + var height = element.GetRequiredProperty("height"); + var tileWidth = element.GetRequiredProperty("tilewidth"); + var tileHeight = element.GetRequiredProperty("tileheight"); + var hexSideLength = element.GetOptionalProperty("hexsidelength", null); + var staggerAxis = element.GetOptionalPropertyParseable("staggeraxis", s => s switch + { + "x" => StaggerAxis.X, + "y" => StaggerAxis.Y, + _ => throw new JsonException($"Unknown stagger axis '{s}'") + }, null); + var staggerIndex = element.GetOptionalPropertyParseable("staggerindex", s => s switch + { + "odd" => StaggerIndex.Odd, + "even" => StaggerIndex.Even, + _ => throw new JsonException($"Unknown stagger index '{s}'") + }, null); + var parallaxOriginX = element.GetOptionalProperty("parallaxoriginx", 0.0f); + var parallaxOriginY = element.GetOptionalProperty("parallaxoriginy", 0.0f); + var backgroundColor = element.GetOptionalPropertyParseable("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); + var nextLayerID = element.GetRequiredProperty("nextlayerid"); + var nextObjectID = element.GetRequiredProperty("nextobjectid"); + var infinite = element.GetOptionalProperty("infinite", 0) == 1; // At most one of - Dictionary? properties = null; + Dictionary? properties = element.GetOptionalPropertyCustom>("properties", ReadProperties, null); // Any number of List layers = []; List tilesets = []; - reader.ProcessJsonObject([ - new RequiredProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString()!)), - new RequiredProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString()!)), - new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true), - new RequiredProperty("orientation", (ref Utf8JsonReader reader) => orientation = reader.Progress(reader.GetString()) switch - { - "orthogonal" => MapOrientation.Orthogonal, - "isometric" => MapOrientation.Isometric, - "staggered" => MapOrientation.Staggered, - "hexagonal" => MapOrientation.Hexagonal, - _ => throw new JsonException("Invalid orientation.") - }), - new OptionalProperty("renderorder", (ref Utf8JsonReader reader) => renderOrder = reader.Progress(reader.GetString()) switch - { - "right-down" => RenderOrder.RightDown, - "right-up" => RenderOrder.RightUp, - "left-down" => RenderOrder.LeftDown, - "left-up" => RenderOrder.LeftUp, - _ => throw new JsonException("Invalid render order.") - }), - new OptionalProperty("compressionlevel", (ref Utf8JsonReader reader) => compressionLevel = reader.Progress(reader.GetInt32())), - new RequiredProperty("width", (ref Utf8JsonReader reader) => width = reader.Progress(reader.GetUInt32())), - new RequiredProperty("height", (ref Utf8JsonReader reader) => height = reader.Progress(reader.GetUInt32())), - new RequiredProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())), - new RequiredProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())), - new OptionalProperty("hexsidelength", (ref Utf8JsonReader reader) => hexSideLength = reader.Progress(reader.GetUInt32())), - new OptionalProperty("staggeraxis", (ref Utf8JsonReader reader) => staggerAxis = reader.Progress(reader.GetString()) switch - { - "x" => StaggerAxis.X, - "y" => StaggerAxis.Y, - _ => throw new JsonException("Invalid stagger axis.") - }), - new OptionalProperty("staggerindex", (ref Utf8JsonReader reader) => staggerIndex = reader.Progress(reader.GetString()) switch - { - "odd" => StaggerIndex.Odd, - "even" => StaggerIndex.Even, - _ => throw new JsonException("Invalid stagger index.") - }), - new OptionalProperty("parallaxoriginx", (ref Utf8JsonReader reader) => parallaxOriginX = reader.Progress(reader.GetSingle())), - new OptionalProperty("parallaxoriginy", (ref Utf8JsonReader reader) => parallaxOriginY = reader.Progress(reader.GetSingle())), - new OptionalProperty("backgroundcolor", (ref Utf8JsonReader reader) => backgroundColor = Color.Parse(reader.Progress(reader.GetString()!), CultureInfo.InvariantCulture)), - new RequiredProperty("nextlayerid", (ref Utf8JsonReader reader) => nextLayerID = reader.Progress(reader.GetUInt32())), - new RequiredProperty("nextobjectid", (ref Utf8JsonReader reader) => nextObjectID = reader.Progress(reader.GetUInt32())), - new OptionalProperty("infinite", (ref Utf8JsonReader reader) => infinite = reader.Progress(reader.GetUInt32()) == 1), - - new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader)), - new OptionalProperty("tilesets", (ref Utf8JsonReader reader) => tilesets = ReadTilesets(ref reader)) - ], "map"); - return new Map { Version = version, @@ -112,168 +87,175 @@ internal partial class Tmj }; } - internal static Dictionary ReadProperties(ref Utf8JsonReader reader) + internal static Dictionary ReadProperties(JsonElement element) { var properties = new Dictionary(); - reader.ProcessJsonArray((ref Utf8JsonReader reader) => + element.GetValueAsList(e => { - var property = ReadProperty(ref reader); - properties.Add(property.Name, property); - }); + var name = e.GetRequiredProperty("name"); + var type = e.GetOptionalPropertyParseable("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 JsonException("Invalid property type") + }, PropertyType.String); + + IProperty property = type switch + { + PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, + PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Class => ReadClassProperty(e), + _ => throw new JsonException("Invalid property type") + }; + + return property!; + }).ForEach(p => properties.Add(p.Name, p)); return properties; } - internal static IProperty ReadProperty(ref Utf8JsonReader reader) + internal static ClassProperty ReadClassProperty(JsonElement element) { - string name = default!; - string type = default!; - IProperty property = null; + var name = element.GetRequiredProperty("name"); + var propertyType = element.GetRequiredProperty("propertytype"); - reader.ProcessJsonObject([ - new RequiredProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString()!)), - new RequiredProperty("type", (ref Utf8JsonReader reader) => type = reader.Progress(reader.GetString()!)), - new RequiredProperty("value", (ref Utf8JsonReader reader) => - { - property = type switch - { - "string" => new StringProperty { Name = name, Value = reader.Progress(reader.GetString()!) }, - "int" => new IntProperty { Name = name, Value = reader.Progress(reader.GetInt32()) }, - "float" => new FloatProperty { Name = name, Value = reader.Progress(reader.GetSingle()) }, - "bool" => new BoolProperty { Name = name, Value = reader.Progress(reader.GetBoolean()) }, - "color" => new ColorProperty { Name = name, Value = Color.Parse(reader.Progress(reader.GetString()!), CultureInfo.InvariantCulture) }, - "file" => new FileProperty { Name = name, Value = reader.Progress(reader.GetString()!) }, - "object" => new ObjectProperty { Name = name, Value = reader.Progress(reader.GetUInt32()) }, - // "class" => ReadClassProperty(ref reader), - _ => throw new JsonException("Invalid property type.") - }; - }), - ], "property"); + var properties = element.GetRequiredPropertyCustom>("properties", ReadProperties); - return property!; + return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; } - internal static List ReadTilesets(ref Utf8JsonReader reader) - { - var tilesets = new List(); + // internal static List ReadTilesets(ref Utf8JsonReader reader) + // { + // var tilesets = new List(); - reader.ProcessJsonArray((ref Utf8JsonReader reader) => - { - var tileset = ReadTileset(ref reader); - tilesets.Add(tileset); - }); + // reader.ProcessJsonArray((ref Utf8JsonReader reader) => + // { + // var tileset = ReadTileset(ref reader); + // tilesets.Add(tileset); + // }); - return tilesets; - } + // return tilesets; + // } - internal static Tileset ReadTileset(ref Utf8JsonReader reader) - { - string? version = null; - string? tiledVersion = null; - uint? firstGID = null; - string? source = null; - string? name = null; - string @class = ""; - uint? tileWidth = null; - uint? tileHeight = null; - uint? spacing = null; - uint? margin = null; - uint? tileCount = null; - uint? columns = null; - ObjectAlignment objectAlignment = ObjectAlignment.Unspecified; - FillMode fillMode = FillMode.Stretch; + // internal static Tileset ReadTileset(ref Utf8JsonReader reader) + // { + // string? version = null; + // string? tiledVersion = null; + // uint? firstGID = null; + // string? source = null; + // string? name = null; + // string @class = ""; + // uint? tileWidth = null; + // uint? tileHeight = null; + // uint? spacing = null; + // uint? margin = null; + // uint? tileCount = null; + // uint? columns = null; + // ObjectAlignment objectAlignment = ObjectAlignment.Unspecified; + // FillMode fillMode = FillMode.Stretch; - string? image = null; - uint? imageWidth = null; - uint? imageHeight = null; + // string? image = null; + // uint? imageWidth = null; + // uint? imageHeight = null; - Dictionary? properties = null; + // Dictionary? properties = null; - reader.ProcessJsonObject([ - new OptionalProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString())), - new OptionalProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString())), - new OptionalProperty("firstgid", (ref Utf8JsonReader reader) => firstGID = reader.Progress(reader.GetUInt32())), - new OptionalProperty("source", (ref Utf8JsonReader reader) => source = reader.Progress(reader.GetString())), - new OptionalProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString())), - new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true), - new OptionalProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())), - new OptionalProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())), - new OptionalProperty("spacing", (ref Utf8JsonReader reader) => spacing = reader.Progress(reader.GetUInt32())), - new OptionalProperty("margin", (ref Utf8JsonReader reader) => margin = reader.Progress(reader.GetUInt32())), - new OptionalProperty("tilecount", (ref Utf8JsonReader reader) => tileCount = reader.Progress(reader.GetUInt32())), - new OptionalProperty("columns", (ref Utf8JsonReader reader) => columns = reader.Progress(reader.GetUInt32())), - new OptionalProperty("objectalignment", (ref Utf8JsonReader reader) => objectAlignment = reader.Progress(reader.GetString()) 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 JsonException("Invalid object alignment.") - }), - new OptionalProperty("fillmode", (ref Utf8JsonReader reader) => fillMode = reader.Progress(reader.GetString()) switch - { - "stretch" => FillMode.Stretch, - "preserve-aspect-fit" => FillMode.PreserveAspectFit, - _ => throw new JsonException("Invalid fill mode.") - }), + // reader.ProcessJsonObject([ + // new OptionalProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString())), + // new OptionalProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString())), + // new OptionalProperty("firstgid", (ref Utf8JsonReader reader) => firstGID = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("source", (ref Utf8JsonReader reader) => source = reader.Progress(reader.GetString())), + // new OptionalProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString())), + // new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true), + // new OptionalProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("spacing", (ref Utf8JsonReader reader) => spacing = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("margin", (ref Utf8JsonReader reader) => margin = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("tilecount", (ref Utf8JsonReader reader) => tileCount = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("columns", (ref Utf8JsonReader reader) => columns = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("objectalignment", (ref Utf8JsonReader reader) => objectAlignment = reader.Progress(reader.GetString()) 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 JsonException("Invalid object alignment.") + // }), + // new OptionalProperty("fillmode", (ref Utf8JsonReader reader) => fillMode = reader.Progress(reader.GetString()) switch + // { + // "stretch" => FillMode.Stretch, + // "preserve-aspect-fit" => FillMode.PreserveAspectFit, + // _ => throw new JsonException("Invalid fill mode.") + // }), - new OptionalProperty("image", (ref Utf8JsonReader reader) => image = reader.Progress(reader.GetString())), - new OptionalProperty("imagewidth", (ref Utf8JsonReader reader) => imageWidth = reader.Progress(reader.GetUInt32())), - new OptionalProperty("imageheight", (ref Utf8JsonReader reader) => imageHeight = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("image", (ref Utf8JsonReader reader) => image = reader.Progress(reader.GetString())), + // new OptionalProperty("imagewidth", (ref Utf8JsonReader reader) => imageWidth = reader.Progress(reader.GetUInt32())), + // new OptionalProperty("imageheight", (ref Utf8JsonReader reader) => imageHeight = reader.Progress(reader.GetUInt32())), - new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader)) - ], "tileset"); + // new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader)) + // ], "tileset"); - Image? imageInstance = image is not null ? new Image - { - Format = ParseImageFormatFromSource(image), - Width = imageWidth, - Height = imageHeight, - Source = image - } : null; + // Image? imageInstance = image is not null ? new Image + // { + // Format = ParseImageFormatFromSource(image), + // Width = imageWidth, + // Height = imageHeight, + // Source = image + // } : null; - 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, - FillMode = fillMode, - Image = imageInstance, - Properties = properties - }; - } + // 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, + // FillMode = fillMode, + // Image = imageInstance, + // Properties = properties + // }; + // } - private static ImageFormat ParseImageFormatFromSource(string? source) - { - if (source is null) - throw new JsonException("Image source is required to determine image format."); + // private static ImageFormat ParseImageFormatFromSource(string? source) + // { + // if (source is null) + // throw new JsonException("Image source is required to determine image format."); - var extension = Path.GetExtension(source); - return extension switch - { - ".png" => ImageFormat.Png, - ".jpg" => ImageFormat.Jpg, - ".jpeg" => ImageFormat.Jpg, - ".gif" => ImageFormat.Gif, - ".bmp" => ImageFormat.Bmp, - _ => throw new JsonException("Invalid image format.") - }; - } + // var extension = Path.GetExtension(source); + // return extension switch + // { + // ".png" => ImageFormat.Png, + // ".jpg" => ImageFormat.Jpg, + // ".jpeg" => ImageFormat.Jpg, + // ".gif" => ImageFormat.Gif, + // ".bmp" => ImageFormat.Bmp, + // _ => throw new JsonException("Invalid image format.") + // }; + // } } diff --git a/DotTiled/Serialization/Tmj/TmjMapReader.cs b/DotTiled/Serialization/Tmj/TmjMapReader.cs index 42919d6..4a113b1 100644 --- a/DotTiled/Serialization/Tmj/TmjMapReader.cs +++ b/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -17,16 +17,9 @@ public class TmjMapReader : IMapReader public Map ReadMap() { - var bytes = Encoding.UTF8.GetBytes(_jsonString); - var options = new JsonReaderOptions - { - AllowTrailingCommas = true, - CommentHandling = JsonCommentHandling.Skip, - }; - var reader = new Utf8JsonReader(bytes, options); - reader.MoveToContent(); - - return Tmj.ReadMap(ref reader); + var jsonDoc = JsonDocument.Parse(_jsonString); + var rootElement = jsonDoc.RootElement; + return Tmj.ReadMap(rootElement); } protected virtual void Dispose(bool disposing) From 1168917c23da9b3d8af657b61d757db1de5208f7 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Fri, 9 Aug 2024 23:12:45 +0200 Subject: [PATCH 19/39] Getting further with the json support --- DotTiled.Tests/Assert/AssertMap.cs | 4 +- DotTiled.Tests/DotTiled.Tests.csproj | 4 +- .../{Tmx/TestData => }/TestData.cs | 6 +- .../TestData/Map/empty-map-base64-gzip.tmj | 30 +++ .../TestData/Map/empty-map-base64-gzip.tmx | 0 .../TestData/Map/empty-map-base64-zlib.tmj | 30 +++ .../TestData/Map/empty-map-base64-zlib.tmx | 0 .../TestData/Map/empty-map-base64.tmj | 30 +++ .../TestData/Map/empty-map-base64.tmx | 0 .../TestData/Map/empty-map-csv.tmj | 32 +++ .../{Tmx => }/TestData/Map/empty-map-csv.tmx | 0 .../TestData/Map/empty-map-properties.cs | 4 +- .../TestData/Map/empty-map-properties.tmj | 69 ++++++ .../TestData/Map/empty-map-properties.tmx | 0 .../{Tmx => }/TestData/Map/empty-map.cs | 4 +- .../{Tmx => }/TestData/Map/map-with-group.cs | 4 +- .../TestData/Map/map-with-group.tmj | 80 +++++++ .../{Tmx => }/TestData/Map/map-with-group.tmx | 0 .../TestData/Map/map-with-object-template.cs | 4 +- .../TestData/Map/map-with-object-template.tmj | 104 +++++++++ .../TestData/Map/map-with-object-template.tmx | 0 .../TestData/Map/simple-tileset-embed.cs | 5 +- .../TestData/Map/simple-tileset-embed.tmj | 45 ++++ .../TestData/Map/simple-tileset-embed.tmx | 0 .../Template/map-with-object-template.tj | 28 +++ .../Template/map-with-object-template.tx | 0 .../Serialization/Tmj/TmjMapReaderTests.cs | 113 +++++----- .../Serialization/Tmx/TmxMapReaderTests.cs | 28 +-- DotTiled/Model/Tileset/Tileset.cs | 4 +- .../Tmj/ExtensionsJsonElement.cs | 15 +- DotTiled/Serialization/Tmj/Tmj.Data.cs | 132 ++++++++++++ DotTiled/Serialization/Tmj/Tmj.Layer.cs | 88 ++++++++ DotTiled/Serialization/Tmj/Tmj.Map.cs | 186 +---------------- DotTiled/Serialization/Tmj/Tmj.Properties.cs | 53 +++++ DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 197 ++++++++++++++++++ DotTiled/Serialization/Tmj/TmjMapReader.cs | 10 +- DotTiled/Serialization/Tmx/Tmx.Tileset.cs | 23 +- Makefile | 9 + 38 files changed, 1072 insertions(+), 269 deletions(-) rename DotTiled.Tests/Serialization/{Tmx/TestData => }/TestData.cs (75%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-base64-gzip.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-base64-zlib.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-base64.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-csv.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-properties.cs (95%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map-properties.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/empty-map.cs (89%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-group.cs (97%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-group.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-object-template.cs (97%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/map-with-object-template.tmx (100%) rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/simple-tileset-embed.cs (92%) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Map/simple-tileset-embed.tmx (100%) create mode 100644 DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj rename DotTiled.Tests/Serialization/{Tmx => }/TestData/Template/map-with-object-template.tx (100%) create mode 100644 DotTiled/Serialization/Tmj/Tmj.Data.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Layer.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Properties.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Tileset.cs diff --git a/DotTiled.Tests/Assert/AssertMap.cs b/DotTiled.Tests/Assert/AssertMap.cs index 167d0ad..e831063 100644 --- a/DotTiled.Tests/Assert/AssertMap.cs +++ b/DotTiled.Tests/Assert/AssertMap.cs @@ -30,11 +30,11 @@ public static partial class DotTiledAssert 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]); + AssertTileset(expected.Tilesets[i], actual.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]); + AssertLayer(expected.Layers[i], actual.Layers[i]); } } diff --git a/DotTiled.Tests/DotTiled.Tests.csproj b/DotTiled.Tests/DotTiled.Tests.csproj index 5e36559..faa22d4 100644 --- a/DotTiled.Tests/DotTiled.Tests.csproj +++ b/DotTiled.Tests/DotTiled.Tests.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs similarity index 75% rename from DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs rename to DotTiled.Tests/Serialization/TestData.cs index 667fec0..d31956f 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -2,12 +2,12 @@ using System.Xml; namespace DotTiled.Tests; -public static class TmxMapReaderTestData +public static partial class TestData { public static XmlReader GetXmlReaderFor(string testDataFile) { var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; - using var stream = typeof(TmxMapReaderTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + using var stream = typeof(TestData).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 TmxMapReaderTestData public static string GetRawStringFor(string testDataFile) { var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; - using var stream = typeof(TmxMapReaderTestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) + using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); using var stringReader = new StreamReader(stream); diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj new file mode 100644 index 0000000..de94421 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj @@ -0,0 +1,30 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "compression":"gzip", + "data":"H4sIAAAAAAAACmNgoD0AAMrGiJlkAAAA", + "encoding":"base64", + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-gzip.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-gzip.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj new file mode 100644 index 0000000..4cf9e84 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj @@ -0,0 +1,30 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "compression":"zlib", + "data":"eJxjYKA9AAAAZAAB", + "encoding":"base64", + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-zlib.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64-zlib.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj new file mode 100644 index 0000000..b13707c --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj @@ -0,0 +1,30 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "compression":"", + "data":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "encoding":"base64", + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-base64.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj new file mode 100644 index 0000000..896df6c --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj @@ -0,0 +1,32 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-csv.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-csv.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs similarity index 95% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs index 795b920..79df5a5 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxMapReaderTests +public partial class TestData { - private static Map EmptyMapWithProperties() => new Map + public static Map EmptyMapWithProperties() => new Map { Version = "1.10", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj new file mode 100644 index 0000000..237546d --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj @@ -0,0 +1,69 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "properties":[ + { + "name":"MapBool", + "type":"bool", + "value":true + }, + { + "name":"MapColor", + "type":"color", + "value":"#ffff0000" + }, + { + "name":"MapFile", + "type":"file", + "value":"file.png" + }, + { + "name":"MapFloat", + "type":"float", + "value":5.2 + }, + { + "name":"MapInt", + "type":"int", + "value":42 + }, + + { + "name":"MapObject", + "type":"object", + "value":5 + }, + { + "name":"MapString", + "type":"string", + "value":"string in map" + }], + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map-properties.tmx rename to DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs b/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs similarity index 89% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs rename to DotTiled.Tests/Serialization/TestData/Map/empty-map.cs index 12cfc00..b51aeba 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/empty-map.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxMapReaderTests +public partial class TestData { - private static Map EmptyMapWithEncodingAndCompression(DataEncoding dataEncoding, DataCompression? compression) => new Map + public static Map EmptyMapWithEncodingAndCompression(DataEncoding dataEncoding, DataCompression? compression) => new Map { Version = "1.10", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs similarity index 97% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs rename to DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs index 515312e..14a2c8c 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxMapReaderTests +public partial class TestData { - private static Map MapWithGroup() => new Map + public static Map MapWithGroup() => new Map { Version = "1.10", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj new file mode 100644 index 0000000..6bce8c8 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj @@ -0,0 +1,80 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":4, + "name":"Tile Layer 2", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }, + { + "id":3, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"Object Layer 1", + "objects":[ + { + "height":64.5, + "id":1, + "name":"Name", + "rotation":0, + "type":"", + "visible":true, + "width":64.5, + "x":35.5, + "y":26 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "name":"Group 1", + "opacity":1, + "type":"group", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":2, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-group.tmx rename to DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs similarity index 97% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs rename to DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs index 8f4459e..26ddde8 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxMapReaderTests +public partial class TestData { - private static Map MapWithObjectTemplate() => new Map + public static Map MapWithObjectTemplate() => new Map { Version = "1.10", TiledVersion = "1.11.0", diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj new file mode 100644 index 0000000..398403b --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj @@ -0,0 +1,104 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"Object Layer 1", + "objects":[ + { + "height":37.0156, + "id":1, + "template":"map-with-object-template.tj", + "name":"Thingy 2", + "properties":[ + { + "name":"Bool", + "type":"bool", + "value":true + }, + { + "name":"TestClassInTemplate", + "propertytype":"TestClass", + "type":"class", + "value": + { + "Amount":37, + "Name":"I am here" + } + }], + "rotation":0, + "type":"", + "visible":true, + "width":37.0156, + "x":94.5749, + "y":33.6842 + }, + { + "id":2, + "template":"map-with-object-template.tj", + "x":29.7976, + "y":33.8693 + }, + { + "height":37.0156, + "id":3, + "template":"map-with-object-template.tj", + "name":"Thingy 3", + "properties":[ + { + "name":"Bool", + "type":"bool", + "value":true + }, + { + "name":"TestClassInTemplate", + "propertytype":"TestClass", + "type":"class", + "value": + { + "Name":"I am here 3" + } + }], + "rotation":0, + "type":"", + "visible":true, + "width":37.0156, + "x":5, + "y":5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":3, + "nextobjectid":3, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/map-with-object-template.tmx rename to DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs similarity index 92% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs rename to DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs index 7dfb740..d6a5f10 100644 --- a/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs @@ -1,8 +1,8 @@ namespace DotTiled.Tests; -public partial class TmxMapReaderTests +public partial class TestData { - private static Map SimpleMapWithEmbeddedTileset() => new Map + public static Map SimpleMapWithEmbeddedTileset() => new Map { Version = "1.10", TiledVersion = "1.11.0", @@ -26,6 +26,7 @@ public partial class TmxMapReaderTests Columns = 4, Image = new Image { + Format = ImageFormat.Png, Source = "tiles.png", Width = 128, Height = 64 diff --git a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj new file mode 100644 index 0000000..fa5a4ef --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj @@ -0,0 +1,45 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "columns":4, + "firstgid":1, + "image":"tiles.png", + "imageheight":64, + "imagewidth":128, + "margin":0, + "name":"Tileset 1", + "spacing":0, + "tilecount":8, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.tmx b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Map/simple-tileset-embed.tmx rename to DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj new file mode 100644 index 0000000..ec2b065 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj @@ -0,0 +1,28 @@ +{ "object": + { + "height":37.0156, + "id":2, + "name":"Thingy", + "properties":[ + { + "name":"Bool", + "type":"bool", + "value":true + }, + { + "name":"TestClassInTemplate", + "propertytype":"TestClass", + "type":"class", + "value": + { + "Amount":4.2, + "Name":"Hello there" + } + }], + "rotation":0, + "type":"", + "visible":true, + "width":37.0156 + }, + "type":"template" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/Tmx/TestData/Template/map-with-object-template.tx b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx similarity index 100% rename from DotTiled.Tests/Serialization/Tmx/TestData/Template/map-with-object-template.tx rename to DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 7e15bac..605cda9 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -2,64 +2,75 @@ namespace DotTiled.Tests; public partial class TmjMapReaderTests { - [Fact] - public void Test1() + public static IEnumerable DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data => + [ + ["Serialization.TestData.Map.empty-map-csv.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], + ["Serialization.TestData.Map.empty-map-base64.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], + ["Serialization.TestData.Map.empty-map-base64-gzip.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], + ["Serialization.TestData.Map.empty-map-base64-zlib.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], + ["Serialization.TestData.Map.simple-tileset-embed.tmj", TestData.SimpleMapWithEmbeddedTileset()], + ["Serialization.TestData.Map.empty-map-properties.tmj", TestData.EmptyMapWithProperties()], + ]; + + [Theory] + [MemberData(nameof(DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] + public void TmxMapReaderReadMap_ValidTmjNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange - var jsonString = - """ + var json = TestData.GetRawStringFor(testDataFile); + static Template ResolveTemplate(string source) { - "backgroundcolor":"#656667", - "height":4, - "nextobjectid":1, - "nextlayerid":1, - "orientation":"orthogonal", - "properties": [ - { - "name":"mapProperty1", - "type":"string", - "value":"one" - }, - { - "name":"mapProperty3", - "type":"string", - "value":"twoeee" - } - ], - "renderorder":"right-down", - "tileheight":32, - "tilewidth":32, - "version":"1", - "tiledversion":"1.0.3", - "width":4, - "tilesets": [ - { - "columns":19, - "firstgid":1, - "image":"image/fishbaddie_parts.png", - "imageheight":480, - "imagewidth":640, - "margin":3, - "name":"", - "properties":[ - { - "name":"myProperty1", - "type":"string", - "value":"myProperty1_value" - }], - "spacing":1, - "tilecount":266, - "tileheight":32, - "tilewidth":32 - } - ] + var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}"); + //var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate); + return null; } - """; + static Tileset ResolveTileset(string source) + { + var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); + //var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate); + return null; + } + using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate); // Act - using var tmjMapReader = new TmjMapReader(jsonString); + var map = mapReader.ReadMap(); // Assert - var map = tmjMapReader.ReadMap(); + Assert.NotNull(map); + DotTiledAssert.AssertMap(expectedMap, map); + } + + public static IEnumerable DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => + [ + ["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate()], + ["Serialization.TestData.Map.map-with-group.tmj", TestData.MapWithGroup()], + ]; + + [Theory] + [MemberData(nameof(DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] + public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) + { + // Arrange + var json = TestData.GetRawStringFor(testDataFile); + static Template ResolveTemplate(string source) + { + var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}"); + //var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate); + return null; + } + static Tileset ResolveTileset(string source) + { + var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); + //var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate); + return null; + } + using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate); + + // Act + var map = mapReader.ReadMap(); + + // Assert + Assert.NotNull(map); + DotTiledAssert.AssertMap(expectedMap, map); } } diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 1924ac2..7a1fc66 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -78,12 +78,12 @@ public partial class TmxMapReaderTests 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()], + ["Serialization.TestData.Map.empty-map-csv.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], + ["Serialization.TestData.Map.empty-map-base64.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], + ["Serialization.TestData.Map.empty-map-base64-gzip.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], + ["Serialization.TestData.Map.empty-map-base64-zlib.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], + ["Serialization.TestData.Map.simple-tileset-embed.tmx", TestData.SimpleMapWithEmbeddedTileset()], + ["Serialization.TestData.Map.empty-map-properties.tmx", TestData.EmptyMapWithProperties()], ]; [Theory] @@ -91,16 +91,16 @@ public partial class TmxMapReaderTests public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange - using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); + using var reader = TestData.GetXmlReaderFor(testDataFile); static Template ResolveTemplate(string source) { - using var xmlTemplateReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Template.{source}"); + using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.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 xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); return tilesetReader.ReadTileset(); } @@ -116,8 +116,8 @@ public partial class TmxMapReaderTests 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()], + ["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate()], + ["Serialization.TestData.Map.map-with-group.tmx", TestData.MapWithGroup()], ]; [Theory] @@ -125,16 +125,16 @@ public partial class TmxMapReaderTests public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange - using var reader = TmxMapReaderTestData.GetXmlReaderFor(testDataFile); + using var reader = TestData.GetXmlReaderFor(testDataFile); static Template ResolveTemplate(string source) { - using var xmlTemplateReader = TmxMapReaderTestData.GetXmlReaderFor($"Serialization.Tmx.TestData.Template.{source}"); + using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.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 xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); return tilesetReader.ReadTileset(); } diff --git a/DotTiled/Model/Tileset/Tileset.cs b/DotTiled/Model/Tileset/Tileset.cs index ac1da0d..7b1a982 100644 --- a/DotTiled/Model/Tileset/Tileset.cs +++ b/DotTiled/Model/Tileset/Tileset.cs @@ -39,8 +39,8 @@ public class Tileset public string Class { get; set; } = ""; public uint? TileWidth { get; set; } public uint? TileHeight { get; set; } - public float? Spacing { get; set; } - public float? Margin { get; set; } + public float? Spacing { get; set; } = 0f; + public float? Margin { get; set; } = 0f; public uint? TileCount { get; set; } public uint? Columns { get; set; } public ObjectAlignment ObjectAlignment { get; set; } = ObjectAlignment.Unspecified; diff --git a/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs b/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs index d7620e4..7462c56 100644 --- a/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs +++ b/DotTiled/Serialization/Tmj/ExtensionsJsonElement.cs @@ -20,21 +20,32 @@ internal static class ExtensionsJsonElement if (!element.TryGetProperty(propertyName, out var property)) return defaultValue; + if (property.ValueKind == JsonValueKind.Null) + return defaultValue; + return property.GetValueAs(); } internal static T GetValueAs(this JsonElement element) { - string val = typeof(T) switch + bool isNullable = Nullable.GetUnderlyingType(typeof(T)) != null; + + if (isNullable && element.ValueKind == JsonValueKind.Null) + return default!; + + var realType = isNullable ? Nullable.GetUnderlyingType(typeof(T))! : typeof(T); + + string val = realType switch { Type t when t == typeof(string) => element.GetString()!, Type t when t == typeof(int) => element.GetInt32().ToString(CultureInfo.InvariantCulture), Type t when t == typeof(uint) => element.GetUInt32().ToString(CultureInfo.InvariantCulture), Type t when t == typeof(float) => element.GetSingle().ToString(CultureInfo.InvariantCulture), + Type t when t == typeof(bool) => element.GetBoolean().ToString(CultureInfo.InvariantCulture), _ => throw new JsonException($"Unsupported type '{typeof(T)}'.") }; - return (T)Convert.ChangeType(val, typeof(T), CultureInfo.InvariantCulture); + return (T)Convert.ChangeType(val, realType, CultureInfo.InvariantCulture); } internal static T GetRequiredPropertyParseable(this JsonElement element, string propertyName) where T : IParsable diff --git a/DotTiled/Serialization/Tmj/Tmj.Data.cs b/DotTiled/Serialization/Tmj/Tmj.Data.cs new file mode 100644 index 0000000..27cd2bc --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Data.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Data ReadDataAsChunks(JsonElement element, DataCompression? compression, DataEncoding encoding) + { + var chunks = element.GetValueAsList(e => ReadChunk(e, compression, encoding)).ToArray(); + return new Data + { + Chunks = chunks, + Compression = compression, + Encoding = encoding, + FlippingFlags = null, + GlobalTileIDs = null + }; + } + + internal static Chunk ReadChunk(JsonElement element, DataCompression? compression, DataEncoding encoding) + { + var data = ReadDataWithoutChunks(element, compression, encoding); + + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("y"); + var width = element.GetRequiredProperty("width"); + var height = element.GetRequiredProperty("height"); + + return new Chunk + { + X = x, + Y = y, + Width = width, + Height = height, + GlobalTileIDs = data.GlobalTileIDs!, + FlippingFlags = data.FlippingFlags! + }; + } + + internal static Data ReadDataWithoutChunks(JsonElement element, DataCompression? compression, DataEncoding encoding) + { + if (encoding == DataEncoding.Csv) + { + // Array of uint + var data = element.GetValueAsList(e => e.GetValueAs()).ToArray(); + var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data); + return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; + } + else if (encoding == DataEncoding.Base64) + { + var base64Data = element.GetBytesFromBase64(); + + if (compression == null) + { + var data = ReadBytesAsInt32Array(base64Data); + var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data); + return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; + } + + using var stream = new MemoryStream(base64Data); + var decompressed = compression switch + { + DataCompression.GZip => DecompressGZip(stream), + DataCompression.ZLib => DecompressZLib(stream), + _ => throw new JsonException($"Unsupported compression '{compression}'.") + }; + + { + var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(decompressed); + return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; + } + } + + throw new JsonException($"Unsupported encoding '{encoding}'."); + } + + 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); + } + + internal static uint[] ReadBytesAsInt32Array(byte[] bytes) + { + var intArray = new uint[bytes.Length / 4]; + for (var i = 0; i < intArray.Length; i++) + { + intArray[i] = BitConverter.ToUInt32(bytes, i * 4); + } + + return intArray; + } + + 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); + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Layer.cs b/DotTiled/Serialization/Tmj/Tmj.Layer.cs new file mode 100644 index 0000000..7e8685e --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Layer.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static BaseLayer ReadLayer(JsonElement element) + { + var type = element.GetRequiredProperty("type"); + + return type switch + { + "tilelayer" => ReadTileLayer(element), + // "objectgroup" => ReadObjectGroup(element), + // "imagelayer" => ReadImageLayer(element), + // "group" => ReadGroup(element), + _ => throw new JsonException($"Unsupported layer type '{type}'.") + }; + } + + internal static TileLayer ReadTileLayer(JsonElement element) + { + var compression = element.GetOptionalPropertyParseable("compression", s => s switch + { + "zlib" => DataCompression.ZLib, + "gzip" => DataCompression.GZip, + "" => null, + _ => throw new JsonException($"Unsupported compression '{s}'.") + }, null); + var encoding = element.GetOptionalPropertyParseable("encoding", s => s switch + { + "csv" => DataEncoding.Csv, + "base64" => DataEncoding.Base64, + _ => throw new JsonException($"Unsupported encoding '{s}'.") + }, DataEncoding.Csv); + var chunks = element.GetOptionalPropertyCustom("chunks", e => ReadDataAsChunks(e, compression, encoding), null); + var @class = element.GetOptionalProperty("class", ""); + var data = element.GetOptionalPropertyCustom("data", e => ReadDataWithoutChunks(e, compression, encoding), null); + var height = element.GetRequiredProperty("height"); + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e), null); + var repeatX = element.GetOptionalProperty("repeatx", false); + var repeatY = element.GetOptionalProperty("repeaty", false); + var startX = element.GetOptionalProperty("startx", 0); + var startY = element.GetOptionalProperty("starty", 0); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var visible = element.GetOptionalProperty("visible", true); + var width = element.GetRequiredProperty("width"); + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("y"); + + if ((data ?? chunks) is null) + throw new JsonException("Tile layer does not contain data."); + + return new TileLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxx, + ParallaxY = parallaxy, + Properties = properties, + X = x, + Y = y, + Width = width, + Height = height, + Data = data ?? chunks + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Map.cs b/DotTiled/Serialization/Tmj/Tmj.Map.cs index d4ea6f1..0906b70 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Map.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -1,13 +1,15 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Text.Json; namespace DotTiled; internal partial class Tmj { - internal static Map ReadMap(JsonElement element) + internal static Map ReadMap(JsonElement element, Func? externalTilesetResolver, Func externalTemplateResolver) { var version = element.GetRequiredProperty("version"); var tiledVersion = element.GetRequiredProperty("tiledversion"); @@ -51,14 +53,12 @@ internal partial class Tmj var backgroundColor = element.GetOptionalPropertyParseable("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); var nextLayerID = element.GetRequiredProperty("nextlayerid"); var nextObjectID = element.GetRequiredProperty("nextobjectid"); - var infinite = element.GetOptionalProperty("infinite", 0) == 1; + var infinite = element.GetOptionalProperty("infinite", false); - // At most one of - Dictionary? properties = element.GetOptionalPropertyCustom>("properties", ReadProperties, null); + var properties = element.GetOptionalPropertyCustom?>("properties", ReadProperties, null); - // Any number of - List layers = []; - List tilesets = []; + List layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(ReadLayer), []); + List tilesets = element.GetOptionalPropertyCustom>("tilesets", e => e.GetValueAsList(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver)), []); return new Map { @@ -86,176 +86,4 @@ internal partial class Tmj Layers = layers }; } - - internal static Dictionary ReadProperties(JsonElement element) - { - var properties = new Dictionary(); - - element.GetValueAsList(e => - { - var name = e.GetRequiredProperty("name"); - var type = e.GetOptionalPropertyParseable("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 JsonException("Invalid property type") - }, PropertyType.String); - - IProperty property = type switch - { - PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, - PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Class => ReadClassProperty(e), - _ => throw new JsonException("Invalid property type") - }; - - return property!; - }).ForEach(p => properties.Add(p.Name, p)); - - return properties; - } - - internal static ClassProperty ReadClassProperty(JsonElement element) - { - var name = element.GetRequiredProperty("name"); - var propertyType = element.GetRequiredProperty("propertytype"); - - var properties = element.GetRequiredPropertyCustom>("properties", ReadProperties); - - return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; - } - - // internal static List ReadTilesets(ref Utf8JsonReader reader) - // { - // var tilesets = new List(); - - // reader.ProcessJsonArray((ref Utf8JsonReader reader) => - // { - // var tileset = ReadTileset(ref reader); - // tilesets.Add(tileset); - // }); - - // return tilesets; - // } - - // internal static Tileset ReadTileset(ref Utf8JsonReader reader) - // { - // string? version = null; - // string? tiledVersion = null; - // uint? firstGID = null; - // string? source = null; - // string? name = null; - // string @class = ""; - // uint? tileWidth = null; - // uint? tileHeight = null; - // uint? spacing = null; - // uint? margin = null; - // uint? tileCount = null; - // uint? columns = null; - // ObjectAlignment objectAlignment = ObjectAlignment.Unspecified; - // FillMode fillMode = FillMode.Stretch; - - // string? image = null; - // uint? imageWidth = null; - // uint? imageHeight = null; - - // Dictionary? properties = null; - - // reader.ProcessJsonObject([ - // new OptionalProperty("version", (ref Utf8JsonReader reader) => version = reader.Progress(reader.GetString())), - // new OptionalProperty("tiledversion", (ref Utf8JsonReader reader) => tiledVersion = reader.Progress(reader.GetString())), - // new OptionalProperty("firstgid", (ref Utf8JsonReader reader) => firstGID = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("source", (ref Utf8JsonReader reader) => source = reader.Progress(reader.GetString())), - // new OptionalProperty("name", (ref Utf8JsonReader reader) => name = reader.Progress(reader.GetString())), - // new OptionalProperty("class", (ref Utf8JsonReader reader) => @class = reader.Progress(reader.GetString() ?? ""), allowNull: true), - // new OptionalProperty("tilewidth", (ref Utf8JsonReader reader) => tileWidth = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("tileheight", (ref Utf8JsonReader reader) => tileHeight = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("spacing", (ref Utf8JsonReader reader) => spacing = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("margin", (ref Utf8JsonReader reader) => margin = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("tilecount", (ref Utf8JsonReader reader) => tileCount = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("columns", (ref Utf8JsonReader reader) => columns = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("objectalignment", (ref Utf8JsonReader reader) => objectAlignment = reader.Progress(reader.GetString()) 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 JsonException("Invalid object alignment.") - // }), - // new OptionalProperty("fillmode", (ref Utf8JsonReader reader) => fillMode = reader.Progress(reader.GetString()) switch - // { - // "stretch" => FillMode.Stretch, - // "preserve-aspect-fit" => FillMode.PreserveAspectFit, - // _ => throw new JsonException("Invalid fill mode.") - // }), - - // new OptionalProperty("image", (ref Utf8JsonReader reader) => image = reader.Progress(reader.GetString())), - // new OptionalProperty("imagewidth", (ref Utf8JsonReader reader) => imageWidth = reader.Progress(reader.GetUInt32())), - // new OptionalProperty("imageheight", (ref Utf8JsonReader reader) => imageHeight = reader.Progress(reader.GetUInt32())), - - // new OptionalProperty("properties", (ref Utf8JsonReader reader) => properties = ReadProperties(ref reader)) - // ], "tileset"); - - // Image? imageInstance = image is not null ? new Image - // { - // Format = ParseImageFormatFromSource(image), - // Width = imageWidth, - // Height = imageHeight, - // Source = image - // } : null; - - // 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, - // FillMode = fillMode, - // Image = imageInstance, - // Properties = properties - // }; - // } - - // private static ImageFormat ParseImageFormatFromSource(string? source) - // { - // if (source is null) - // throw new JsonException("Image source is required to determine image format."); - - // var extension = Path.GetExtension(source); - // return extension switch - // { - // ".png" => ImageFormat.Png, - // ".jpg" => ImageFormat.Jpg, - // ".jpeg" => ImageFormat.Jpg, - // ".gif" => ImageFormat.Gif, - // ".bmp" => ImageFormat.Bmp, - // _ => throw new JsonException("Invalid image format.") - // }; - // } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Properties.cs b/DotTiled/Serialization/Tmj/Tmj.Properties.cs new file mode 100644 index 0000000..37b7dd1 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Properties.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Dictionary ReadProperties(JsonElement element) => + element.GetValueAsList(e => + { + var name = e.GetRequiredProperty("name"); + var type = e.GetOptionalPropertyParseable("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 JsonException("Invalid property type") + }, PropertyType.String); + + IProperty property = type switch + { + PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, + PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Class => ReadClassProperty(e), + _ => throw new JsonException("Invalid property type") + }; + + return property!; + }).ToDictionary(p => p.Name); + + internal static ClassProperty ReadClassProperty(JsonElement element) + { + var name = element.GetRequiredProperty("name"); + var propertyType = element.GetRequiredProperty("propertytype"); + + var properties = element.GetRequiredPropertyCustom>("properties", ReadProperties); + + return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs new file mode 100644 index 0000000..66d42b1 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Tileset ReadTileset(JsonElement element, Func? externalTilesetResolver, Func externalTemplateResolver) + { + var backgroundColor = element.GetOptionalPropertyParseable("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var @class = element.GetOptionalProperty("class", ""); + var columns = element.GetOptionalProperty("columns", null); + var fillMode = element.GetOptionalPropertyParseable("fillmode", s => s switch + { + "stretch" => FillMode.Stretch, + "preserve-aspect-fit" => FillMode.PreserveAspectFit, + _ => throw new JsonException($"Unknown fill mode '{s}'") + }, FillMode.Stretch); + var firstGID = element.GetOptionalProperty("firstgid", null); + var grid = element.GetOptionalPropertyCustom("grid", ReadGrid, null); + var image = element.GetOptionalProperty("image", null); + var imageHeight = element.GetOptionalProperty("imageheight", null); + var imageWidth = element.GetOptionalProperty("imagewidth", null); + var margin = element.GetOptionalProperty("margin", null); + var name = element.GetOptionalProperty("name", null); + var objectAlignment = element.GetOptionalPropertyParseable("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 JsonException($"Unknown object alignment '{s}'") + }, ObjectAlignment.Unspecified); + var properties = element.GetOptionalPropertyCustom?>("properties", ReadProperties, null); + var source = element.GetOptionalProperty("source", null); + var spacing = element.GetOptionalProperty("spacing", null); + var tileCount = element.GetOptionalProperty("tilecount", null); + var tiledVersion = element.GetOptionalProperty("tiledversion", null); + var tileHeight = element.GetOptionalProperty("tileheight", null); + var tileOffset = element.GetOptionalPropertyCustom("tileoffset", ReadTileOffset, null); + var tileRenderSize = element.GetOptionalPropertyParseable("tilerendersize", s => s switch + { + "tile" => TileRenderSize.Tile, + "grid" => TileRenderSize.Grid, + _ => throw new JsonException($"Unknown tile render size '{s}'") + }, TileRenderSize.Tile); + var tiles = element.GetOptionalPropertyCustom>("tiles", ReadTiles, []); + var tileWidth = element.GetOptionalProperty("tilewidth", null); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var type = element.GetOptionalProperty("type", null); + var version = element.GetOptionalProperty("version", null); + //var wangsets = element.GetOptionalPropertyCustom?>("wangsets", ReadWangSets, null); + + if (source is not null) + { + if (externalTilesetResolver is null) + throw new JsonException("External tileset resolver is required to resolve external tilesets."); + + var resolvedTileset = externalTilesetResolver(source); + resolvedTileset.FirstGID = firstGID; + resolvedTileset.Source = source; + return resolvedTileset; + } + + var imageModel = new Image + { + Format = ParseImageFormatFromSource(image!), + Source = image, + Height = imageHeight, + Width = imageWidth, + TransparentColor = transparentColor + }; + + return new Tileset + { + Class = @class, + Columns = columns, + FillMode = fillMode, + FirstGID = firstGID, + Grid = grid, + Image = imageModel, + Margin = margin, + Name = name, + ObjectAlignment = objectAlignment, + Properties = properties, + Source = source, + Spacing = spacing, + TileCount = tileCount, + TiledVersion = tiledVersion, + TileHeight = tileHeight, + TileOffset = tileOffset, + RenderSize = tileRenderSize, + Tiles = tiles, + TileWidth = tileWidth, + Version = version, + //Wangsets = wangsets + }; + } + + private static ImageFormat ParseImageFormatFromSource(string source) + { + var extension = Path.GetExtension(source).ToLowerInvariant(); + return extension switch + { + ".png" => ImageFormat.Png, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".jpeg" => ImageFormat.Jpg, + ".bmp" => ImageFormat.Bmp, + _ => throw new JsonException($"Unsupported image format '{extension}'") + }; + } + + internal static Grid ReadGrid(JsonElement element) + { + var orientation = element.GetOptionalPropertyParseable("orientation", s => s switch + { + "orthogonal" => GridOrientation.Orthogonal, + "isometric" => GridOrientation.Isometric, + _ => throw new JsonException($"Unknown grid orientation '{s}'") + }, GridOrientation.Orthogonal); + var height = element.GetRequiredProperty("height"); + var width = element.GetRequiredProperty("width"); + + return new Grid + { + Orientation = orientation, + Height = height, + Width = width + }; + } + + internal static TileOffset ReadTileOffset(JsonElement element) + { + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("y"); + + return new TileOffset + { + X = x, + Y = y + }; + } + + internal static List ReadTiles(JsonElement element) => + element.GetValueAsList(e => + { + //var animation = e.GetOptionalPropertyCustom>("animation", ReadFrames, null); + var id = e.GetRequiredProperty("id"); + var image = e.GetOptionalProperty("image", null); + var imageHeight = e.GetOptionalProperty("imageheight", null); + var imageWidth = e.GetOptionalProperty("imagewidth", null); + var x = e.GetOptionalProperty("x", 0); + var y = e.GetOptionalProperty("y", 0); + var width = e.GetOptionalProperty("width", imageWidth ?? 0); + var height = e.GetOptionalProperty("height", imageHeight ?? 0); + //var objectGroup = e.GetOptionalPropertyCustom("objectgroup", ReadObjectLayer, null); + var probability = e.GetOptionalProperty("probability", 1.0f); + var properties = e.GetOptionalPropertyCustom?>("properties", ReadProperties, null); + // var terrain, replaced by wangsets + var type = e.GetOptionalProperty("type", ""); + + var imageModel = image != null ? new Image + { + Format = ParseImageFormatFromSource(image), + Source = image, + Height = imageHeight ?? 0, + Width = imageWidth ?? 0 + } : null; + + return new Tile + { + //Animation = animation, + ID = id, + Image = imageModel, + X = x, + Y = y, + Width = width, + Height = height, + //ObjectLayer = objectGroup, + Probability = probability, + Properties = properties, + Type = type + }; + }); +} diff --git a/DotTiled/Serialization/Tmj/TmjMapReader.cs b/DotTiled/Serialization/Tmj/TmjMapReader.cs index 4a113b1..9aa9675 100644 --- a/DotTiled/Serialization/Tmj/TmjMapReader.cs +++ b/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -7,19 +7,25 @@ namespace DotTiled; public class TmjMapReader : IMapReader { + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + private string _jsonString; private bool disposedValue; - public TmjMapReader(string jsonString) + public TmjMapReader(string jsonString, Func externalTilesetResolver, Func externalTemplateResolver) { _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); + _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); } public Map ReadMap() { var jsonDoc = JsonDocument.Parse(_jsonString); var rootElement = jsonDoc.RootElement; - return Tmj.ReadMap(rootElement); + return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver); } protected virtual void Dispose(bool disposing) diff --git a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs index 1996bc2..3c2a3a8 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Xml; @@ -18,8 +19,8 @@ internal partial class Tmx 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 spacing = reader.GetOptionalAttributeParseable("spacing") ?? 0; + var margin = reader.GetOptionalAttributeParseable("margin") ?? 0; var tileCount = reader.GetOptionalAttributeParseable("tilecount"); var columns = reader.GetOptionalAttributeParseable("columns"); var objectAlignment = reader.GetOptionalAttributeEnum("objectalignment", s => s switch @@ -131,6 +132,9 @@ internal partial class Tmx _ => r.Skip }); + if (format is null && source is not null) + format = ParseImageFormatFromSource(source); + return new Image { Format = format, @@ -141,6 +145,21 @@ internal partial class Tmx }; } + + private static ImageFormat ParseImageFormatFromSource(string source) + { + var extension = Path.GetExtension(source).ToLowerInvariant(); + return extension switch + { + ".png" => ImageFormat.Png, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".jpeg" => ImageFormat.Jpg, + ".bmp" => ImageFormat.Bmp, + _ => throw new XmlException($"Unsupported image format '{extension}'") + }; + } + internal static TileOffset ReadTileOffset(XmlReader reader) { // Attributes diff --git a/Makefile b/Makefile index e69de29..148b225 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,9 @@ +test: + dotnet test + +.PHONY: benchmark +benchmark: DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md + +BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs +DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES) + dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release \ No newline at end of file From 5e93716d2c9cdb2147f81ada57b20ddd76155615 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 10 Aug 2024 19:18:33 +0200 Subject: [PATCH 20/39] Big properties work --- DotTiled.Tests/Assert/AssertObject.cs | 2 +- DotTiled.Tests/Assert/AssertProperties.cs | 20 +- .../TestData/Map/map-with-object-template.cs | 10 +- ...ap-with-object-template-propertytypes.json | 24 ++ .../TestData/propertytypes-2.json | 103 +++++++ .../Serialization/Tmj/TmjMapReaderTests.cs | 63 +++- .../Serialization/Tmx/TmxMapReaderTests.cs | 2 +- DotTiled/Model/IProperty.cs | 95 ++++++ .../Serialization/Tmj/TjTemplateReader.cs | 64 ++++ DotTiled/Serialization/Tmj/Tmj.Layer.cs | 290 +++++++++++++++++- DotTiled/Serialization/Tmj/Tmj.Map.cs | 12 +- DotTiled/Serialization/Tmj/Tmj.Properties.cs | 97 +++++- DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 16 +- DotTiled/Serialization/Tmj/TmjMapReader.cs | 12 +- .../Serialization/Tmj/TsjTilesetReader.cs | 65 ++++ 15 files changed, 820 insertions(+), 55 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/map-with-object-template-propertytypes.json create mode 100644 DotTiled.Tests/Serialization/TestData/propertytypes-2.json create mode 100644 DotTiled/Serialization/Tmj/TjTemplateReader.cs create mode 100644 DotTiled/Serialization/Tmj/TsjTilesetReader.cs diff --git a/DotTiled.Tests/Assert/AssertObject.cs b/DotTiled.Tests/Assert/AssertObject.cs index 68d74eb..3b08744 100644 --- a/DotTiled.Tests/Assert/AssertObject.cs +++ b/DotTiled.Tests/Assert/AssertObject.cs @@ -17,7 +17,7 @@ public static partial class DotTiledAssert Assert.Equal(expected.Visible, actual.Visible); Assert.Equal(expected.Template, actual.Template); - AssertProperties(actual.Properties, expected.Properties); + AssertProperties(expected.Properties, actual.Properties); AssertObject((dynamic)expected, (dynamic)actual); } diff --git a/DotTiled.Tests/Assert/AssertProperties.cs b/DotTiled.Tests/Assert/AssertProperties.cs index d0a8269..afd28c2 100644 --- a/DotTiled.Tests/Assert/AssertProperties.cs +++ b/DotTiled.Tests/Assert/AssertProperties.cs @@ -19,51 +19,51 @@ public static partial class DotTiledAssert } } - private static void AssertProperty(IProperty actual, IProperty expected) + private static void AssertProperty(IProperty expected, IProperty actual) { Assert.Equal(expected.Type, actual.Type); Assert.Equal(expected.Name, actual.Name); AssertProperties((dynamic)actual, (dynamic)expected); } - private static void AssertProperty(StringProperty actual, StringProperty expected) + private static void AssertProperty(StringProperty expected, StringProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(IntProperty actual, IntProperty expected) + private static void AssertProperty(IntProperty expected, IntProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(FloatProperty actual, FloatProperty expected) + private static void AssertProperty(FloatProperty expected, FloatProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(BoolProperty actual, BoolProperty expected) + private static void AssertProperty(BoolProperty expected, BoolProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(ColorProperty actual, ColorProperty expected) + private static void AssertProperty(ColorProperty expected, ColorProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(FileProperty actual, FileProperty expected) + private static void AssertProperty(FileProperty expected, FileProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(ObjectProperty actual, ObjectProperty expected) + private static void AssertProperty(ObjectProperty expected, ObjectProperty actual) { Assert.Equal(expected.Value, actual.Value); } - private static void AssertProperty(ClassProperty actual, ClassProperty expected) + private static void AssertProperty(ClassProperty expected, ClassProperty actual) { Assert.Equal(expected.PropertyType, actual.PropertyType); - AssertProperties(actual.Properties, expected.Properties); + AssertProperties(expected.Properties, actual.Properties); } } diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs index 26ddde8..1f9cb1c 100644 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs @@ -2,7 +2,7 @@ namespace DotTiled.Tests; public partial class TestData { - public static Map MapWithObjectTemplate() => new Map + public static Map MapWithObjectTemplate(string templateExtension) => new Map { Version = "1.10", TiledVersion = "1.11.0", @@ -49,7 +49,7 @@ public partial class TestData new RectangleObject { ID = 1, - Template = "map-with-object-template.tx", + Template = $"map-with-object-template.{templateExtension}", Name = "Thingy 2", X = 94.5749f, Y = 33.6842f, @@ -73,7 +73,7 @@ public partial class TestData new RectangleObject { ID = 2, - Template = "map-with-object-template.tx", + Template = $"map-with-object-template.{templateExtension}", Name = "Thingy", X = 29.7976f, Y = 33.8693f, @@ -97,7 +97,7 @@ public partial class TestData new RectangleObject { ID = 3, - Template = "map-with-object-template.tx", + Template = $"map-with-object-template.{templateExtension}", Name = "Thingy 3", X = 5, Y = 5, @@ -112,7 +112,7 @@ public partial class TestData PropertyType = "TestClass", Properties = new Dictionary { - ["Amount"] = new FloatProperty { Name = "Amount", Value = 4.2f }, + ["Amount"] = new FloatProperty { Name = "Amount", Value = 0.0f }, ["Name"] = new StringProperty { Name = "Name", Value = "I am here 3" } } } diff --git a/DotTiled.Tests/Serialization/TestData/map-with-object-template-propertytypes.json b/DotTiled.Tests/Serialization/TestData/map-with-object-template-propertytypes.json new file mode 100644 index 0000000..3505dce --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/map-with-object-template-propertytypes.json @@ -0,0 +1,24 @@ +[ + { + "color": "#ffa0a0a4", + "drawFill": true, + "id": 1, + "members": [ + { + "name": "Amount", + "type": "float", + "value": 0 + }, + { + "name": "Name", + "type": "string", + "value": "" + } + ], + "name": "TestClass", + "type": "class", + "useAs": [ + "property" + ] + } +] diff --git a/DotTiled.Tests/Serialization/TestData/propertytypes-2.json b/DotTiled.Tests/Serialization/TestData/propertytypes-2.json new file mode 100644 index 0000000..e21cf83 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/propertytypes-2.json @@ -0,0 +1,103 @@ +[ + { + "id": 4, + "name": "Enum0String", + "storageType": "string", + "type": "enum", + "values": [ + "Enum0_1", + "Enum0_2", + "Enum0_3" + ], + "valuesAsFlags": false + }, + { + "id": 5, + "name": "Enum1Num", + "storageType": "int", + "type": "enum", + "values": [ + "Enum1Num_1", + "Enum1Num_2", + "Enum1Num_3", + "Enum1Num_4" + ], + "valuesAsFlags": false + }, + { + "id": 6, + "name": "Enum2StringFlags", + "storageType": "string", + "type": "enum", + "values": [ + "Enum2StringFlags_1", + "Enum2StringFlags_2", + "Enum2StringFlags_3", + "Enum2StringFlags_4" + ], + "valuesAsFlags": true + }, + { + "id": 7, + "name": "Enum3NumFlags", + "storageType": "int", + "type": "enum", + "values": [ + "Enum3NumFlags_1", + "Enum3NumFlags_2", + "Enum3NumFlags_3", + "Enum3NumFlags_4", + "Enum3NumFlags_5" + ], + "valuesAsFlags": true + }, + { + "color": "#ffa0a0a4", + "drawFill": true, + "id": 2, + "members": [ + { + "name": "Yep", + "propertyType": "TestClass", + "type": "class", + "value": { + } + } + ], + "name": "Test", + "type": "class", + "useAs": [ + "property", + "map", + "layer", + "object", + "tile", + "tileset", + "wangcolor", + "wangset", + "project" + ] + }, + { + "color": "#ffa0a0a4", + "drawFill": true, + "id": 1, + "members": [ + { + "name": "Amount", + "type": "float", + "value": 0 + }, + { + "name": "Name", + "type": "string", + "value": "" + } + ], + "name": "TestClass", + "type": "class", + "useAs": [ + "property" + ] + } +] diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 605cda9..7e220a9 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -20,17 +20,13 @@ public partial class TmjMapReaderTests var json = TestData.GetRawStringFor(testDataFile); static Template ResolveTemplate(string source) { - var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}"); - //var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate); - return null; + throw new NotSupportedException("External templates are not supported in this test."); } static Tileset ResolveTileset(string source) { - var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); - //var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate); - return null; + throw new NotSupportedException("External tilesets are not supported in this test."); } - using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate); + using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, []); // Act var map = mapReader.ReadMap(); @@ -42,7 +38,7 @@ public partial class TmjMapReaderTests public static IEnumerable DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => [ - ["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate()], + ["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate("tj")], ["Serialization.TestData.Map.map-with-group.tmj", TestData.MapWithGroup()], ]; @@ -51,20 +47,55 @@ public partial class TmjMapReaderTests public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange + CustomTypeDefinition[] customTypeDefinitions = [ + new CustomClassDefinition + { + Name = "TestClass", + ID = 1, + UseAs = CustomClassUseAs.Property, + Members = [ + new StringProperty + { + Name = "Name", + Value = "" + }, + new FloatProperty + { + Name = "Amount", + Value = 0f + } + ] + }, + new CustomClassDefinition + { + Name = "Test", + ID = 2, + UseAs = CustomClassUseAs.All, + Members = [ + new ClassProperty + { + Name = "Yep", + PropertyType = "TestClass", + Properties = [] + } + ] + } + ]; + var json = TestData.GetRawStringFor(testDataFile); - static Template ResolveTemplate(string source) + Template ResolveTemplate(string source) { var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}"); - //var templateReader = new TmjTemplateReader(templateJson, ResolveTemplate); - return null; + using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, customTypeDefinitions); + return templateReader.ReadTemplate(); } - static Tileset ResolveTileset(string source) + Tileset ResolveTileset(string source) { - var tilesetJson = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); - //var tilesetReader = new TmjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate); - return null; + var tilesetJson = TestData.GetRawStringFor($"Serialization.TestData.Tileset.{source}"); + using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTemplate, customTypeDefinitions); + return tilesetReader.ReadTileset(); } - using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate); + using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, customTypeDefinitions); // Act var map = mapReader.ReadMap(); diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 7a1fc66..7c25d1b 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -116,7 +116,7 @@ public partial class TmxMapReaderTests public static IEnumerable DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => [ - ["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate()], + ["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate("tx")], ["Serialization.TestData.Map.map-with-group.tmx", TestData.MapWithGroup()], ]; diff --git a/DotTiled/Model/IProperty.cs b/DotTiled/Model/IProperty.cs index 9558ee2..ae522f2 100644 --- a/DotTiled/Model/IProperty.cs +++ b/DotTiled/Model/IProperty.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Linq; namespace DotTiled; @@ -18,6 +20,8 @@ public interface IProperty { public string Name { get; set; } public PropertyType Type { get; } + + IProperty Clone(); } public class StringProperty : IProperty @@ -25,6 +29,12 @@ public class StringProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.String; public required string Value { get; set; } + + public IProperty Clone() => new StringProperty + { + Name = Name, + Value = Value + }; } public class IntProperty : IProperty @@ -32,6 +42,12 @@ public class IntProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.Int; public required int Value { get; set; } + + public IProperty Clone() => new IntProperty + { + Name = Name, + Value = Value + }; } public class FloatProperty : IProperty @@ -39,6 +55,12 @@ public class FloatProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.Float; public required float Value { get; set; } + + public IProperty Clone() => new FloatProperty + { + Name = Name, + Value = Value + }; } public class BoolProperty : IProperty @@ -46,6 +68,12 @@ public class BoolProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.Bool; public required bool Value { get; set; } + + public IProperty Clone() => new BoolProperty + { + Name = Name, + Value = Value + }; } public class ColorProperty : IProperty @@ -53,6 +81,12 @@ public class ColorProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.Color; public required Color Value { get; set; } + + public IProperty Clone() => new ColorProperty + { + Name = Name, + Value = Value + }; } public class FileProperty : IProperty @@ -60,6 +94,12 @@ public class FileProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.File; public required string Value { get; set; } + + public IProperty Clone() => new FileProperty + { + Name = Name, + Value = Value + }; } public class ObjectProperty : IProperty @@ -67,6 +107,12 @@ public class ObjectProperty : IProperty public required string Name { get; set; } public PropertyType Type => PropertyType.Object; public required uint Value { get; set; } + + public IProperty Clone() => new ObjectProperty + { + Name = Name, + Value = Value + }; } public class ClassProperty : IProperty @@ -75,4 +121,53 @@ public class ClassProperty : IProperty public PropertyType Type => DotTiled.PropertyType.Class; public required string PropertyType { get; set; } public required Dictionary Properties { get; set; } + + public IProperty Clone() => new ClassProperty + { + Name = Name, + PropertyType = PropertyType, + Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone()) + }; +} + +public abstract class CustomTypeDefinition +{ + public uint ID { get; set; } + public string Name { get; set; } = ""; +} + +[Flags] +public enum CustomClassUseAs +{ + Property, + Map, + Layer, + Object, + Tile, + Tileset, + WangColor, + Wangset, + Project, + All = Property | Map | Layer | Object | Tile | Tileset | WangColor | Wangset | Project +} + +public class CustomClassDefinition : CustomTypeDefinition +{ + public Color Color { get; set; } + public bool DrawFill { get; set; } + public CustomClassUseAs UseAs { get; set; } + public List Members { get; set; } +} + +public enum CustomEnumStorageType +{ + Int, + String +} + +public class CustomEnumDefinition : CustomTypeDefinition +{ + public CustomEnumStorageType StorageType { get; set; } + public List Values { get; set; } = []; + public bool ValueAsFlags { get; set; } } diff --git a/DotTiled/Serialization/Tmj/TjTemplateReader.cs b/DotTiled/Serialization/Tmj/TjTemplateReader.cs new file mode 100644 index 0000000..0dc1dd8 --- /dev/null +++ b/DotTiled/Serialization/Tmj/TjTemplateReader.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; + +namespace DotTiled; + +public class TjTemplateReader : ITemplateReader +{ + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + + private readonly string _jsonString; + private bool disposedValue; + + private readonly IReadOnlyCollection _customTypeDefinitions; + + public TjTemplateReader( + string jsonString, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); + _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + _customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions)); + } + + public Template ReadTemplate() + { + var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString); + var rootElement = jsonDoc.RootElement; + return Tmj.ReadTemplate(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions); + } + + 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 + // ~TjTemplateReader() + // { + // // 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); + GC.SuppressFinalize(this); + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Layer.cs b/DotTiled/Serialization/Tmj/Tmj.Layer.cs index 7e8685e..33d87e7 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Layer.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Layer.cs @@ -4,27 +4,33 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Numerics; using System.Text.Json; namespace DotTiled; internal partial class Tmj { - internal static BaseLayer ReadLayer(JsonElement element) + internal static BaseLayer ReadLayer( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { var type = element.GetRequiredProperty("type"); return type switch { - "tilelayer" => ReadTileLayer(element), - // "objectgroup" => ReadObjectGroup(element), + "tilelayer" => ReadTileLayer(element, customTypeDefinitions), + "objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions), // "imagelayer" => ReadImageLayer(element), - // "group" => ReadGroup(element), + "group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions), _ => throw new JsonException($"Unsupported layer type '{type}'.") }; } - internal static TileLayer ReadTileLayer(JsonElement element) + internal static TileLayer ReadTileLayer( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) { var compression = element.GetOptionalPropertyParseable("compression", s => s switch { @@ -50,7 +56,7 @@ internal partial class Tmj var opacity = element.GetOptionalProperty("opacity", 1.0f); var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e), null); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var repeatX = element.GetOptionalProperty("repeatx", false); var repeatY = element.GetOptionalProperty("repeaty", false); var startX = element.GetOptionalProperty("startx", 0); @@ -85,4 +91,276 @@ internal partial class Tmj Data = data ?? chunks }; } + + internal static Group ReadGroup( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); + + 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 + }; + } + + internal static ObjectLayer ReadObjectLayer( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + + var x = element.GetOptionalProperty("x", 0); + var y = element.GetOptionalProperty("y", 0); + var width = element.GetOptionalProperty("width", null); + var height = element.GetOptionalProperty("height", null); + var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var drawOrder = element.GetOptionalPropertyParseable("draworder", s => s switch + { + "topdown" => DrawOrder.TopDown, + "index" => DrawOrder.Index, + _ => throw new JsonException($"Unknown draw order '{s}'.") + }, DrawOrder.TopDown); + + var objects = element.GetOptionalPropertyCustom>("objects", e => e.GetValueAsList(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []); + + return new ObjectLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + X = x, + Y = y, + Width = width, + Height = height, + Color = color, + DrawOrder = drawOrder, + Objects = objects + }; + } + + internal static Object ReadObject( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + 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; + bool ellipseDefault = false; + bool pointDefault = false; + List? polygonDefault = null; + List? polylineDefault = null; + Dictionary? propertiesDefault = null; + + var template = element.GetOptionalProperty("template", null); + 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; + ellipseDefault = templObj is EllipseObject; + pointDefault = templObj is PointObject; + polygonDefault = (templObj is PolygonObject polygonObj) ? polygonObj.Points : null; + polylineDefault = (templObj is PolylineObject polylineObj) ? polylineObj.Points : null; + } + + var ellipse = element.GetOptionalProperty("ellipse", ellipseDefault); + var gid = element.GetOptionalProperty("gid", gidDefault); + var height = element.GetOptionalProperty("height", heightDefault); + var id = element.GetOptionalProperty("id", idDefault); + var name = element.GetOptionalProperty("name", nameDefault); + var point = element.GetOptionalProperty("point", pointDefault); + var polygon = element.GetOptionalPropertyCustom?>("polygon", e => ReadPoints(e), polygonDefault); + var polyline = element.GetOptionalPropertyCustom?>("polyline", e => ReadPoints(e), polylineDefault); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); + var rotation = element.GetOptionalProperty("rotation", rotationDefault); + // var text + var type = element.GetOptionalProperty("type", typeDefault); + var visible = element.GetOptionalProperty("visible", visibleDefault); + var width = element.GetOptionalProperty("width", widthDefault); + var x = element.GetOptionalProperty("x", xDefault); + var y = element.GetOptionalProperty("y", yDefault); + + if (ellipse) + { + return new EllipseObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + if (point) + { + return new PointObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + if (polygon is not null) + { + return new PolygonObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties, + Points = polygon + }; + } + + if (polyline is not null) + { + return new PolylineObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties, + Points = polyline + }; + } + + // Text + + return new RectangleObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + internal static List ReadPoints(JsonElement element) => + element.GetValueAsList(e => + { + var x = e.GetRequiredProperty("x"); + var y = e.GetRequiredProperty("y"); + return new Vector2(x, y); + }); + + internal static Template ReadTemplate( + JsonElement element, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var type = element.GetRequiredProperty("type"); + var tileset = element.GetOptionalPropertyCustom("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null); + var @object = element.GetRequiredPropertyCustom("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)); + + return new Template + { + Tileset = tileset, + Object = @object + }; + } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Map.cs b/DotTiled/Serialization/Tmj/Tmj.Map.cs index 0906b70..ea7313f 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Map.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -9,7 +9,11 @@ namespace DotTiled; internal partial class Tmj { - internal static Map ReadMap(JsonElement element, Func? externalTilesetResolver, Func externalTemplateResolver) + internal static Map ReadMap( + JsonElement element, + Func? externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { var version = element.GetRequiredProperty("version"); var tiledVersion = element.GetRequiredProperty("tiledversion"); @@ -55,10 +59,10 @@ internal partial class Tmj var nextObjectID = element.GetRequiredProperty("nextobjectid"); var infinite = element.GetOptionalProperty("infinite", false); - var properties = element.GetOptionalPropertyCustom?>("properties", ReadProperties, null); + var properties = element.GetOptionalPropertyCustom?>("properties", el => ReadProperties(el, customTypeDefinitions), null); - List layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(ReadLayer), []); - List tilesets = element.GetOptionalPropertyCustom>("tilesets", e => e.GetValueAsList(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver)), []); + List layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); + List tilesets = element.GetOptionalPropertyCustom>("tilesets", e => e.GetValueAsList(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []); return new Map { diff --git a/DotTiled/Serialization/Tmj/Tmj.Properties.cs b/DotTiled/Serialization/Tmj/Tmj.Properties.cs index 37b7dd1..2e01749 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Properties.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Properties.cs @@ -8,7 +8,9 @@ namespace DotTiled; internal partial class Tmj { - internal static Dictionary ReadProperties(JsonElement element) => + internal static Dictionary ReadProperties( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) => element.GetValueAsList(e => { var name = e.GetRequiredProperty("name"); @@ -34,20 +36,105 @@ internal partial class Tmj PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("value") }, - PropertyType.Class => ReadClassProperty(e), + PropertyType.Class => ReadClassProperty(e, customTypeDefinitions), _ => throw new JsonException("Invalid property type") }; return property!; }).ToDictionary(p => p.Name); - internal static ClassProperty ReadClassProperty(JsonElement element) + internal static ClassProperty ReadClassProperty( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) { var name = element.GetRequiredProperty("name"); var propertyType = element.GetRequiredProperty("propertytype"); - var properties = element.GetRequiredPropertyCustom>("properties", ReadProperties); + var customTypeDef = customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == propertyType); - return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; + if (customTypeDef is CustomClassDefinition ccd) + { + var propsInType = CreateInstanceOfCustomClass(ccd); + var props = element.GetOptionalPropertyCustom>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); + + var mergedProps = MergeProperties(propsInType, props); + + return new ClassProperty + { + Name = name, + PropertyType = propertyType, + Properties = mergedProps + }; + } + + throw new JsonException($"Unknown custom class '{propertyType}'."); + } + + internal static Dictionary ReadCustomClassProperties( + JsonElement element, + CustomClassDefinition customClassDefinition, + IReadOnlyCollection customTypeDefinitions) + { + Dictionary resultingProps = []; + + foreach (var prop in customClassDefinition.Members) + { + if (!element.TryGetProperty(prop.Name, out var propElement)) + continue; // Property not present in element, therefore will use default value + + IProperty property = prop.Type switch + { + PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs() }, + PropertyType.Int => new IntProperty { Name = prop.Name, Value = propElement.GetValueAs() }, + PropertyType.Float => new FloatProperty { Name = prop.Name, Value = propElement.GetValueAs() }, + PropertyType.Bool => new BoolProperty { Name = prop.Name, Value = propElement.GetValueAs() }, + PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs(), CultureInfo.InvariantCulture) }, + PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs() }, + PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs() }, + PropertyType.Class => ReadClassProperty(propElement, customTypeDefinitions), + _ => throw new JsonException("Invalid property type") + }; + + resultingProps[prop.Name] = property; + } + + return resultingProps; + } + + internal static Dictionary CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) + { + return customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone()); + } + + internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) + { + if (baseProperties is null) + return overrideProperties ?? new Dictionary(); + + if (overrideProperties is null) + return baseProperties; + + var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); + 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; } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs index 66d42b1..1d0aa6e 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -10,7 +10,11 @@ namespace DotTiled; internal partial class Tmj { - internal static Tileset ReadTileset(JsonElement element, Func? externalTilesetResolver, Func externalTemplateResolver) + internal static Tileset ReadTileset( + JsonElement element, + Func? externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { var backgroundColor = element.GetOptionalPropertyParseable("backgroundcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); var @class = element.GetOptionalProperty("class", ""); @@ -42,7 +46,7 @@ internal partial class Tmj "bottomright" => ObjectAlignment.BottomRight, _ => throw new JsonException($"Unknown object alignment '{s}'") }, ObjectAlignment.Unspecified); - var properties = element.GetOptionalPropertyCustom?>("properties", ReadProperties, null); + var properties = element.GetOptionalPropertyCustom?>("properties", el => ReadProperties(el, customTypeDefinitions), null); var source = element.GetOptionalProperty("source", null); var spacing = element.GetOptionalProperty("spacing", null); var tileCount = element.GetOptionalProperty("tilecount", null); @@ -55,7 +59,7 @@ internal partial class Tmj "grid" => TileRenderSize.Grid, _ => throw new JsonException($"Unknown tile render size '{s}'") }, TileRenderSize.Tile); - var tiles = element.GetOptionalPropertyCustom>("tiles", ReadTiles, []); + var tiles = element.GetOptionalPropertyCustom>("tiles", el => ReadTiles(el, customTypeDefinitions), []); var tileWidth = element.GetOptionalProperty("tilewidth", null); var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); var type = element.GetOptionalProperty("type", null); @@ -153,7 +157,9 @@ internal partial class Tmj }; } - internal static List ReadTiles(JsonElement element) => + internal static List ReadTiles( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) => element.GetValueAsList(e => { //var animation = e.GetOptionalPropertyCustom>("animation", ReadFrames, null); @@ -167,7 +173,7 @@ internal partial class Tmj var height = e.GetOptionalProperty("height", imageHeight ?? 0); //var objectGroup = e.GetOptionalPropertyCustom("objectgroup", ReadObjectLayer, null); var probability = e.GetOptionalProperty("probability", 1.0f); - var properties = e.GetOptionalPropertyCustom?>("properties", ReadProperties, null); + var properties = e.GetOptionalPropertyCustom?>("properties", el => ReadProperties(el, customTypeDefinitions), null); // var terrain, replaced by wangsets var type = e.GetOptionalProperty("type", ""); diff --git a/DotTiled/Serialization/Tmj/TmjMapReader.cs b/DotTiled/Serialization/Tmj/TmjMapReader.cs index 9aa9675..260cd21 100644 --- a/DotTiled/Serialization/Tmj/TmjMapReader.cs +++ b/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Text.Json; @@ -14,18 +15,25 @@ public class TmjMapReader : IMapReader private string _jsonString; private bool disposedValue; - public TmjMapReader(string jsonString, Func externalTilesetResolver, Func externalTemplateResolver) + private readonly IReadOnlyCollection _customTypeDefinitions; + + public TmjMapReader( + string jsonString, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + _customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions)); } public Map ReadMap() { var jsonDoc = JsonDocument.Parse(_jsonString); var rootElement = jsonDoc.RootElement; - return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver); + return Tmj.ReadMap(rootElement, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions); } protected virtual void Dispose(bool disposing) diff --git a/DotTiled/Serialization/Tmj/TsjTilesetReader.cs b/DotTiled/Serialization/Tmj/TsjTilesetReader.cs new file mode 100644 index 0000000..14e5323 --- /dev/null +++ b/DotTiled/Serialization/Tmj/TsjTilesetReader.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; + +namespace DotTiled; + +public class TsjTilesetReader : ITilesetReader +{ + // External resolvers + private readonly Func _externalTemplateResolver; + + private readonly string _jsonString; + private bool disposedValue; + + private readonly IReadOnlyCollection _customTypeDefinitions; + + public TsjTilesetReader( + string jsonString, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + _jsonString = jsonString ?? throw new ArgumentNullException(nameof(jsonString)); + _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + _customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions)); + } + + public Tileset ReadTileset() + { + var jsonDoc = System.Text.Json.JsonDocument.Parse(_jsonString); + var rootElement = jsonDoc.RootElement; + return Tmj.ReadTileset( + rootElement, + _ => throw new NotSupportedException("External tilesets cannot refer to other external tilesets."), + _externalTemplateResolver, + _customTypeDefinitions); + } + + 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 + // ~TsjTilesetReader() + // { + // // 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 e9cf6e01f69a48707bcc16480105c1694a13d48c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 10 Aug 2024 19:24:25 +0200 Subject: [PATCH 21/39] Add text object parsing for JSON --- DotTiled/Serialization/Tmj/Tmj.Layer.cs | 63 ++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/DotTiled/Serialization/Tmj/Tmj.Layer.cs b/DotTiled/Serialization/Tmj/Tmj.Layer.cs index 33d87e7..68cd92a 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Layer.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Layer.cs @@ -235,7 +235,7 @@ internal partial class Tmj var polyline = element.GetOptionalPropertyCustom?>("polyline", e => ReadPoints(e), polylineDefault); var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); var rotation = element.GetOptionalProperty("rotation", rotationDefault); - // var text + var text = element.GetOptionalPropertyCustom("text", ReadText, null); var type = element.GetOptionalProperty("type", typeDefault); var visible = element.GetOptionalProperty("visible", visibleDefault); var width = element.GetOptionalProperty("width", widthDefault); @@ -320,7 +320,22 @@ internal partial class Tmj }; } - // Text + if (text is not null) + { + text.ID = id; + text.Name = name; + text.Type = type; + text.X = x; + text.Y = y; + text.Width = width; + text.Height = height; + text.Rotation = rotation; + text.GID = gid; + text.Visible = visible; + text.Template = template; + text.Properties = properties; + return text; + } return new RectangleObject { @@ -363,4 +378,48 @@ internal partial class Tmj Object = @object }; } + + internal static TextObject ReadText(JsonElement element) + { + var bold = element.GetOptionalProperty("bold", false); + var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); + var fontfamily = element.GetOptionalProperty("fontfamily", "sans-serif"); + var halign = element.GetOptionalPropertyParseable("halign", s => s switch + { + "left" => TextHorizontalAlignment.Left, + "center" => TextHorizontalAlignment.Center, + "right" => TextHorizontalAlignment.Right, + _ => throw new JsonException($"Unknown horizontal alignment '{s}'.") + }, TextHorizontalAlignment.Left); + var italic = element.GetOptionalProperty("italic", false); + var kerning = element.GetOptionalProperty("kerning", true); + var pixelsize = element.GetOptionalProperty("pixelsize", 16); + var strikeout = element.GetOptionalProperty("strikeout", false); + var text = element.GetRequiredProperty("text"); + var underline = element.GetOptionalProperty("underline", false); + var valign = element.GetOptionalPropertyParseable("valign", s => s switch + { + "top" => TextVerticalAlignment.Top, + "center" => TextVerticalAlignment.Center, + "bottom" => TextVerticalAlignment.Bottom, + _ => throw new JsonException($"Unknown vertical alignment '{s}'.") + }, TextVerticalAlignment.Top); + var wrap = element.GetOptionalProperty("wrap", false); + + return new TextObject + { + Bold = bold, + Color = color, + FontFamily = fontfamily, + HorizontalAlignment = halign, + Italic = italic, + Kerning = kerning, + PixelSize = pixelsize, + Strikeout = strikeout, + Text = text, + Underline = underline, + VerticalAlignment = valign, + Wrap = wrap + }; + } } From bb74d3cceef64a5a51e83004c64c56ac82c3d5b7 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sat, 10 Aug 2024 21:36:13 +0200 Subject: [PATCH 22/39] Added same CustomTypeDefinition stuff to Tmx --- .../Serialization/Tmx/TmxMapReaderTests.cs | 97 ++++++++++++++++--- DotTiled/Serialization/Tmx/Tmx.Map.cs | 18 ++-- DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs | 26 +++-- DotTiled/Serialization/Tmx/Tmx.Properties.cs | 32 ++++-- DotTiled/Serialization/Tmx/Tmx.TileLayer.cs | 28 ++++-- DotTiled/Serialization/Tmx/Tmx.Tileset.cs | 41 +++++--- DotTiled/Serialization/Tmx/TmxMapReader.cs | 11 ++- .../Serialization/Tmx/TsxTilesetReader.cs | 14 ++- .../Serialization/Tmx/TxTemplateReader.cs | 12 ++- 9 files changed, 213 insertions(+), 66 deletions(-) diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 7c25d1b..3556893 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -15,7 +15,7 @@ public partial class TmxMapReaderTests // Act Action act = () => { - using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver, []); }; // Assert @@ -34,7 +34,7 @@ public partial class TmxMapReaderTests // Act Action act = () => { - using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver, []); }; // Assert @@ -53,7 +53,7 @@ public partial class TmxMapReaderTests // Act Action act = () => { - using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + using var _ = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver, []); }; // Assert @@ -70,7 +70,7 @@ public partial class TmxMapReaderTests Func externalTemplateResolver = (_) => new Template { Object = new RectangleObject { } }; // Act - using var tmxMapReader = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver); + using var tmxMapReader = new TmxMapReader(xmlReader, externalTilesetResolver, externalTemplateResolver, []); // Assert Assert.NotNull(tmxMapReader); @@ -91,20 +91,55 @@ public partial class TmxMapReaderTests public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange + CustomTypeDefinition[] customTypeDefinitions = [ + new CustomClassDefinition + { + Name = "TestClass", + ID = 1, + UseAs = CustomClassUseAs.Property, + Members = [ + new StringProperty + { + Name = "Name", + Value = "" + }, + new FloatProperty + { + Name = "Amount", + Value = 0f + } + ] + }, + new CustomClassDefinition + { + Name = "Test", + ID = 2, + UseAs = CustomClassUseAs.All, + Members = [ + new ClassProperty + { + Name = "Yep", + PropertyType = "TestClass", + Properties = [] + } + ] + } + ]; + using var reader = TestData.GetXmlReaderFor(testDataFile); - static Template ResolveTemplate(string source) + Template ResolveTemplate(string source) { using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.TestData.Template.{source}"); - using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); + using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions); return templateReader.ReadTemplate(); } - static Tileset ResolveTileset(string source) + Tileset ResolveTileset(string source) { using var xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); - using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); + using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions); return tilesetReader.ReadTileset(); } - using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate); + using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, customTypeDefinitions); // Act var map = mapReader.ReadMap(); @@ -125,20 +160,54 @@ public partial class TmxMapReaderTests public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) { // Arrange + CustomTypeDefinition[] customTypeDefinitions = [ + new CustomClassDefinition + { + Name = "TestClass", + ID = 1, + UseAs = CustomClassUseAs.Property, + Members = [ + new StringProperty + { + Name = "Name", + Value = "" + }, + new FloatProperty + { + Name = "Amount", + Value = 0f + } + ] + }, + new CustomClassDefinition + { + Name = "Test", + ID = 2, + UseAs = CustomClassUseAs.All, + Members = [ + new ClassProperty + { + Name = "Yep", + PropertyType = "TestClass", + Properties = [] + } + ] + } + ]; using var reader = TestData.GetXmlReaderFor(testDataFile); - static Template ResolveTemplate(string source) + Template ResolveTemplate(string source) { using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.TestData.Template.{source}"); - using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate); + using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions); return templateReader.ReadTemplate(); } - static Tileset ResolveTileset(string source) + Tileset ResolveTileset(string source) { using var xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); - using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate); + using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions); return tilesetReader.ReadTileset(); } - using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate); + using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, customTypeDefinitions); // Act var map = mapReader.ReadMap(); diff --git a/DotTiled/Serialization/Tmx/Tmx.Map.cs b/DotTiled/Serialization/Tmx/Tmx.Map.cs index d43d0b1..bb7f1ae 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Map.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Map.cs @@ -8,7 +8,11 @@ namespace DotTiled; internal partial class Tmx { - internal static Map ReadMap(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + internal static Map ReadMap( + XmlReader reader, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { // Attributes var version = reader.GetRequiredAttribute("version"); @@ -64,12 +68,12 @@ internal partial class Tmx 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)), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), + "tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), + "layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)), + "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)), + "imagelayer" => () => layers.Add(ReadImageLayer(r, customTypeDefinitions)), + "group" => () => layers.Add(ReadGroup(r, externalTemplateResolver, customTypeDefinitions)), _ => r.Skip }); diff --git a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs index 2c52429..4fb0ce6 100644 --- a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs @@ -9,7 +9,10 @@ namespace DotTiled; internal partial class Tmx { - internal static ObjectLayer ReadObjectLayer(XmlReader reader, Func externalTemplateResolver) + internal static ObjectLayer ReadObjectLayer( + XmlReader reader, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { // Attributes var id = reader.GetRequiredAttributeParseable("id"); @@ -40,8 +43,8 @@ internal partial class Tmx reader.ProcessChildren("objectgroup", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "object" => () => objects.Add(ReadObject(r, externalTemplateResolver)), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), + "object" => () => objects.Add(ReadObject(r, externalTemplateResolver, customTypeDefinitions)), _ => r.Skip }); @@ -68,7 +71,10 @@ internal partial class Tmx }; } - internal static Object ReadObject(XmlReader reader, Func externalTemplateResolver) + internal static Object ReadObject( + XmlReader reader, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { // Attributes var template = reader.GetOptionalAttribute("template"); @@ -122,7 +128,7 @@ internal partial class Tmx reader.ProcessChildren("object", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, MergeProperties(properties, ReadProperties(r)), "Properties", ref propertiesCounter), + "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "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"), @@ -280,7 +286,11 @@ internal partial class Tmx }; } - internal static Template ReadTemplate(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + internal static Template ReadTemplate( + XmlReader reader, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { // No attributes @@ -292,8 +302,8 @@ internal partial class Tmx 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"), + "tileset" => () => Helpers.SetAtMostOnce(ref tileset, ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), "Tileset"), + "object" => () => Helpers.SetAtMostOnce(ref obj, ReadObject(r, externalTemplateResolver, customTypeDefinitions), "Object"), _ => r.Skip }); diff --git a/DotTiled/Serialization/Tmx/Tmx.Properties.cs b/DotTiled/Serialization/Tmx/Tmx.Properties.cs index 5609a85..0de4adf 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Properties.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Properties.cs @@ -6,7 +6,9 @@ namespace DotTiled; internal partial class Tmx { - internal static Dictionary ReadProperties(XmlReader reader) + internal static Dictionary ReadProperties( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) { return reader.ReadList("properties", "property", (r) => { @@ -33,22 +35,38 @@ internal partial class Tmx 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), + PropertyType.Class => ReadClassProperty(r, customTypeDefinitions), _ => throw new XmlException("Invalid property type") }; return (name, property); }).ToDictionary(x => x.name, x => x.property); } - internal static ClassProperty ReadClassProperty(XmlReader reader) + internal static ClassProperty ReadClassProperty( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) { var name = reader.GetRequiredAttribute("name"); var propertyType = reader.GetRequiredAttribute("propertytype"); - reader.ReadStartElement("property"); - var properties = ReadProperties(reader); - reader.ReadEndElement(); + var customTypeDef = customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == propertyType); + if (customTypeDef is CustomClassDefinition ccd) + { + reader.ReadStartElement("property"); + var propsInType = CreateInstanceOfCustomClass(ccd); + var props = ReadProperties(reader, customTypeDefinitions); - return new ClassProperty { Name = name, PropertyType = propertyType, Properties = properties }; + var mergedProps = MergeProperties(propsInType, props); + + reader.ReadEndElement(); + return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps }; + } + + throw new XmlException($"Unkonwn custom class definition: {propertyType}"); + } + + internal static Dictionary CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) + { + return customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone()); } } diff --git a/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs b/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs index e162542..78096e3 100644 --- a/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs @@ -7,7 +7,10 @@ namespace DotTiled; internal partial class Tmx { - internal static TileLayer ReadTileLayer(XmlReader reader, bool dataUsesChunks) + internal static TileLayer ReadTileLayer( + XmlReader reader, + bool dataUsesChunks, + IReadOnlyCollection customTypeDefinitions) { var id = reader.GetRequiredAttributeParseable("id"); var name = reader.GetOptionalAttribute("name") ?? ""; @@ -30,7 +33,7 @@ internal partial class Tmx reader.ProcessChildren("layer", (r, elementName) => elementName switch { "data" => () => Helpers.SetAtMostOnce(ref data, ReadData(r, dataUsesChunks), "Data"), - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), _ => r.Skip }); @@ -55,7 +58,9 @@ internal partial class Tmx }; } - internal static ImageLayer ReadImageLayer(XmlReader reader) + internal static ImageLayer ReadImageLayer( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) { var id = reader.GetRequiredAttributeParseable("id"); var name = reader.GetOptionalAttribute("name") ?? ""; @@ -78,7 +83,7 @@ internal partial class Tmx reader.ProcessChildren("imagelayer", (r, elementName) => elementName switch { "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), _ => r.Skip }); @@ -103,7 +108,10 @@ internal partial class Tmx }; } - internal static Group ReadGroup(XmlReader reader, Func externalTemplateResolver) + internal static Group ReadGroup( + XmlReader reader, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { var id = reader.GetRequiredAttributeParseable("id"); var name = reader.GetOptionalAttribute("name") ?? ""; @@ -121,11 +129,11 @@ internal partial class Tmx 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)), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), + "layer" => () => layers.Add(ReadTileLayer(r, false, customTypeDefinitions)), + "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)), + "imagelayer" => () => layers.Add(ReadImageLayer(r, customTypeDefinitions)), + "group" => () => layers.Add(ReadGroup(r, externalTemplateResolver, customTypeDefinitions)), _ => r.Skip }); diff --git a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs index 3c2a3a8..3885dac 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs @@ -8,7 +8,11 @@ namespace DotTiled; internal partial class Tmx { - internal static Tileset ReadTileset(XmlReader reader, Func? externalTilesetResolver, Func externalTemplateResolver) + internal static Tileset ReadTileset( + XmlReader reader, + Func? externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { // Attributes var version = reader.GetOptionalAttribute("version"); @@ -64,10 +68,10 @@ internal partial class Tmx "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"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), + "wangsets" => () => Helpers.SetAtMostOnce(ref wangsets, ReadWangsets(r, customTypeDefinitions), "Wangsets"), "transformations" => () => Helpers.SetAtMostOnce(ref transformations, ReadTransformations(r), "Transformations"), - "tile" => () => tiles.Add(ReadTile(r, externalTemplateResolver)), + "tile" => () => tiles.Add(ReadTile(r, externalTemplateResolver, customTypeDefinitions)), _ => r.Skip }); @@ -198,7 +202,10 @@ internal partial class Tmx return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed }; } - internal static Tile ReadTile(XmlReader reader, Func externalTemplateResolver) + internal static Tile ReadTile( + XmlReader reader, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { // Attributes var id = reader.GetRequiredAttributeParseable("id"); @@ -217,9 +224,9 @@ internal partial class Tmx reader.ProcessChildren("tile", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), "image" => () => Helpers.SetAtMostOnce(ref image, ReadImage(r), "Image"), - "objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(r, externalTemplateResolver), "ObjectLayer"), + "objectgroup" => () => Helpers.SetAtMostOnce(ref objectLayer, ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions), "ObjectLayer"), "animation" => () => Helpers.SetAtMostOnce(ref animation, r.ReadList("animation", "frame", (ar) => { var tileID = ar.GetRequiredAttributeParseable("tileid"); @@ -245,12 +252,16 @@ internal partial class Tmx }; } - internal static List ReadWangsets(XmlReader reader) + internal static List ReadWangsets( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) { - return reader.ReadList("wangsets", "wangset", ReadWangset); + return reader.ReadList("wangsets", "wangset", r => ReadWangset(r, customTypeDefinitions)); } - internal static Wangset ReadWangset(XmlReader reader) + internal static Wangset ReadWangset( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) { // Attributes var name = reader.GetRequiredAttribute("name"); @@ -264,8 +275,8 @@ internal partial class Tmx reader.ProcessChildren("wangset", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), - "wangcolor" => () => wangColors.Add(ReadWangColor(r)), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), + "wangcolor" => () => wangColors.Add(ReadWangColor(r, customTypeDefinitions)), "wangtile" => () => wangTiles.Add(ReadWangTile(r)), _ => r.Skip }); @@ -284,7 +295,9 @@ internal partial class Tmx }; } - internal static WangColor ReadWangColor(XmlReader reader) + internal static WangColor ReadWangColor( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) { // Attributes var name = reader.GetRequiredAttribute("name"); @@ -298,7 +311,7 @@ internal partial class Tmx reader.ProcessChildren("wangcolor", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r), "Properties"), + "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), _ => r.Skip }); diff --git a/DotTiled/Serialization/Tmx/TmxMapReader.cs b/DotTiled/Serialization/Tmx/TmxMapReader.cs index d7e631a..02388bb 100644 --- a/DotTiled/Serialization/Tmx/TmxMapReader.cs +++ b/DotTiled/Serialization/Tmx/TmxMapReader.cs @@ -13,11 +13,18 @@ public class TmxMapReader : IMapReader private readonly XmlReader _reader; private bool disposedValue; - public TmxMapReader(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + private readonly IReadOnlyCollection _customTypeDefinitions; + + public TmxMapReader( + XmlReader reader, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + _customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions)); // Prepare reader _reader.MoveToContent(); @@ -25,7 +32,7 @@ public class TmxMapReader : IMapReader public Map ReadMap() { - return Tmx.ReadMap(_reader, _externalTilesetResolver, _externalTemplateResolver); + return Tmx.ReadMap(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions); } protected virtual void Dispose(bool disposing) diff --git a/DotTiled/Serialization/Tmx/TsxTilesetReader.cs b/DotTiled/Serialization/Tmx/TsxTilesetReader.cs index c089129..dba516b 100644 --- a/DotTiled/Serialization/Tmx/TsxTilesetReader.cs +++ b/DotTiled/Serialization/Tmx/TsxTilesetReader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Xml; namespace DotTiled; @@ -11,13 +12,22 @@ public class TsxTilesetReader : ITilesetReader private readonly XmlReader _reader; private bool disposedValue; - public TsxTilesetReader(XmlReader reader, Func externalTemplateResolver) + private readonly IReadOnlyCollection _customTypeDefinitions; + + public TsxTilesetReader( + XmlReader reader, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + _customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions)); + + // Prepare reader + _reader.MoveToContent(); } - public Tileset ReadTileset() => Tmx.ReadTileset(_reader, null, _externalTemplateResolver); + public Tileset ReadTileset() => Tmx.ReadTileset(_reader, null, _externalTemplateResolver, _customTypeDefinitions); protected virtual void Dispose(bool disposing) { diff --git a/DotTiled/Serialization/Tmx/TxTemplateReader.cs b/DotTiled/Serialization/Tmx/TxTemplateReader.cs index 24b95f4..eba6299 100644 --- a/DotTiled/Serialization/Tmx/TxTemplateReader.cs +++ b/DotTiled/Serialization/Tmx/TxTemplateReader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Xml; namespace DotTiled; @@ -12,17 +13,24 @@ public class TxTemplateReader : ITemplateReader private readonly XmlReader _reader; private bool disposedValue; - public TxTemplateReader(XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver) + private readonly IReadOnlyCollection _customTypeDefinitions; + + public TxTemplateReader( + XmlReader reader, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); + _customTypeDefinitions = customTypeDefinitions ?? throw new ArgumentNullException(nameof(customTypeDefinitions)); // Prepare reader _reader.MoveToContent(); } - public Template ReadTemplate() => Tmx.ReadTemplate(_reader, _externalTilesetResolver, _externalTemplateResolver); + public Template ReadTemplate() => Tmx.ReadTemplate(_reader, _externalTilesetResolver, _externalTemplateResolver, _customTypeDefinitions); protected virtual void Dispose(bool disposing) { From cc57b172a89a067e5d881fcfe6c86066f3dabd6d Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 11 Aug 2024 12:10:56 +0200 Subject: [PATCH 23/39] Added Tmj parsing to benchmarking --- DotTiled.Benchmark/Program.cs | 41 +++++---------- DotTiled/Serialization/Helpers.Data.cs | 62 +++++++++++++++++++++++ DotTiled/Serialization/Tmj/Tmj.Data.cs | 62 +++-------------------- DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 23 +++++++-- Makefile | 9 ++-- README.md | 16 +++--- 6 files changed, 112 insertions(+), 101 deletions(-) create mode 100644 DotTiled/Serialization/Helpers.Data.cs diff --git a/DotTiled.Benchmark/Program.cs b/DotTiled.Benchmark/Program.cs index 1abb982..8397495 100644 --- a/DotTiled.Benchmark/Program.cs +++ b/DotTiled.Benchmark/Program.cs @@ -15,68 +15,54 @@ namespace MyBenchmarks [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [CategoriesColumn] [Orderer(SummaryOrderPolicy.FastestToSlowest)] + [HideColumns(["StdDev", "Error", "RatioSD"])] public class MapLoading { - private string _tmxPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\Tmx\TestData\Map\empty-map-csv.tmx"; + private string _tmxPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\TestData\Map\empty-map-csv.tmx"; private string _tmxContents = ""; + private string _tmjPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\TestData\Map\empty-map-csv.tmj"; + private string _tmjContents = ""; + public MapLoading() { _tmxContents = System.IO.File.ReadAllText(_tmxPath); + _tmjContents = System.IO.File.ReadAllText(_tmjPath); } [BenchmarkCategory("MapFromInMemoryTmxString")] [Benchmark(Baseline = true, Description = "DotTiled")] - public DotTiled.Map LoadWithDotTiledFromInMemoryString() + public DotTiled.Map LoadWithDotTiledFromInMemoryTmxString() { 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()); + using var mapReader = new DotTiled.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception(), []); return mapReader.ReadMap(); } - [BenchmarkCategory("MapFromTmxFile")] + [BenchmarkCategory("MapFromInMemoryTmjString")] [Benchmark(Baseline = true, Description = "DotTiled")] - public DotTiled.Map LoadWithDotTiledFromFile() + public DotTiled.Map LoadWithDotTiledFromInMemoryTmjString() { - 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()); + using var mapReader = new DotTiled.TmjMapReader(_tmjContents, _ => throw new Exception(), _ => throw new Exception(), []); return mapReader.ReadMap(); } [BenchmarkCategory("MapFromInMemoryTmxString")] [Benchmark(Description = "TiledLib")] - public TiledLib.Map LoadWithTiledLibFromInMemoryString() + public TiledLib.Map LoadWithTiledLibFromInMemoryTmxString() { 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() + public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromInMemoryTmxString() { 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); - } } public class Program @@ -84,6 +70,7 @@ namespace MyBenchmarks public static void Main(string[] args) { var config = BenchmarkDotNet.Configs.DefaultConfig.Instance + .WithArtifactsPath(args[0]) .WithOptions(ConfigOptions.DisableOptimizationsValidator) .AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default); var summary = BenchmarkRunner.Run(config); diff --git a/DotTiled/Serialization/Helpers.Data.cs b/DotTiled/Serialization/Helpers.Data.cs new file mode 100644 index 0000000..ab1fadc --- /dev/null +++ b/DotTiled/Serialization/Helpers.Data.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; + +namespace DotTiled; + +internal static partial class Helpers +{ + internal static class Data + { + 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]; + } + + 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); + } + + internal static uint[] ReadBytesAsInt32Array(byte[] bytes) + { + var intArray = new uint[bytes.Length / 4]; + for (var i = 0; i < intArray.Length; i++) + { + intArray[i] = BitConverter.ToUInt32(bytes, i * 4); + } + + return intArray; + } + + 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); + } + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Data.cs b/DotTiled/Serialization/Tmj/Tmj.Data.cs index 27cd2bc..05f24c5 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Data.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Data.cs @@ -49,7 +49,7 @@ internal partial class Tmj { // Array of uint var data = element.GetValueAsList(e => e.GetValueAs()).ToArray(); - var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data); + var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } else if (encoding == DataEncoding.Base64) @@ -58,75 +58,25 @@ internal partial class Tmj if (compression == null) { - var data = ReadBytesAsInt32Array(base64Data); - var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data); + var data = Helpers.Data.ReadBytesAsInt32Array(base64Data); + var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } using var stream = new MemoryStream(base64Data); var decompressed = compression switch { - DataCompression.GZip => DecompressGZip(stream), - DataCompression.ZLib => DecompressZLib(stream), + DataCompression.GZip => Helpers.Data.DecompressGZip(stream), + DataCompression.ZLib => Helpers.Data.DecompressZLib(stream), _ => throw new JsonException($"Unsupported compression '{compression}'.") }; { - var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(decompressed); + var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(decompressed); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } } throw new JsonException($"Unsupported encoding '{encoding}'."); } - - 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); - } - - internal static uint[] ReadBytesAsInt32Array(byte[] bytes) - { - var intArray = new uint[bytes.Length / 4]; - for (var i = 0; i < intArray.Length; i++) - { - intArray[i] = BitConverter.ToUInt32(bytes, i * 4); - } - - return intArray; - } - - 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); - } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs index 1d0aa6e..47b8e17 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -59,7 +59,7 @@ internal partial class Tmj "grid" => TileRenderSize.Grid, _ => throw new JsonException($"Unknown tile render size '{s}'") }, TileRenderSize.Tile); - var tiles = element.GetOptionalPropertyCustom>("tiles", el => ReadTiles(el, customTypeDefinitions), []); + var tiles = element.GetOptionalPropertyCustom>("tiles", el => ReadTiles(el, externalTemplateResolver, customTypeDefinitions), []); var tileWidth = element.GetOptionalProperty("tilewidth", null); var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); var type = element.GetOptionalProperty("type", null); @@ -159,10 +159,11 @@ internal partial class Tmj internal static List ReadTiles( JsonElement element, + Func externalTemplateResolver, IReadOnlyCollection customTypeDefinitions) => element.GetValueAsList(e => { - //var animation = e.GetOptionalPropertyCustom>("animation", ReadFrames, null); + var animation = e.GetOptionalPropertyCustom>("animation", e => e.GetValueAsList(ReadFrame), null); var id = e.GetRequiredProperty("id"); var image = e.GetOptionalProperty("image", null); var imageHeight = e.GetOptionalProperty("imageheight", null); @@ -171,7 +172,7 @@ internal partial class Tmj var y = e.GetOptionalProperty("y", 0); var width = e.GetOptionalProperty("width", imageWidth ?? 0); var height = e.GetOptionalProperty("height", imageHeight ?? 0); - //var objectGroup = e.GetOptionalPropertyCustom("objectgroup", ReadObjectLayer, null); + var objectGroup = e.GetOptionalPropertyCustom("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null); var probability = e.GetOptionalProperty("probability", 1.0f); var properties = e.GetOptionalPropertyCustom?>("properties", el => ReadProperties(el, customTypeDefinitions), null); // var terrain, replaced by wangsets @@ -187,17 +188,29 @@ internal partial class Tmj return new Tile { - //Animation = animation, + Animation = animation, ID = id, Image = imageModel, X = x, Y = y, Width = width, Height = height, - //ObjectLayer = objectGroup, + ObjectLayer = objectGroup, Probability = probability, Properties = properties, Type = type }; }); + + internal static Frame ReadFrame(JsonElement element) + { + var duration = element.GetRequiredProperty("duration"); + var tileID = element.GetRequiredProperty("tileid"); + + return new Frame + { + Duration = duration, + TileID = tileID + }; + } } diff --git a/Makefile b/Makefile index 148b225..e7dc7a5 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ test: dotnet test +BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs DotTiled.Benchmark/DotTiled.Benchmark.csproj +BENCHMARK_OUTPUTDIR = DotTiled.Benchmark/BenchmarkDotNet.Artifacts .PHONY: benchmark -benchmark: DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md +benchmark: $(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md -BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs -DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES) - dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release \ No newline at end of file +$(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES) + dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release -- $(BENCHMARK_OUTPUTDIR) \ No newline at end of file diff --git a/README.md b/README.md index 2fec131..a629121 100644 --- a/README.md +++ b/README.md @@ -60,15 +60,13 @@ BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update) [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 | +| Method | Categories | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | +|------------ |------------------------- |---------:|------:|-------:|-------:|----------:|------------:| +| DotTiled | MapFromInMemoryTmjString | 4.292 Ξs | 1.00 | 0.4349 | - | 5.62 KB | 1.00 | +| | | | | | | | | +| DotTiled | MapFromInMemoryTmxString | 3.075 Ξs | 1.00 | 1.2817 | 0.0610 | 16.4 KB | 1.00 | +| TiledLib | MapFromInMemoryTmxString | 5.574 Ξs | 1.81 | 1.8005 | 0.0916 | 23.32 KB | 1.42 | +| TiledCSPlus | MapFromInMemoryTmxString | 6.546 Ξs | 2.13 | 2.5940 | 0.1831 | 33.16 KB | 2.02 | From 36f600dcdf4129afea660b8defe944fc012a2076 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 11 Aug 2024 17:02:16 +0200 Subject: [PATCH 24/39] Splitting up Tmj parsing more --- DotTiled/Serialization/Helpers.Data.cs | 62 --- DotTiled/Serialization/Helpers.cs | 123 ++++++ .../Tmj/ExtensionsUtf8JsonReader.cs | 139 ------ DotTiled/Serialization/Tmj/Tmj.Data.cs | 12 +- DotTiled/Serialization/Tmj/Tmj.Group.cs | 45 ++ DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs | 63 +++ DotTiled/Serialization/Tmj/Tmj.Layer.cs | 400 +----------------- DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs | 289 +++++++++++++ DotTiled/Serialization/Tmj/Tmj.Properties.cs | 34 +- DotTiled/Serialization/Tmj/Tmj.Template.cs | 27 ++ DotTiled/Serialization/Tmj/Tmj.TileLayer.cs | 75 ++++ DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 77 +++- DotTiled/Serialization/Tmj/Tmj.Wangset.cs | 13 + DotTiled/Serialization/Tmx/Tmx.Helpers.cs | 26 -- DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs | 34 +- DotTiled/Serialization/Tmx/Tmx.Properties.cs | 2 +- 16 files changed, 705 insertions(+), 716 deletions(-) delete mode 100644 DotTiled/Serialization/Helpers.Data.cs create mode 100644 DotTiled/Serialization/Helpers.cs delete mode 100644 DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Group.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Template.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.TileLayer.cs create mode 100644 DotTiled/Serialization/Tmj/Tmj.Wangset.cs delete mode 100644 DotTiled/Serialization/Tmx/Tmx.Helpers.cs diff --git a/DotTiled/Serialization/Helpers.Data.cs b/DotTiled/Serialization/Helpers.Data.cs deleted file mode 100644 index ab1fadc..0000000 --- a/DotTiled/Serialization/Helpers.Data.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; - -namespace DotTiled; - -internal static partial class Helpers -{ - internal static class Data - { - 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]; - } - - 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); - } - - internal static uint[] ReadBytesAsInt32Array(byte[] bytes) - { - var intArray = new uint[bytes.Length / 4]; - for (var i = 0; i < intArray.Length; i++) - { - intArray[i] = BitConverter.ToUInt32(bytes, i * 4); - } - - return intArray; - } - - 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); - } - } -} diff --git a/DotTiled/Serialization/Helpers.cs b/DotTiled/Serialization/Helpers.cs new file mode 100644 index 0000000..905cb9f --- /dev/null +++ b/DotTiled/Serialization/Helpers.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace DotTiled; + +internal static partial class Helpers +{ + 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]; + } + + 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); + } + + internal static uint[] ReadBytesAsInt32Array(byte[] bytes) + { + var intArray = new uint[bytes.Length / 4]; + for (var i = 0; i < intArray.Length; i++) + { + intArray[i] = BitConverter.ToUInt32(bytes, i * 4); + } + + return intArray; + } + + 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 ImageFormat ParseImageFormatFromSource(string source) + { + var extension = Path.GetExtension(source).ToLowerInvariant(); + return extension switch + { + ".png" => ImageFormat.Png, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".jpeg" => ImageFormat.Jpg, + ".bmp" => ImageFormat.Bmp, + _ => throw new NotSupportedException($"Unsupported image format '{extension}'") + }; + } + + internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) + { + if (baseProperties is null) + return overrideProperties ?? new Dictionary(); + + if (overrideProperties is null) + return baseProperties; + + var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); + 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 void SetAtMostOnce(ref T? field, T value, string fieldName) + { + if (field is not null) + throw new InvalidOperationException($"{fieldName} already set"); + + field = value; + } + + internal 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/Tmj/ExtensionsUtf8JsonReader.cs b/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs deleted file mode 100644 index 1c98bc6..0000000 --- a/DotTiled/Serialization/Tmj/ExtensionsUtf8JsonReader.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using System.Text.Json; - -namespace DotTiled; - -internal partial class Tmj -{ - internal abstract class JsonProperty(string propertyName) - { - internal string PropertyName { get; } = propertyName; - } - - internal delegate void UseReader(ref Utf8JsonReader reader); - - internal class RequiredProperty(string propertyName, UseReader useReader) : JsonProperty(propertyName) - { - internal UseReader UseReader { get; } = useReader; - } - - internal class OptionalProperty(string propertyName, UseReader useReader, bool allowNull = true) : JsonProperty(propertyName) - { - internal UseReader UseReader { get; } = useReader; - internal bool AllowNull { get; } = allowNull; - } -} - -internal static class ExtensionsUtf8JsonReader -{ - internal static T Progress(ref this Utf8JsonReader reader, T value) - { - reader.Read(); - return value; - } - - internal static void MoveToContent(this ref Utf8JsonReader reader) - { - while (reader.Read() && reader.TokenType == JsonTokenType.Comment || - reader.TokenType == JsonTokenType.None) - ; - } - - internal delegate void ProcessProperty(ref Utf8JsonReader reader); - - private static void ProcessJsonObject(this ref Utf8JsonReader reader, (string PropertyName, ProcessProperty Processor)[] processors) - { - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException("Expected start of object."); - - reader.Read(); - - while (reader.TokenType != JsonTokenType.EndObject) - { - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Expected property name."); - - var propertyName = reader.GetString(); - reader.Read(); - - if (!processors.Any(x => x.PropertyName == propertyName)) - { - var depthBefore = reader.CurrentDepth; - - while (reader.TokenType != JsonTokenType.PropertyName || reader.CurrentDepth > depthBefore) - reader.Read(); - - continue; - } - - var processor = processors.First(x => x.PropertyName == propertyName).Processor; - processor(ref reader); - } - - if (reader.TokenType != JsonTokenType.EndObject) - throw new JsonException("Expected end of object."); - - reader.Read(); - } - - internal static void ProcessJsonObject(this ref Utf8JsonReader reader, Tmj.JsonProperty[] properties, string objectTypeName) - { - List processedProperties = []; - - ProcessJsonObject(ref reader, properties.Select(x => (x.PropertyName, (ref Utf8JsonReader reader) => - { - if (processedProperties.Contains(x.PropertyName)) - throw new JsonException($"Property '{x.PropertyName}' was already processed."); - - processedProperties.Add(x.PropertyName); - - if (x is Tmj.RequiredProperty req) - { - if (reader.TokenType == JsonTokenType.Null) - throw new JsonException($"Required property '{req.PropertyName}' cannot be null when reading {objectTypeName}."); - - req.UseReader(ref reader); - } - else if (x is Tmj.OptionalProperty opt) - { - if (reader.TokenType == JsonTokenType.Null && !opt.AllowNull) - throw new JsonException($"Value cannot be null for optional property '{opt.PropertyName}' when reading {objectTypeName}."); - else if (reader.TokenType == JsonTokenType.Null && opt.AllowNull) - return; - - opt.UseReader(ref reader); - } - } - )).ToArray()); - - foreach (var property in properties) - { - if (property is Tmj.RequiredProperty && !processedProperties.Contains(property.PropertyName)) - throw new JsonException($"Required property '{property.PropertyName}' was not found when reading {objectTypeName}."); - } - } - - internal delegate void UseReader(ref Utf8JsonReader reader); - - internal static void ProcessJsonArray(this ref Utf8JsonReader reader, UseReader useReader) - { - if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException("Expected start of array."); - - reader.Read(); - - while (reader.TokenType != JsonTokenType.EndArray) - { - useReader(ref reader); - } - - if (reader.TokenType != JsonTokenType.EndArray) - throw new JsonException("Expected end of array."); - - reader.Read(); - } -} diff --git a/DotTiled/Serialization/Tmj/Tmj.Data.cs b/DotTiled/Serialization/Tmj/Tmj.Data.cs index 05f24c5..0b05c01 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Data.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Data.cs @@ -49,7 +49,7 @@ internal partial class Tmj { // Array of uint var data = element.GetValueAsList(e => e.GetValueAs()).ToArray(); - var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data); + var (globalTileIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs(data); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } else if (encoding == DataEncoding.Base64) @@ -58,21 +58,21 @@ internal partial class Tmj if (compression == null) { - var data = Helpers.Data.ReadBytesAsInt32Array(base64Data); - var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data); + var data = Helpers.ReadBytesAsInt32Array(base64Data); + var (globalTileIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs(data); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } using var stream = new MemoryStream(base64Data); var decompressed = compression switch { - DataCompression.GZip => Helpers.Data.DecompressGZip(stream), - DataCompression.ZLib => Helpers.Data.DecompressZLib(stream), + DataCompression.GZip => Helpers.DecompressGZip(stream), + DataCompression.ZLib => Helpers.DecompressZLib(stream), _ => throw new JsonException($"Unsupported compression '{compression}'.") }; { - var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(decompressed); + var (globalTileIDs, flippingFlags) = Helpers.ReadAndClearFlippingFlagsFromGIDs(decompressed); return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null }; } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Group.cs b/DotTiled/Serialization/Tmj/Tmj.Group.cs new file mode 100644 index 0000000..44e8b4d --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Group.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Group ReadGroup( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); + + 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/Tmj/Tmj.ImageLayer.cs b/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs new file mode 100644 index 0000000..d315891 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static ImageLayer ReadImageLayer( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + + var image = element.GetRequiredProperty("image"); + var repeatX = element.GetRequiredProperty("repeatx"); + var repeatY = element.GetRequiredProperty("repeaty"); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var x = element.GetOptionalProperty("x", 0); + var y = element.GetOptionalProperty("y", 0); + + var imgModel = new Image + { + Format = Helpers.ParseImageFormatFromSource(image), + Height = 0, + Width = 0, + Source = image, + TransparentColor = transparentColor + }; + + return new ImageLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + Image = imgModel, + RepeatX = repeatX, + RepeatY = repeatY, + X = x, + Y = y + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Layer.cs b/DotTiled/Serialization/Tmj/Tmj.Layer.cs index 68cd92a..f14d614 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Layer.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Layer.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; using System.Numerics; using System.Text.Json; @@ -22,404 +19,9 @@ internal partial class Tmj { "tilelayer" => ReadTileLayer(element, customTypeDefinitions), "objectgroup" => ReadObjectLayer(element, externalTemplateResolver, customTypeDefinitions), - // "imagelayer" => ReadImageLayer(element), + "imagelayer" => ReadImageLayer(element, customTypeDefinitions), "group" => ReadGroup(element, externalTemplateResolver, customTypeDefinitions), _ => throw new JsonException($"Unsupported layer type '{type}'.") }; } - - internal static TileLayer ReadTileLayer( - JsonElement element, - IReadOnlyCollection customTypeDefinitions) - { - var compression = element.GetOptionalPropertyParseable("compression", s => s switch - { - "zlib" => DataCompression.ZLib, - "gzip" => DataCompression.GZip, - "" => null, - _ => throw new JsonException($"Unsupported compression '{s}'.") - }, null); - var encoding = element.GetOptionalPropertyParseable("encoding", s => s switch - { - "csv" => DataEncoding.Csv, - "base64" => DataEncoding.Base64, - _ => throw new JsonException($"Unsupported encoding '{s}'.") - }, DataEncoding.Csv); - var chunks = element.GetOptionalPropertyCustom("chunks", e => ReadDataAsChunks(e, compression, encoding), null); - var @class = element.GetOptionalProperty("class", ""); - var data = element.GetOptionalPropertyCustom("data", e => ReadDataWithoutChunks(e, compression, encoding), null); - var height = element.GetRequiredProperty("height"); - var id = element.GetRequiredProperty("id"); - var name = element.GetRequiredProperty("name"); - var offsetX = element.GetOptionalProperty("offsetx", 0.0f); - var offsetY = element.GetOptionalProperty("offsety", 0.0f); - var opacity = element.GetOptionalProperty("opacity", 1.0f); - var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); - var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - var repeatX = element.GetOptionalProperty("repeatx", false); - var repeatY = element.GetOptionalProperty("repeaty", false); - var startX = element.GetOptionalProperty("startx", 0); - var startY = element.GetOptionalProperty("starty", 0); - var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var visible = element.GetOptionalProperty("visible", true); - var width = element.GetRequiredProperty("width"); - var x = element.GetRequiredProperty("x"); - var y = element.GetRequiredProperty("y"); - - if ((data ?? chunks) is null) - throw new JsonException("Tile layer does not contain data."); - - return new TileLayer - { - ID = id, - Name = name, - Class = @class, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxx, - ParallaxY = parallaxy, - Properties = properties, - X = x, - Y = y, - Width = width, - Height = height, - Data = data ?? chunks - }; - } - - internal static Group ReadGroup( - JsonElement element, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - var id = element.GetRequiredProperty("id"); - var name = element.GetRequiredProperty("name"); - var @class = element.GetOptionalProperty("class", ""); - var opacity = element.GetOptionalProperty("opacity", 1.0f); - var visible = element.GetOptionalProperty("visible", true); - var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var offsetX = element.GetOptionalProperty("offsetx", 0.0f); - var offsetY = element.GetOptionalProperty("offsety", 0.0f); - var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); - var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - var layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); - - 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 - }; - } - - internal static ObjectLayer ReadObjectLayer( - JsonElement element, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - var id = element.GetRequiredProperty("id"); - var name = element.GetRequiredProperty("name"); - var @class = element.GetOptionalProperty("class", ""); - var opacity = element.GetOptionalProperty("opacity", 1.0f); - var visible = element.GetOptionalProperty("visible", true); - var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var offsetX = element.GetOptionalProperty("offsetx", 0.0f); - var offsetY = element.GetOptionalProperty("offsety", 0.0f); - var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); - var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - - var x = element.GetOptionalProperty("x", 0); - var y = element.GetOptionalProperty("y", 0); - var width = element.GetOptionalProperty("width", null); - var height = element.GetOptionalProperty("height", null); - var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null); - var drawOrder = element.GetOptionalPropertyParseable("draworder", s => s switch - { - "topdown" => DrawOrder.TopDown, - "index" => DrawOrder.Index, - _ => throw new JsonException($"Unknown draw order '{s}'.") - }, DrawOrder.TopDown); - - var objects = element.GetOptionalPropertyCustom>("objects", e => e.GetValueAsList(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []); - - return new ObjectLayer - { - ID = id, - Name = name, - Class = @class, - Opacity = opacity, - Visible = visible, - TintColor = tintColor, - OffsetX = offsetX, - OffsetY = offsetY, - ParallaxX = parallaxX, - ParallaxY = parallaxY, - Properties = properties, - X = x, - Y = y, - Width = width, - Height = height, - Color = color, - DrawOrder = drawOrder, - Objects = objects - }; - } - - internal static Object ReadObject( - JsonElement element, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - 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; - bool ellipseDefault = false; - bool pointDefault = false; - List? polygonDefault = null; - List? polylineDefault = null; - Dictionary? propertiesDefault = null; - - var template = element.GetOptionalProperty("template", null); - 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; - ellipseDefault = templObj is EllipseObject; - pointDefault = templObj is PointObject; - polygonDefault = (templObj is PolygonObject polygonObj) ? polygonObj.Points : null; - polylineDefault = (templObj is PolylineObject polylineObj) ? polylineObj.Points : null; - } - - var ellipse = element.GetOptionalProperty("ellipse", ellipseDefault); - var gid = element.GetOptionalProperty("gid", gidDefault); - var height = element.GetOptionalProperty("height", heightDefault); - var id = element.GetOptionalProperty("id", idDefault); - var name = element.GetOptionalProperty("name", nameDefault); - var point = element.GetOptionalProperty("point", pointDefault); - var polygon = element.GetOptionalPropertyCustom?>("polygon", e => ReadPoints(e), polygonDefault); - var polyline = element.GetOptionalPropertyCustom?>("polyline", e => ReadPoints(e), polylineDefault); - var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); - var rotation = element.GetOptionalProperty("rotation", rotationDefault); - var text = element.GetOptionalPropertyCustom("text", ReadText, null); - var type = element.GetOptionalProperty("type", typeDefault); - var visible = element.GetOptionalProperty("visible", visibleDefault); - var width = element.GetOptionalProperty("width", widthDefault); - var x = element.GetOptionalProperty("x", xDefault); - var y = element.GetOptionalProperty("y", yDefault); - - if (ellipse) - { - return new EllipseObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties - }; - } - - if (point) - { - return new PointObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties - }; - } - - if (polygon is not null) - { - return new PolygonObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties, - Points = polygon - }; - } - - if (polyline is not null) - { - return new PolylineObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties, - Points = polyline - }; - } - - if (text is not null) - { - text.ID = id; - text.Name = name; - text.Type = type; - text.X = x; - text.Y = y; - text.Width = width; - text.Height = height; - text.Rotation = rotation; - text.GID = gid; - text.Visible = visible; - text.Template = template; - text.Properties = properties; - return text; - } - - return new RectangleObject - { - ID = id, - Name = name, - Type = type, - X = x, - Y = y, - Width = width, - Height = height, - Rotation = rotation, - GID = gid, - Visible = visible, - Template = template, - Properties = properties - }; - } - - internal static List ReadPoints(JsonElement element) => - element.GetValueAsList(e => - { - var x = e.GetRequiredProperty("x"); - var y = e.GetRequiredProperty("y"); - return new Vector2(x, y); - }); - - internal static Template ReadTemplate( - JsonElement element, - Func externalTilesetResolver, - Func externalTemplateResolver, - IReadOnlyCollection customTypeDefinitions) - { - var type = element.GetRequiredProperty("type"); - var tileset = element.GetOptionalPropertyCustom("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null); - var @object = element.GetRequiredPropertyCustom("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)); - - return new Template - { - Tileset = tileset, - Object = @object - }; - } - - internal static TextObject ReadText(JsonElement element) - { - var bold = element.GetOptionalProperty("bold", false); - var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); - var fontfamily = element.GetOptionalProperty("fontfamily", "sans-serif"); - var halign = element.GetOptionalPropertyParseable("halign", s => s switch - { - "left" => TextHorizontalAlignment.Left, - "center" => TextHorizontalAlignment.Center, - "right" => TextHorizontalAlignment.Right, - _ => throw new JsonException($"Unknown horizontal alignment '{s}'.") - }, TextHorizontalAlignment.Left); - var italic = element.GetOptionalProperty("italic", false); - var kerning = element.GetOptionalProperty("kerning", true); - var pixelsize = element.GetOptionalProperty("pixelsize", 16); - var strikeout = element.GetOptionalProperty("strikeout", false); - var text = element.GetRequiredProperty("text"); - var underline = element.GetOptionalProperty("underline", false); - var valign = element.GetOptionalPropertyParseable("valign", s => s switch - { - "top" => TextVerticalAlignment.Top, - "center" => TextVerticalAlignment.Center, - "bottom" => TextVerticalAlignment.Bottom, - _ => throw new JsonException($"Unknown vertical alignment '{s}'.") - }, TextVerticalAlignment.Top); - var wrap = element.GetOptionalProperty("wrap", false); - - return new TextObject - { - Bold = bold, - Color = color, - FontFamily = fontfamily, - HorizontalAlignment = halign, - Italic = italic, - Kerning = kerning, - PixelSize = pixelsize, - Strikeout = strikeout, - Text = text, - Underline = underline, - VerticalAlignment = valign, - Wrap = wrap - }; - } } diff --git a/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs b/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs new file mode 100644 index 0000000..2fdf3c9 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static ObjectLayer ReadObjectLayer( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var @class = element.GetOptionalProperty("class", ""); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var visible = element.GetOptionalProperty("visible", true); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var parallaxX = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxY = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + + var x = element.GetOptionalProperty("x", 0); + var y = element.GetOptionalProperty("y", 0); + var width = element.GetOptionalProperty("width", null); + var height = element.GetOptionalProperty("height", null); + var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var drawOrder = element.GetOptionalPropertyParseable("draworder", s => s switch + { + "topdown" => DrawOrder.TopDown, + "index" => DrawOrder.Index, + _ => throw new JsonException($"Unknown draw order '{s}'.") + }, DrawOrder.TopDown); + + var objects = element.GetOptionalPropertyCustom>("objects", e => e.GetValueAsList(el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)), []); + + return new ObjectLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxX, + ParallaxY = parallaxY, + Properties = properties, + X = x, + Y = y, + Width = width, + Height = height, + Color = color, + DrawOrder = drawOrder, + Objects = objects + }; + } + + internal static Object ReadObject( + JsonElement element, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + 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; + bool ellipseDefault = false; + bool pointDefault = false; + List? polygonDefault = null; + List? polylineDefault = null; + Dictionary? propertiesDefault = null; + + var template = element.GetOptionalProperty("template", null); + 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; + ellipseDefault = templObj is EllipseObject; + pointDefault = templObj is PointObject; + polygonDefault = (templObj is PolygonObject polygonObj) ? polygonObj.Points : null; + polylineDefault = (templObj is PolylineObject polylineObj) ? polylineObj.Points : null; + } + + var ellipse = element.GetOptionalProperty("ellipse", ellipseDefault); + var gid = element.GetOptionalProperty("gid", gidDefault); + var height = element.GetOptionalProperty("height", heightDefault); + var id = element.GetOptionalProperty("id", idDefault); + var name = element.GetOptionalProperty("name", nameDefault); + var point = element.GetOptionalProperty("point", pointDefault); + var polygon = element.GetOptionalPropertyCustom?>("polygon", e => ReadPoints(e), polygonDefault); + var polyline = element.GetOptionalPropertyCustom?>("polyline", e => ReadPoints(e), polylineDefault); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), propertiesDefault); + var rotation = element.GetOptionalProperty("rotation", rotationDefault); + var text = element.GetOptionalPropertyCustom("text", ReadText, null); + var type = element.GetOptionalProperty("type", typeDefault); + var visible = element.GetOptionalProperty("visible", visibleDefault); + var width = element.GetOptionalProperty("width", widthDefault); + var x = element.GetOptionalProperty("x", xDefault); + var y = element.GetOptionalProperty("y", yDefault); + + if (ellipse) + { + return new EllipseObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + if (point) + { + return new PointObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + if (polygon is not null) + { + return new PolygonObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties, + Points = polygon + }; + } + + if (polyline is not null) + { + return new PolylineObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties, + Points = polyline + }; + } + + if (text is not null) + { + text.ID = id; + text.Name = name; + text.Type = type; + text.X = x; + text.Y = y; + text.Width = width; + text.Height = height; + text.Rotation = rotation; + text.GID = gid; + text.Visible = visible; + text.Template = template; + text.Properties = properties; + return text; + } + + return new RectangleObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + GID = gid, + Visible = visible, + Template = template, + Properties = properties + }; + } + + internal static List ReadPoints(JsonElement element) => + element.GetValueAsList(e => + { + var x = e.GetRequiredProperty("x"); + var y = e.GetRequiredProperty("y"); + return new Vector2(x, y); + }); + + internal static TextObject ReadText(JsonElement element) + { + var bold = element.GetOptionalProperty("bold", false); + var color = element.GetOptionalPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture), Color.Parse("#00000000", CultureInfo.InvariantCulture)); + var fontfamily = element.GetOptionalProperty("fontfamily", "sans-serif"); + var halign = element.GetOptionalPropertyParseable("halign", s => s switch + { + "left" => TextHorizontalAlignment.Left, + "center" => TextHorizontalAlignment.Center, + "right" => TextHorizontalAlignment.Right, + _ => throw new JsonException($"Unknown horizontal alignment '{s}'.") + }, TextHorizontalAlignment.Left); + var italic = element.GetOptionalProperty("italic", false); + var kerning = element.GetOptionalProperty("kerning", true); + var pixelsize = element.GetOptionalProperty("pixelsize", 16); + var strikeout = element.GetOptionalProperty("strikeout", false); + var text = element.GetRequiredProperty("text"); + var underline = element.GetOptionalProperty("underline", false); + var valign = element.GetOptionalPropertyParseable("valign", s => s switch + { + "top" => TextVerticalAlignment.Top, + "center" => TextVerticalAlignment.Center, + "bottom" => TextVerticalAlignment.Bottom, + _ => throw new JsonException($"Unknown vertical alignment '{s}'.") + }, TextVerticalAlignment.Top); + var wrap = element.GetOptionalProperty("wrap", false); + + return new TextObject + { + Bold = bold, + Color = color, + FontFamily = fontfamily, + HorizontalAlignment = halign, + Italic = italic, + Kerning = kerning, + PixelSize = pixelsize, + Strikeout = strikeout, + Text = text, + Underline = underline, + VerticalAlignment = valign, + Wrap = wrap + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Properties.cs b/DotTiled/Serialization/Tmj/Tmj.Properties.cs index 2e01749..6981521 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Properties.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Properties.cs @@ -57,7 +57,7 @@ internal partial class Tmj var propsInType = CreateInstanceOfCustomClass(ccd); var props = element.GetOptionalPropertyCustom>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); - var mergedProps = MergeProperties(propsInType, props); + var mergedProps = Helpers.MergeProperties(propsInType, props); return new ClassProperty { @@ -105,36 +105,4 @@ internal partial class Tmj { return customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone()); } - - internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) - { - if (baseProperties is null) - return overrideProperties ?? new Dictionary(); - - if (overrideProperties is null) - return baseProperties; - - var result = baseProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); - 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; - } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Template.cs b/DotTiled/Serialization/Tmj/Tmj.Template.cs new file mode 100644 index 0000000..79c7860 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Template.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + internal static Template ReadTemplate( + JsonElement element, + Func externalTilesetResolver, + Func externalTemplateResolver, + IReadOnlyCollection customTypeDefinitions) + { + var type = element.GetRequiredProperty("type"); + var tileset = element.GetOptionalPropertyCustom("tileset", el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions), null); + var @object = element.GetRequiredPropertyCustom("object", el => ReadObject(el, externalTemplateResolver, customTypeDefinitions)); + + return new Template + { + Tileset = tileset, + Object = @object + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.TileLayer.cs b/DotTiled/Serialization/Tmj/Tmj.TileLayer.cs new file mode 100644 index 0000000..5528177 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.TileLayer.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ + + internal static TileLayer ReadTileLayer( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var compression = element.GetOptionalPropertyParseable("compression", s => s switch + { + "zlib" => DataCompression.ZLib, + "gzip" => DataCompression.GZip, + "" => null, + _ => throw new JsonException($"Unsupported compression '{s}'.") + }, null); + var encoding = element.GetOptionalPropertyParseable("encoding", s => s switch + { + "csv" => DataEncoding.Csv, + "base64" => DataEncoding.Base64, + _ => throw new JsonException($"Unsupported encoding '{s}'.") + }, DataEncoding.Csv); + var chunks = element.GetOptionalPropertyCustom("chunks", e => ReadDataAsChunks(e, compression, encoding), null); + var @class = element.GetOptionalProperty("class", ""); + var data = element.GetOptionalPropertyCustom("data", e => ReadDataWithoutChunks(e, compression, encoding), null); + var height = element.GetRequiredProperty("height"); + var id = element.GetRequiredProperty("id"); + var name = element.GetRequiredProperty("name"); + var offsetX = element.GetOptionalProperty("offsetx", 0.0f); + var offsetY = element.GetOptionalProperty("offsety", 0.0f); + var opacity = element.GetOptionalProperty("opacity", 1.0f); + var parallaxx = element.GetOptionalProperty("parallaxx", 1.0f); + var parallaxy = element.GetOptionalProperty("parallaxy", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var repeatX = element.GetOptionalProperty("repeatx", false); + var repeatY = element.GetOptionalProperty("repeaty", false); + var startX = element.GetOptionalProperty("startx", 0); + var startY = element.GetOptionalProperty("starty", 0); + var tintColor = element.GetOptionalPropertyParseable("tintcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); + var visible = element.GetOptionalProperty("visible", true); + var width = element.GetRequiredProperty("width"); + var x = element.GetRequiredProperty("x"); + var y = element.GetRequiredProperty("y"); + + if ((data ?? chunks) is null) + throw new JsonException("Tile layer does not contain data."); + + return new TileLayer + { + ID = id, + Name = name, + Class = @class, + Opacity = opacity, + Visible = visible, + TintColor = tintColor, + OffsetX = offsetX, + OffsetY = offsetY, + ParallaxX = parallaxx, + ParallaxY = parallaxy, + Properties = properties, + X = x, + Y = y, + Width = width, + Height = height, + Data = data ?? chunks + }; + } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs index 47b8e17..fd5088b 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -79,7 +79,7 @@ internal partial class Tmj var imageModel = new Image { - Format = ParseImageFormatFromSource(image!), + Format = Helpers.ParseImageFormatFromSource(image!), Source = image, Height = imageHeight, Width = imageWidth, @@ -112,20 +112,6 @@ internal partial class Tmj }; } - private static ImageFormat ParseImageFormatFromSource(string source) - { - var extension = Path.GetExtension(source).ToLowerInvariant(); - return extension switch - { - ".png" => ImageFormat.Png, - ".gif" => ImageFormat.Gif, - ".jpg" => ImageFormat.Jpg, - ".jpeg" => ImageFormat.Jpg, - ".bmp" => ImageFormat.Bmp, - _ => throw new JsonException($"Unsupported image format '{extension}'") - }; - } - internal static Grid ReadGrid(JsonElement element) { var orientation = element.GetOptionalPropertyParseable("orientation", s => s switch @@ -163,7 +149,7 @@ internal partial class Tmj IReadOnlyCollection customTypeDefinitions) => element.GetValueAsList(e => { - var animation = e.GetOptionalPropertyCustom>("animation", e => e.GetValueAsList(ReadFrame), null); + var animation = e.GetOptionalPropertyCustom?>("animation", e => e.GetValueAsList(ReadFrame), null); var id = e.GetRequiredProperty("id"); var image = e.GetOptionalProperty("image", null); var imageHeight = e.GetOptionalProperty("imageheight", null); @@ -180,7 +166,7 @@ internal partial class Tmj var imageModel = image != null ? new Image { - Format = ParseImageFormatFromSource(image), + Format = Helpers.ParseImageFormatFromSource(image), Source = image, Height = imageHeight ?? 0, Width = imageWidth ?? 0 @@ -213,4 +199,61 @@ internal partial class Tmj TileID = tileID }; } + + internal static Wangset ReadWangset( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var @clalss = element.GetOptionalProperty("class", ""); + var colors = element.GetOptionalPropertyCustom>("colors", e => e.GetValueAsList(el => ReadWangColor(el, customTypeDefinitions)), []); + var name = element.GetRequiredProperty("name"); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var tile = element.GetOptionalProperty("tile", 0); + var type = element.GetOptionalProperty("type", ""); + var wangTiles = element.GetOptionalPropertyCustom>("wangtiles", e => e.GetValueAsList(ReadWangTile), []); + + return new Wangset + { + Class = @clalss, + WangColors = colors, + Name = name, + Properties = properties, + Tile = tile, + WangTiles = wangTiles + }; + } + + internal static WangColor ReadWangColor( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) + { + var @class = element.GetOptionalProperty("class", ""); + var color = element.GetRequiredPropertyParseable("color", s => Color.Parse(s, CultureInfo.InvariantCulture)); + var name = element.GetRequiredProperty("name"); + var probability = element.GetOptionalProperty("probability", 1.0f); + var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); + var tile = element.GetOptionalProperty("tile", 0); + + return new WangColor + { + Class = @class, + Color = color, + Name = name, + Probability = probability, + Properties = properties, + Tile = tile + }; + } + + internal static WangTile ReadWangTile(JsonElement element) + { + var tileID = element.GetRequiredProperty("tileid"); + var wangID = element.GetOptionalPropertyCustom>("wangid", e => e.GetValueAsList(el => (byte)el.GetUInt32()), []); + + return new WangTile + { + TileID = tileID, + WangID = [.. wangID] + }; + } } diff --git a/DotTiled/Serialization/Tmj/Tmj.Wangset.cs b/DotTiled/Serialization/Tmj/Tmj.Wangset.cs new file mode 100644 index 0000000..cf9f024 --- /dev/null +++ b/DotTiled/Serialization/Tmj/Tmj.Wangset.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.Json; + +namespace DotTiled; + +internal partial class Tmj +{ +} diff --git a/DotTiled/Serialization/Tmx/Tmx.Helpers.cs b/DotTiled/Serialization/Tmx/Tmx.Helpers.cs deleted file mode 100644 index bfc04f4..0000000 --- a/DotTiled/Serialization/Tmx/Tmx.Helpers.cs +++ /dev/null @@ -1,26 +0,0 @@ -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.ObjectLayer.cs b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs index 4fb0ce6..2367974 100644 --- a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs @@ -128,7 +128,7 @@ internal partial class Tmx reader.ProcessChildren("object", (r, elementName) => elementName switch { - "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "Properties", ref propertiesCounter), + "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "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"), @@ -158,38 +158,6 @@ internal partial class Tmx 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(); diff --git a/DotTiled/Serialization/Tmx/Tmx.Properties.cs b/DotTiled/Serialization/Tmx/Tmx.Properties.cs index 0de4adf..6ea65e5 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Properties.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Properties.cs @@ -56,7 +56,7 @@ internal partial class Tmx var propsInType = CreateInstanceOfCustomClass(ccd); var props = ReadProperties(reader, customTypeDefinitions); - var mergedProps = MergeProperties(propsInType, props); + var mergedProps = Helpers.MergeProperties(propsInType, props); reader.ReadEndElement(); return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps }; From 9117b451dd70869bea7a0d71a67bf14bf1cf5d9c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 11 Aug 2024 17:05:52 +0200 Subject: [PATCH 25/39] Move custom types files --- .../large-propertytypes.json} | 0 .../{ => CustomTypes}/map-with-object-template-propertytypes.json | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename DotTiled.Tests/Serialization/TestData/{propertytypes-2.json => CustomTypes/large-propertytypes.json} (100%) rename DotTiled.Tests/Serialization/TestData/{ => CustomTypes}/map-with-object-template-propertytypes.json (100%) diff --git a/DotTiled.Tests/Serialization/TestData/propertytypes-2.json b/DotTiled.Tests/Serialization/TestData/CustomTypes/large-propertytypes.json similarity index 100% rename from DotTiled.Tests/Serialization/TestData/propertytypes-2.json rename to DotTiled.Tests/Serialization/TestData/CustomTypes/large-propertytypes.json diff --git a/DotTiled.Tests/Serialization/TestData/map-with-object-template-propertytypes.json b/DotTiled.Tests/Serialization/TestData/CustomTypes/map-with-object-template-propertytypes.json similarity index 100% rename from DotTiled.Tests/Serialization/TestData/map-with-object-template-propertytypes.json rename to DotTiled.Tests/Serialization/TestData/CustomTypes/map-with-object-template-propertytypes.json From 4aebf1d67b8735afd1e5525b483f51562f49c4c9 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 21:24:09 +0200 Subject: [PATCH 26/39] Remove old maps from tests and seperate model more --- DotTiled.Benchmark/Program.cs | 2 +- DotTiled.Tests/Serialization/TestData.cs | 2 + .../TestData/Map/default-map/default-map.cs | 96 ++++++++++ .../default-map.tmj} | 0 .../default-map.tmx} | 0 .../TestData/Map/empty-map-base64-gzip.tmj | 30 --- .../TestData/Map/empty-map-base64-gzip.tmx | 8 - .../TestData/Map/empty-map-base64-zlib.tmj | 30 --- .../TestData/Map/empty-map-base64-zlib.tmx | 8 - .../TestData/Map/empty-map-base64.tmj | 30 --- .../TestData/Map/empty-map-base64.tmx | 8 - .../TestData/Map/empty-map-properties.cs | 56 ------ .../TestData/Map/empty-map-properties.tmj | 69 ------- .../TestData/Map/empty-map-properties.tmx | 21 --- .../Serialization/TestData/Map/empty-map.cs | 47 ----- .../TestData/Map/map-with-group.cs | 94 ---------- .../TestData/Map/map-with-group.tmj | 80 -------- .../TestData/Map/map-with-group.tmx | 26 --- .../TestData/Map/map-with-object-template.cs | 125 ------------- .../TestData/Map/map-with-object-template.tmj | 104 ----------- .../TestData/Map/map-with-object-template.tmx | 35 ---- .../TestData/Map/simple-tileset-embed.cs | 65 ------- .../TestData/Map/simple-tileset-embed.tmj | 45 ----- .../TestData/Map/simple-tileset-embed.tmx | 15 -- .../Template/map-with-object-template.tj | 28 --- .../Template/map-with-object-template.tx | 14 -- .../Serialization/Tmj/TmjMapReaderTests.cs | 37 +--- .../Serialization/Tmx/TmxMapReaderTests.cs | 148 +-------------- DotTiled/Model/IProperty.cs | 173 ------------------ DotTiled/Model/Map.cs | 1 - DotTiled/Model/Properties/BoolProperty.cs | 14 ++ DotTiled/Model/Properties/ClassProperty.cs | 19 ++ DotTiled/Model/Properties/ColorProperty.cs | 14 ++ .../CustomTypes/CustomClassDefinition.cs | 27 +++ .../CustomTypes/CustomEnumDefinition.cs | 16 ++ .../CustomTypes/CustomTypeDefinition.cs | 7 + DotTiled/Model/Properties/FileProperty.cs | 14 ++ DotTiled/Model/Properties/FloatProperty.cs | 14 ++ DotTiled/Model/Properties/IProperty.cs | 9 + DotTiled/Model/Properties/IntProperty.cs | 14 ++ DotTiled/Model/Properties/ObjectProperty.cs | 14 ++ DotTiled/Model/Properties/PropertyType.cs | 13 ++ DotTiled/Model/Properties/StringProperty.cs | 14 ++ 43 files changed, 290 insertions(+), 1296 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs rename DotTiled.Tests/Serialization/TestData/Map/{empty-map-csv.tmj => default-map/default-map.tmj} (100%) rename DotTiled.Tests/Serialization/TestData/Map/{empty-map-csv.tmx => default-map/default-map.tmx} (100%) delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/empty-map.cs delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj delete mode 100644 DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx delete mode 100644 DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj delete mode 100644 DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx delete mode 100644 DotTiled/Model/IProperty.cs create mode 100644 DotTiled/Model/Properties/BoolProperty.cs create mode 100644 DotTiled/Model/Properties/ClassProperty.cs create mode 100644 DotTiled/Model/Properties/ColorProperty.cs create mode 100644 DotTiled/Model/Properties/CustomTypes/CustomClassDefinition.cs create mode 100644 DotTiled/Model/Properties/CustomTypes/CustomEnumDefinition.cs create mode 100644 DotTiled/Model/Properties/CustomTypes/CustomTypeDefinition.cs create mode 100644 DotTiled/Model/Properties/FileProperty.cs create mode 100644 DotTiled/Model/Properties/FloatProperty.cs create mode 100644 DotTiled/Model/Properties/IProperty.cs create mode 100644 DotTiled/Model/Properties/IntProperty.cs create mode 100644 DotTiled/Model/Properties/ObjectProperty.cs create mode 100644 DotTiled/Model/Properties/PropertyType.cs create mode 100644 DotTiled/Model/Properties/StringProperty.cs diff --git a/DotTiled.Benchmark/Program.cs b/DotTiled.Benchmark/Program.cs index 8397495..b432b7e 100644 --- a/DotTiled.Benchmark/Program.cs +++ b/DotTiled.Benchmark/Program.cs @@ -1,4 +1,4 @@ -ïŧŋusing System; +using System; using System.Collections.Immutable; using System.Security.Cryptography; using System.Text; diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index d31956f..9b3b13f 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -6,6 +6,8 @@ public static partial class TestData { public static XmlReader GetXmlReaderFor(string testDataFile) { + var names = typeof(TestData).Assembly.GetManifestResourceNames(); + var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); diff --git a/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs b/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs new file mode 100644 index 0000000..44c909e --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs @@ -0,0 +1,96 @@ +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map DefaultMap() => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = new Color { R = 0, G = 0, B = 0, A = 0 }, + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + FlippingFlags = [ + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None, + FlippingFlags.None + ] + } + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj b/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj similarity index 100% rename from DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmj rename to DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmx b/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx similarity index 100% rename from DotTiled.Tests/Serialization/TestData/Map/empty-map-csv.tmx rename to DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj deleted file mode 100644 index de94421..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmj +++ /dev/null @@ -1,30 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "compression":"gzip", - "data":"H4sIAAAAAAAACmNgoD0AAMrGiJlkAAAA", - "encoding":"base64", - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }], - "nextlayerid":2, - "nextobjectid":1, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx deleted file mode 100644 index d3e0f29..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-gzip.tmx +++ /dev/null @@ -1,8 +0,0 @@ - - - - - H4sIAAAAAAAACmNgoD0AAMrGiJlkAAAA - - - diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj deleted file mode 100644 index 4cf9e84..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmj +++ /dev/null @@ -1,30 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "compression":"zlib", - "data":"eJxjYKA9AAAAZAAB", - "encoding":"base64", - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }], - "nextlayerid":2, - "nextobjectid":1, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx deleted file mode 100644 index d35b438..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64-zlib.tmx +++ /dev/null @@ -1,8 +0,0 @@ - - - - - eJxjYKA9AAAAZAAB - - - diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj deleted file mode 100644 index b13707c..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmj +++ /dev/null @@ -1,30 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "compression":"", - "data":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "encoding":"base64", - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }], - "nextlayerid":2, - "nextobjectid":1, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx deleted file mode 100644 index 0e98f67..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-base64.tmx +++ /dev/null @@ -1,8 +0,0 @@ - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - - diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs deleted file mode 100644 index 79df5a5..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace DotTiled.Tests; - -public partial class TestData -{ - public static Map EmptyMapWithProperties() => new Map - { - Version = "1.10", - TiledVersion = "1.11.0", - Orientation = MapOrientation.Orthogonal, - RenderOrder = RenderOrder.RightDown, - Width = 5, - Height = 5, - TileWidth = 32, - TileHeight = 32, - Infinite = false, - NextLayerID = 2, - NextObjectID = 1, - Layers = [ - new TileLayer - { - ID = 1, - Name = "Tile Layer 1", - Width = 5, - Height = 5, - Data = new Data - { - Encoding = DataEncoding.Csv, - GlobalTileIDs = [ - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0 - ], - FlippingFlags = [ - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None - ] - } - } - ], - Properties = new Dictionary - { - ["MapBool"] = new BoolProperty { Name = "MapBool", Value = true }, - ["MapColor"] = new ColorProperty { Name = "MapColor", Value = new Color { R = 255, G = 0, B = 0, A = 255 } }, - ["MapFile"] = new FileProperty { Name = "MapFile", Value = "file.png" }, - ["MapFloat"] = new FloatProperty { Name = "MapFloat", Value = 5.2f }, - ["MapInt"] = new IntProperty { Name = "MapInt", Value = 42 }, - ["MapObject"] = new ObjectProperty { Name = "MapObject", Value = 5 }, - ["MapString"] = new StringProperty { Name = "MapString", Value = "string in map" } - } - }; -} diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj deleted file mode 100644 index 237546d..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmj +++ /dev/null @@ -1,69 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "data":[0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0], - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }], - "nextlayerid":2, - "nextobjectid":1, - "orientation":"orthogonal", - "properties":[ - { - "name":"MapBool", - "type":"bool", - "value":true - }, - { - "name":"MapColor", - "type":"color", - "value":"#ffff0000" - }, - { - "name":"MapFile", - "type":"file", - "value":"file.png" - }, - { - "name":"MapFloat", - "type":"float", - "value":5.2 - }, - { - "name":"MapInt", - "type":"int", - "value":42 - }, - - { - "name":"MapObject", - "type":"object", - "value":5 - }, - { - "name":"MapString", - "type":"string", - "value":"string in map" - }], - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx b/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx deleted file mode 100644 index 5a14d94..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map-properties.tmx +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0 - - - diff --git a/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs b/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs deleted file mode 100644 index b51aeba..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/empty-map.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace DotTiled.Tests; - -public partial class TestData -{ - public static Map EmptyMapWithEncodingAndCompression(DataEncoding dataEncoding, DataCompression? compression) => new Map - { - Version = "1.10", - TiledVersion = "1.11.0", - Orientation = MapOrientation.Orthogonal, - RenderOrder = RenderOrder.RightDown, - Width = 5, - Height = 5, - TileWidth = 32, - TileHeight = 32, - Infinite = false, - NextLayerID = 2, - NextObjectID = 1, - Layers = [ - new TileLayer - { - ID = 1, - Name = "Tile Layer 1", - Width = 5, - Height = 5, - Data = new Data - { - Encoding = dataEncoding, - Compression = compression, - GlobalTileIDs = [ - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0 - ], - FlippingFlags = [ - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None - ] - } - } - ] - }; -} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs deleted file mode 100644 index 14a2c8c..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace DotTiled.Tests; - -public partial class TestData -{ - public static Map MapWithGroup() => new Map - { - Version = "1.10", - TiledVersion = "1.11.0", - Orientation = MapOrientation.Orthogonal, - RenderOrder = RenderOrder.RightDown, - Width = 5, - Height = 5, - TileWidth = 32, - TileHeight = 32, - Infinite = false, - NextLayerID = 5, - NextObjectID = 2, - Layers = [ - new TileLayer - { - ID = 4, - Name = "Tile Layer 2", - Width = 5, - Height = 5, - Data = new Data - { - Encoding = DataEncoding.Csv, - GlobalTileIDs = [ - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0 - ], - FlippingFlags = [ - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None - ] - } - }, - new Group - { - ID = 3, - Name = "Group 1", - Layers = [ - new TileLayer - { - ID = 1, - Name = "Tile Layer 1", - Width = 5, - Height = 5, - Data = new Data - { - Encoding = DataEncoding.Csv, - GlobalTileIDs = [ - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0 - ], - FlippingFlags = [ - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None - ] - } - }, - new ObjectLayer - { - ID = 2, - Name = "Object Layer 1", - Objects = [ - new RectangleObject - { - ID = 1, - Name = "Name", - X = 35.5f, - Y = 26, - Width = 64.5f, - Height = 64.5f, - } - ] - } - ] - } - ] - }; -} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj deleted file mode 100644 index 6bce8c8..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmj +++ /dev/null @@ -1,80 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "data":[0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0], - "height":5, - "id":4, - "name":"Tile Layer 2", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }, - { - "id":3, - "layers":[ - { - "data":[0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0], - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }, - { - "draworder":"topdown", - "id":2, - "name":"Object Layer 1", - "objects":[ - { - "height":64.5, - "id":1, - "name":"Name", - "rotation":0, - "type":"", - "visible":true, - "width":64.5, - "x":35.5, - "y":26 - }], - "opacity":1, - "type":"objectgroup", - "visible":true, - "x":0, - "y":0 - }], - "name":"Group 1", - "opacity":1, - "type":"group", - "visible":true, - "x":0, - "y":0 - }], - "nextlayerid":5, - "nextobjectid":2, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx deleted file mode 100644 index 61191c4..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-group.tmx +++ /dev/null @@ -1,26 +0,0 @@ - - - - -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0 - - - - - -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0 - - - - - - - diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs deleted file mode 100644 index 1f9cb1c..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace DotTiled.Tests; - -public partial class TestData -{ - public static Map MapWithObjectTemplate(string templateExtension) => new Map - { - Version = "1.10", - TiledVersion = "1.11.0", - Orientation = MapOrientation.Orthogonal, - RenderOrder = RenderOrder.RightDown, - Width = 5, - Height = 5, - TileWidth = 32, - TileHeight = 32, - Infinite = false, - NextLayerID = 3, - NextObjectID = 3, - Layers = [ - new TileLayer - { - ID = 1, - Name = "Tile Layer 1", - Width = 5, - Height = 5, - Data = new Data - { - Encoding = DataEncoding.Csv, - GlobalTileIDs = [ - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0, - 0,0,0,0,0 - ], - FlippingFlags = [ - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None - ] - } - }, - new ObjectLayer - { - ID = 2, - Name = "Object Layer 1", - Objects = [ - new RectangleObject - { - ID = 1, - Template = $"map-with-object-template.{templateExtension}", - Name = "Thingy 2", - X = 94.5749f, - Y = 33.6842f, - Width = 37.0156f, - Height = 37.0156f, - Properties = new Dictionary - { - ["Bool"] = new BoolProperty { Name = "Bool", Value = true }, - ["TestClassInTemplate"] = new ClassProperty - { - Name = "TestClassInTemplate", - PropertyType = "TestClass", - Properties = new Dictionary - { - ["Amount"] = new FloatProperty { Name = "Amount", Value = 37 }, - ["Name"] = new StringProperty { Name = "Name", Value = "I am here" } - } - } - } - }, - new RectangleObject - { - ID = 2, - Template = $"map-with-object-template.{templateExtension}", - Name = "Thingy", - X = 29.7976f, - Y = 33.8693f, - Width = 37.0156f, - Height = 37.0156f, - Properties = new Dictionary - { - ["Bool"] = new BoolProperty { Name = "Bool", Value = true }, - ["TestClassInTemplate"] = new ClassProperty - { - Name = "TestClassInTemplate", - PropertyType = "TestClass", - Properties = new Dictionary - { - ["Amount"] = new FloatProperty { Name = "Amount", Value = 4.2f }, - ["Name"] = new StringProperty { Name = "Name", Value = "Hello there" } - } - } - } - }, - new RectangleObject - { - ID = 3, - Template = $"map-with-object-template.{templateExtension}", - Name = "Thingy 3", - X = 5, - Y = 5, - Width = 37.0156f, - Height = 37.0156f, - Properties = new Dictionary - { - ["Bool"] = new BoolProperty { Name = "Bool", Value = true }, - ["TestClassInTemplate"] = new ClassProperty - { - Name = "TestClassInTemplate", - PropertyType = "TestClass", - Properties = new Dictionary - { - ["Amount"] = new FloatProperty { Name = "Amount", Value = 0.0f }, - ["Name"] = new StringProperty { Name = "Name", Value = "I am here 3" } - } - } - } - } - ] - } - ] - }; -} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj deleted file mode 100644 index 398403b..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmj +++ /dev/null @@ -1,104 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "data":[0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0], - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }, - { - "draworder":"topdown", - "id":2, - "name":"Object Layer 1", - "objects":[ - { - "height":37.0156, - "id":1, - "template":"map-with-object-template.tj", - "name":"Thingy 2", - "properties":[ - { - "name":"Bool", - "type":"bool", - "value":true - }, - { - "name":"TestClassInTemplate", - "propertytype":"TestClass", - "type":"class", - "value": - { - "Amount":37, - "Name":"I am here" - } - }], - "rotation":0, - "type":"", - "visible":true, - "width":37.0156, - "x":94.5749, - "y":33.6842 - }, - { - "id":2, - "template":"map-with-object-template.tj", - "x":29.7976, - "y":33.8693 - }, - { - "height":37.0156, - "id":3, - "template":"map-with-object-template.tj", - "name":"Thingy 3", - "properties":[ - { - "name":"Bool", - "type":"bool", - "value":true - }, - { - "name":"TestClassInTemplate", - "propertytype":"TestClass", - "type":"class", - "value": - { - "Name":"I am here 3" - } - }], - "rotation":0, - "type":"", - "visible":true, - "width":37.0156, - "x":5, - "y":5 - }], - "opacity":1, - "type":"objectgroup", - "visible":true, - "x":0, - "y":0 - }], - "nextlayerid":3, - "nextobjectid":3, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx deleted file mode 100644 index 83716a0..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-object-template.tmx +++ /dev/null @@ -1,35 +0,0 @@ - - - - -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0, -0,0,0,0,0 - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs deleted file mode 100644 index d6a5f10..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace DotTiled.Tests; - -public partial class TestData -{ - public static Map SimpleMapWithEmbeddedTileset() => new Map - { - Version = "1.10", - TiledVersion = "1.11.0", - Orientation = MapOrientation.Orthogonal, - RenderOrder = RenderOrder.RightDown, - Width = 5, - Height = 5, - TileWidth = 32, - TileHeight = 32, - Infinite = false, - NextLayerID = 2, - NextObjectID = 1, - Tilesets = [ - new Tileset - { - FirstGID = 1, - Name = "Tileset 1", - TileWidth = 32, - TileHeight = 32, - TileCount = 8, - Columns = 4, - Image = new Image - { - Format = ImageFormat.Png, - Source = "tiles.png", - Width = 128, - Height = 64 - } - } - ], - Layers = [ - new TileLayer - { - ID = 1, - Name = "Tile Layer 1", - Width = 5, - Height = 5, - Data = new Data - { - Encoding = DataEncoding.Csv, - Compression = null, - GlobalTileIDs = [ - 1,1,1,1,1, - 1,1,1,1,1, - 1,1,1,1,1, - 2,2,2,2,2, - 2,2,2,2,2 - ], - FlippingFlags = [ - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, - FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None - ] - }, - } - ] - }; -} diff --git a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj deleted file mode 100644 index fa5a4ef..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmj +++ /dev/null @@ -1,45 +0,0 @@ -{ "compressionlevel":-1, - "height":5, - "infinite":false, - "layers":[ - { - "data":[1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2], - "height":5, - "id":1, - "name":"Tile Layer 1", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":5, - "x":0, - "y":0 - }], - "nextlayerid":2, - "nextobjectid":1, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.11.0", - "tileheight":32, - "tilesets":[ - { - "columns":4, - "firstgid":1, - "image":"tiles.png", - "imageheight":64, - "imagewidth":128, - "margin":0, - "name":"Tileset 1", - "spacing":0, - "tilecount":8, - "tileheight":32, - "tilewidth":32 - }], - "tilewidth":32, - "type":"map", - "version":"1.10", - "width":5 -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx b/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx deleted file mode 100644 index 3d91b9d..0000000 --- a/DotTiled.Tests/Serialization/TestData/Map/simple-tileset-embed.tmx +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - -1,1,1,1,1, -1,1,1,1,1, -1,1,1,1,1, -2,2,2,2,2, -2,2,2,2,2 - - - diff --git a/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj deleted file mode 100644 index ec2b065..0000000 --- a/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tj +++ /dev/null @@ -1,28 +0,0 @@ -{ "object": - { - "height":37.0156, - "id":2, - "name":"Thingy", - "properties":[ - { - "name":"Bool", - "type":"bool", - "value":true - }, - { - "name":"TestClassInTemplate", - "propertytype":"TestClass", - "type":"class", - "value": - { - "Amount":4.2, - "Name":"Hello there" - } - }], - "rotation":0, - "type":"", - "visible":true, - "width":37.0156 - }, - "type":"template" -} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx b/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx deleted file mode 100644 index 3039be2..0000000 --- a/DotTiled.Tests/Serialization/TestData/Template/map-with-object-template.tx +++ /dev/null @@ -1,14 +0,0 @@ - - diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 7e220a9..e04423b 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -2,44 +2,9 @@ namespace DotTiled.Tests; public partial class TmjMapReaderTests { - public static IEnumerable DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data => - [ - ["Serialization.TestData.Map.empty-map-csv.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], - ["Serialization.TestData.Map.empty-map-base64.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], - ["Serialization.TestData.Map.empty-map-base64-gzip.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], - ["Serialization.TestData.Map.empty-map-base64-zlib.tmj", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], - ["Serialization.TestData.Map.simple-tileset-embed.tmj", TestData.SimpleMapWithEmbeddedTileset()], - ["Serialization.TestData.Map.empty-map-properties.tmj", TestData.EmptyMapWithProperties()], - ]; - - [Theory] - [MemberData(nameof(DeserializeMap_ValidTmjNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] - public void TmxMapReaderReadMap_ValidTmjNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) - { - // Arrange - var json = TestData.GetRawStringFor(testDataFile); - static Template ResolveTemplate(string source) - { - throw new NotSupportedException("External templates are not supported in this test."); - } - static Tileset ResolveTileset(string source) - { - throw new NotSupportedException("External tilesets are not supported in this test."); - } - using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, []); - - // Act - var map = mapReader.ReadMap(); - - // Assert - Assert.NotNull(map); - DotTiledAssert.AssertMap(expectedMap, map); - } - public static IEnumerable DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => [ - ["Serialization.TestData.Map.map-with-object-template.tmj", TestData.MapWithObjectTemplate("tj")], - ["Serialization.TestData.Map.map-with-group.tmj", TestData.MapWithGroup()], + ["Serialization.TestData.Map.default_map.default-map.tmj", TestData.DefaultMap()] ]; [Theory] diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 3556893..5ee162f 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -4,155 +4,9 @@ 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.TestData.Map.empty-map-csv.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Csv, null)], - ["Serialization.TestData.Map.empty-map-base64.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, null)], - ["Serialization.TestData.Map.empty-map-base64-gzip.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.GZip)], - ["Serialization.TestData.Map.empty-map-base64-zlib.tmx", TestData.EmptyMapWithEncodingAndCompression(DataEncoding.Base64, DataCompression.ZLib)], - ["Serialization.TestData.Map.simple-tileset-embed.tmx", TestData.SimpleMapWithEmbeddedTileset()], - ["Serialization.TestData.Map.empty-map-properties.tmx", TestData.EmptyMapWithProperties()], - ]; - - [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlNoExternalTilesets_ReturnsMapWithoutThrowing_Data))] - public void TmxMapReaderReadMap_ValidXmlNoExternalTilesets_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) - { - // Arrange - CustomTypeDefinition[] customTypeDefinitions = [ - new CustomClassDefinition - { - Name = "TestClass", - ID = 1, - UseAs = CustomClassUseAs.Property, - Members = [ - new StringProperty - { - Name = "Name", - Value = "" - }, - new FloatProperty - { - Name = "Amount", - Value = 0f - } - ] - }, - new CustomClassDefinition - { - Name = "Test", - ID = 2, - UseAs = CustomClassUseAs.All, - Members = [ - new ClassProperty - { - Name = "Yep", - PropertyType = "TestClass", - Properties = [] - } - ] - } - ]; - - using var reader = TestData.GetXmlReaderFor(testDataFile); - Template ResolveTemplate(string source) - { - using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.TestData.Template.{source}"); - using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions); - return templateReader.ReadTemplate(); - } - Tileset ResolveTileset(string source) - { - using var xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); - using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions); - return tilesetReader.ReadTileset(); - } - using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, customTypeDefinitions); - - // Act - var map = mapReader.ReadMap(); - - // Assert - Assert.NotNull(map); - DotTiledAssert.AssertMap(expectedMap, map); - } - public static IEnumerable DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => [ - ["Serialization.TestData.Map.map-with-object-template.tmx", TestData.MapWithObjectTemplate("tx")], - ["Serialization.TestData.Map.map-with-group.tmx", TestData.MapWithGroup()], + ["Serialization.TestData.Map.default_map.default-map.tmx", TestData.DefaultMap()] ]; [Theory] diff --git a/DotTiled/Model/IProperty.cs b/DotTiled/Model/IProperty.cs deleted file mode 100644 index ae522f2..0000000 --- a/DotTiled/Model/IProperty.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace DotTiled; - -public enum PropertyType -{ - String, - Int, - Float, - Bool, - Color, - File, - Object, - Class -} - -public interface IProperty -{ - public string Name { get; set; } - public PropertyType Type { get; } - - IProperty Clone(); -} - -public class StringProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.String; - public required string Value { get; set; } - - public IProperty Clone() => new StringProperty - { - Name = Name, - Value = Value - }; -} - -public class IntProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.Int; - public required int Value { get; set; } - - public IProperty Clone() => new IntProperty - { - Name = Name, - Value = Value - }; -} - -public class FloatProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.Float; - public required float Value { get; set; } - - public IProperty Clone() => new FloatProperty - { - Name = Name, - Value = Value - }; -} - -public class BoolProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.Bool; - public required bool Value { get; set; } - - public IProperty Clone() => new BoolProperty - { - Name = Name, - Value = Value - }; -} - -public class ColorProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.Color; - public required Color Value { get; set; } - - public IProperty Clone() => new ColorProperty - { - Name = Name, - Value = Value - }; -} - -public class FileProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.File; - public required string Value { get; set; } - - public IProperty Clone() => new FileProperty - { - Name = Name, - Value = Value - }; -} - -public class ObjectProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => PropertyType.Object; - public required uint Value { get; set; } - - public IProperty Clone() => new ObjectProperty - { - Name = Name, - Value = Value - }; -} - -public class ClassProperty : IProperty -{ - public required string Name { get; set; } - public PropertyType Type => DotTiled.PropertyType.Class; - public required string PropertyType { get; set; } - public required Dictionary Properties { get; set; } - - public IProperty Clone() => new ClassProperty - { - Name = Name, - PropertyType = PropertyType, - Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone()) - }; -} - -public abstract class CustomTypeDefinition -{ - public uint ID { get; set; } - public string Name { get; set; } = ""; -} - -[Flags] -public enum CustomClassUseAs -{ - Property, - Map, - Layer, - Object, - Tile, - Tileset, - WangColor, - Wangset, - Project, - All = Property | Map | Layer | Object | Tile | Tileset | WangColor | Wangset | Project -} - -public class CustomClassDefinition : CustomTypeDefinition -{ - public Color Color { get; set; } - public bool DrawFill { get; set; } - public CustomClassUseAs UseAs { get; set; } - public List Members { get; set; } -} - -public enum CustomEnumStorageType -{ - Int, - String -} - -public class CustomEnumDefinition : CustomTypeDefinition -{ - public CustomEnumStorageType StorageType { get; set; } - public List Values { get; set; } = []; - public bool ValueAsFlags { get; set; } -} diff --git a/DotTiled/Model/Map.cs b/DotTiled/Model/Map.cs index 246f21c..fdcdbd1 100644 --- a/DotTiled/Model/Map.cs +++ b/DotTiled/Model/Map.cs @@ -60,5 +60,4 @@ public class Map // Any number of public List Tilesets { get; set; } = []; public List Layers { get; set; } = []; - public List Groups { get; set; } = []; } diff --git a/DotTiled/Model/Properties/BoolProperty.cs b/DotTiled/Model/Properties/BoolProperty.cs new file mode 100644 index 0000000..949858f --- /dev/null +++ b/DotTiled/Model/Properties/BoolProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class BoolProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.Bool; + public required bool Value { get; set; } + + public IProperty Clone() => new BoolProperty + { + Name = Name, + Value = Value + }; +} diff --git a/DotTiled/Model/Properties/ClassProperty.cs b/DotTiled/Model/Properties/ClassProperty.cs new file mode 100644 index 0000000..0b1391d --- /dev/null +++ b/DotTiled/Model/Properties/ClassProperty.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; + +namespace DotTiled; + +public class ClassProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => DotTiled.PropertyType.Class; + public required string PropertyType { get; set; } + public required Dictionary Properties { get; set; } + + public IProperty Clone() => new ClassProperty + { + Name = Name, + PropertyType = PropertyType, + Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone()) + }; +} diff --git a/DotTiled/Model/Properties/ColorProperty.cs b/DotTiled/Model/Properties/ColorProperty.cs new file mode 100644 index 0000000..07ca25e --- /dev/null +++ b/DotTiled/Model/Properties/ColorProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class ColorProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.Color; + public required Color Value { get; set; } + + public IProperty Clone() => new ColorProperty + { + Name = Name, + Value = Value + }; +} diff --git a/DotTiled/Model/Properties/CustomTypes/CustomClassDefinition.cs b/DotTiled/Model/Properties/CustomTypes/CustomClassDefinition.cs new file mode 100644 index 0000000..ec92b3f --- /dev/null +++ b/DotTiled/Model/Properties/CustomTypes/CustomClassDefinition.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace DotTiled; + +[Flags] +public enum CustomClassUseAs +{ + Property, + Map, + Layer, + Object, + Tile, + Tileset, + WangColor, + Wangset, + Project, + All = Property | Map | Layer | Object | Tile | Tileset | WangColor | Wangset | Project +} + +public class CustomClassDefinition : CustomTypeDefinition +{ + public Color? Color { get; set; } + public bool DrawFill { get; set; } + public CustomClassUseAs UseAs { get; set; } + public List Members { get; set; } = []; +} diff --git a/DotTiled/Model/Properties/CustomTypes/CustomEnumDefinition.cs b/DotTiled/Model/Properties/CustomTypes/CustomEnumDefinition.cs new file mode 100644 index 0000000..d570442 --- /dev/null +++ b/DotTiled/Model/Properties/CustomTypes/CustomEnumDefinition.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace DotTiled; + +public enum CustomEnumStorageType +{ + Int, + String +} + +public class CustomEnumDefinition : CustomTypeDefinition +{ + public CustomEnumStorageType StorageType { get; set; } + public List Values { get; set; } = []; + public bool ValueAsFlags { get; set; } +} diff --git a/DotTiled/Model/Properties/CustomTypes/CustomTypeDefinition.cs b/DotTiled/Model/Properties/CustomTypes/CustomTypeDefinition.cs new file mode 100644 index 0000000..1f50462 --- /dev/null +++ b/DotTiled/Model/Properties/CustomTypes/CustomTypeDefinition.cs @@ -0,0 +1,7 @@ +namespace DotTiled; + +public abstract class CustomTypeDefinition +{ + public uint ID { get; set; } + public string Name { get; set; } = ""; +} diff --git a/DotTiled/Model/Properties/FileProperty.cs b/DotTiled/Model/Properties/FileProperty.cs new file mode 100644 index 0000000..edc939c --- /dev/null +++ b/DotTiled/Model/Properties/FileProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class FileProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.File; + public required string Value { get; set; } + + public IProperty Clone() => new FileProperty + { + Name = Name, + Value = Value + }; +} diff --git a/DotTiled/Model/Properties/FloatProperty.cs b/DotTiled/Model/Properties/FloatProperty.cs new file mode 100644 index 0000000..469cc45 --- /dev/null +++ b/DotTiled/Model/Properties/FloatProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class FloatProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.Float; + public required float Value { get; set; } + + public IProperty Clone() => new FloatProperty + { + Name = Name, + Value = Value + }; +} diff --git a/DotTiled/Model/Properties/IProperty.cs b/DotTiled/Model/Properties/IProperty.cs new file mode 100644 index 0000000..f4294cd --- /dev/null +++ b/DotTiled/Model/Properties/IProperty.cs @@ -0,0 +1,9 @@ +namespace DotTiled; + +public interface IProperty +{ + public string Name { get; set; } + public PropertyType Type { get; } + + IProperty Clone(); +} diff --git a/DotTiled/Model/Properties/IntProperty.cs b/DotTiled/Model/Properties/IntProperty.cs new file mode 100644 index 0000000..b8fb02a --- /dev/null +++ b/DotTiled/Model/Properties/IntProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class IntProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.Int; + public required int Value { get; set; } + + public IProperty Clone() => new IntProperty + { + Name = Name, + Value = Value + }; +} diff --git a/DotTiled/Model/Properties/ObjectProperty.cs b/DotTiled/Model/Properties/ObjectProperty.cs new file mode 100644 index 0000000..1591319 --- /dev/null +++ b/DotTiled/Model/Properties/ObjectProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class ObjectProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.Object; + public required uint Value { get; set; } + + public IProperty Clone() => new ObjectProperty + { + Name = Name, + Value = Value + }; +} diff --git a/DotTiled/Model/Properties/PropertyType.cs b/DotTiled/Model/Properties/PropertyType.cs new file mode 100644 index 0000000..79b05cb --- /dev/null +++ b/DotTiled/Model/Properties/PropertyType.cs @@ -0,0 +1,13 @@ +namespace DotTiled; + +public enum PropertyType +{ + String, + Int, + Float, + Bool, + Color, + File, + Object, + Class +} diff --git a/DotTiled/Model/Properties/StringProperty.cs b/DotTiled/Model/Properties/StringProperty.cs new file mode 100644 index 0000000..655b7b4 --- /dev/null +++ b/DotTiled/Model/Properties/StringProperty.cs @@ -0,0 +1,14 @@ +namespace DotTiled; + +public class StringProperty : IProperty +{ + public required string Name { get; set; } + public PropertyType Type => PropertyType.String; + public required string Value { get; set; } + + public IProperty Clone() => new StringProperty + { + Name = Name, + Value = Value + }; +} From da5bf73ca02e402418ab5ff9631f6ec13fc1f04b Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 22:00:59 +0200 Subject: [PATCH 27/39] Make assertions tell you where they fail --- DotTiled.Tests/Assert/AssertData.cs | 22 ++--- DotTiled.Tests/Assert/AssertImage.cs | 10 +- DotTiled.Tests/Assert/AssertLayer.cs | 46 ++++----- DotTiled.Tests/Assert/AssertMap.cs | 67 ++++++++----- DotTiled.Tests/Assert/AssertObject.cs | 50 +++++----- DotTiled.Tests/Assert/AssertProperties.cs | 22 ++--- DotTiled.Tests/Assert/AssertTileset.cs | 94 +++++++++---------- .../TestData/Map/default-map/default-map.cs | 60 ++---------- 8 files changed, 178 insertions(+), 193 deletions(-) diff --git a/DotTiled.Tests/Assert/AssertData.cs b/DotTiled.Tests/Assert/AssertData.cs index d4b54f0..31ffff2 100644 --- a/DotTiled.Tests/Assert/AssertData.cs +++ b/DotTiled.Tests/Assert/AssertData.cs @@ -12,17 +12,17 @@ public static partial class DotTiledAssert // Attributes Assert.NotNull(actual); - Assert.Equal(expected.Encoding, actual.Encoding); - Assert.Equal(expected.Compression, actual.Compression); + AssertEqual(expected.Encoding, actual.Encoding, nameof(Data.Encoding)); + AssertEqual(expected.Compression, actual.Compression, nameof(Data.Compression)); // Data - Assert.Equal(expected.GlobalTileIDs, actual.GlobalTileIDs); - Assert.Equal(expected.FlippingFlags, actual.FlippingFlags); + AssertEqual(expected.GlobalTileIDs, actual.GlobalTileIDs, nameof(Data.GlobalTileIDs)); + AssertEqual(expected.FlippingFlags, actual.FlippingFlags, nameof(Data.FlippingFlags)); if (expected.Chunks is not null) { Assert.NotNull(actual.Chunks); - Assert.Equal(expected.Chunks.Length, actual.Chunks.Length); + AssertEqual(expected.Chunks.Length, actual.Chunks.Length, "Chunks.Length"); for (var i = 0; i < expected.Chunks.Length; i++) AssertChunk(expected.Chunks[i], actual.Chunks[i]); } @@ -31,13 +31,13 @@ public static partial class DotTiledAssert private static void AssertChunk(Chunk expected, Chunk actual) { // Attributes - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); + AssertEqual(expected.X, actual.X, nameof(Chunk.X)); + AssertEqual(expected.Y, actual.Y, nameof(Chunk.Y)); + AssertEqual(expected.Width, actual.Width, nameof(Chunk.Width)); + AssertEqual(expected.Height, actual.Height, nameof(Chunk.Height)); // Data - Assert.Equal(expected.GlobalTileIDs, actual.GlobalTileIDs); - Assert.Equal(expected.FlippingFlags, actual.FlippingFlags); + AssertEqual(expected.GlobalTileIDs, actual.GlobalTileIDs, nameof(Chunk.GlobalTileIDs)); + AssertEqual(expected.FlippingFlags, actual.FlippingFlags, nameof(Chunk.FlippingFlags)); } } diff --git a/DotTiled.Tests/Assert/AssertImage.cs b/DotTiled.Tests/Assert/AssertImage.cs index 9943c46..a674faa 100644 --- a/DotTiled.Tests/Assert/AssertImage.cs +++ b/DotTiled.Tests/Assert/AssertImage.cs @@ -12,10 +12,10 @@ public static partial class DotTiledAssert // Attributes Assert.NotNull(actual); - Assert.Equal(expected.Format, actual.Format); - Assert.Equal(expected.Source, actual.Source); - Assert.Equal(expected.TransparentColor, actual.TransparentColor); - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); + AssertEqual(expected.Format, actual.Format, nameof(Image.Format)); + AssertEqual(expected.Source, actual.Source, nameof(Image.Source)); + AssertEqual(expected.TransparentColor, actual.TransparentColor, nameof(Image.TransparentColor)); + AssertEqual(expected.Width, actual.Width, nameof(Image.Width)); + AssertEqual(expected.Height, actual.Height, nameof(Image.Height)); } } diff --git a/DotTiled.Tests/Assert/AssertLayer.cs b/DotTiled.Tests/Assert/AssertLayer.cs index 57df04d..5432d62 100644 --- a/DotTiled.Tests/Assert/AssertLayer.cs +++ b/DotTiled.Tests/Assert/AssertLayer.cs @@ -12,16 +12,16 @@ public static partial class DotTiledAssert // Attributes Assert.NotNull(actual); - Assert.Equal(expected.ID, actual.ID); - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Class, actual.Class); - Assert.Equal(expected.Opacity, actual.Opacity); - Assert.Equal(expected.Visible, actual.Visible); - Assert.Equal(expected.TintColor, actual.TintColor); - Assert.Equal(expected.OffsetX, actual.OffsetX); - Assert.Equal(expected.OffsetY, actual.OffsetY); - Assert.Equal(expected.ParallaxX, actual.ParallaxX); - Assert.Equal(expected.ParallaxY, actual.ParallaxY); + AssertEqual(expected.ID, actual.ID, nameof(BaseLayer.ID)); + AssertEqual(expected.Name, actual.Name, nameof(BaseLayer.Name)); + AssertEqual(expected.Class, actual.Class, nameof(BaseLayer.Class)); + AssertEqual(expected.Opacity, actual.Opacity, nameof(BaseLayer.Opacity)); + AssertEqual(expected.Visible, actual.Visible, nameof(BaseLayer.Visible)); + AssertEqual(expected.TintColor, actual.TintColor, nameof(BaseLayer.TintColor)); + AssertEqual(expected.OffsetX, actual.OffsetX, nameof(BaseLayer.OffsetX)); + AssertEqual(expected.OffsetY, actual.OffsetY, nameof(BaseLayer.OffsetY)); + AssertEqual(expected.ParallaxX, actual.ParallaxX, nameof(BaseLayer.ParallaxX)); + AssertEqual(expected.ParallaxY, actual.ParallaxY, nameof(BaseLayer.ParallaxY)); AssertProperties(expected.Properties, actual.Properties); AssertLayer((dynamic)expected, (dynamic)actual); @@ -30,10 +30,10 @@ public static partial class DotTiledAssert private static void AssertLayer(TileLayer expected, TileLayer actual) { // Attributes - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); + AssertEqual(expected.Width, actual.Width, nameof(TileLayer.Width)); + AssertEqual(expected.Height, actual.Height, nameof(TileLayer.Height)); + AssertEqual(expected.X, actual.X, nameof(TileLayer.X)); + AssertEqual(expected.Y, actual.Y, nameof(TileLayer.Y)); Assert.NotNull(actual.Data); AssertData(expected.Data, actual.Data); @@ -42,12 +42,12 @@ public static partial class DotTiledAssert private static void AssertLayer(ObjectLayer expected, ObjectLayer actual) { // Attributes - Assert.Equal(expected.DrawOrder, actual.DrawOrder); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); + AssertEqual(expected.DrawOrder, actual.DrawOrder, nameof(ObjectLayer.DrawOrder)); + AssertEqual(expected.X, actual.X, nameof(ObjectLayer.X)); + AssertEqual(expected.Y, actual.Y, nameof(ObjectLayer.Y)); Assert.NotNull(actual.Objects); - Assert.Equal(expected.Objects.Count, actual.Objects.Count); + AssertEqual(expected.Objects.Count, actual.Objects.Count, "Objects.Count"); for (var i = 0; i < expected.Objects.Count; i++) AssertObject(expected.Objects[i], actual.Objects[i]); } @@ -55,10 +55,10 @@ public static partial class DotTiledAssert private static void AssertLayer(ImageLayer expected, ImageLayer actual) { // Attributes - Assert.Equal(expected.RepeatX, actual.RepeatX); - Assert.Equal(expected.RepeatY, actual.RepeatY); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); + AssertEqual(expected.RepeatX, actual.RepeatX, nameof(ImageLayer.RepeatX)); + AssertEqual(expected.RepeatY, actual.RepeatY, nameof(ImageLayer.RepeatY)); + AssertEqual(expected.X, actual.X, nameof(ImageLayer.X)); + AssertEqual(expected.Y, actual.Y, nameof(ImageLayer.Y)); Assert.NotNull(actual.Image); AssertImage(expected.Image, actual.Image); @@ -68,7 +68,7 @@ public static partial class DotTiledAssert { // Attributes Assert.NotNull(actual.Layers); - Assert.Equal(expected.Layers.Count, actual.Layers.Count); + AssertEqual(expected.Layers.Count, actual.Layers.Count, "Layers.Count"); for (var i = 0; i < expected.Layers.Count; i++) AssertLayer(expected.Layers[i], actual.Layers[i]); } diff --git a/DotTiled.Tests/Assert/AssertMap.cs b/DotTiled.Tests/Assert/AssertMap.cs index e831063..6596ee8 100644 --- a/DotTiled.Tests/Assert/AssertMap.cs +++ b/DotTiled.Tests/Assert/AssertMap.cs @@ -2,38 +2,63 @@ namespace DotTiled.Tests; public static partial class DotTiledAssert { + private static void AssertEqual(T expected, T actual, string nameof) + { + if (expected == null) + { + Assert.Null(actual); + return; + } + + if (typeof(T).IsArray) + { + var expectedArray = (Array)(object)expected; + var actualArray = (Array)(object)actual!; + + Assert.NotNull(actualArray); + AssertEqual(expectedArray.Length, actualArray.Length, $"{nameof}.Length"); + + for (var i = 0; i < expectedArray.Length; i++) + AssertEqual(expectedArray.GetValue(i), actualArray.GetValue(i), $"{nameof}[{i}]"); + + return; + } + + Assert.True(expected.Equals(actual), $"Expected {nameof} '{expected}' but got '{actual}'"); + } + 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); + AssertEqual(expected.Version, actual.Version, nameof(Map.Version)); + AssertEqual(expected.TiledVersion, actual.TiledVersion, nameof(Map.TiledVersion)); + AssertEqual(expected.Class, actual.Class, nameof(Map.Class)); + AssertEqual(expected.Orientation, actual.Orientation, nameof(Map.Orientation)); + AssertEqual(expected.RenderOrder, actual.RenderOrder, nameof(Map.RenderOrder)); + AssertEqual(expected.CompressionLevel, actual.CompressionLevel, nameof(Map.CompressionLevel)); + AssertEqual(expected.Width, actual.Width, nameof(Map.Width)); + AssertEqual(expected.Height, actual.Height, nameof(Map.Height)); + AssertEqual(expected.TileWidth, actual.TileWidth, nameof(Map.TileWidth)); + AssertEqual(expected.TileHeight, actual.TileHeight, nameof(Map.TileHeight)); + AssertEqual(expected.HexSideLength, actual.HexSideLength, nameof(Map.HexSideLength)); + AssertEqual(expected.StaggerAxis, actual.StaggerAxis, nameof(Map.StaggerAxis)); + AssertEqual(expected.StaggerIndex, actual.StaggerIndex, nameof(Map.StaggerIndex)); + AssertEqual(expected.ParallaxOriginX, actual.ParallaxOriginX, nameof(Map.ParallaxOriginX)); + AssertEqual(expected.ParallaxOriginY, actual.ParallaxOriginY, nameof(Map.ParallaxOriginY)); + AssertEqual(expected.BackgroundColor, actual.BackgroundColor, nameof(Map.BackgroundColor)); + AssertEqual(expected.NextLayerID, actual.NextLayerID, nameof(Map.NextLayerID)); + AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID)); + AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite)); AssertProperties(actual.Properties, expected.Properties); Assert.NotNull(actual.Tilesets); - Assert.Equal(expected.Tilesets.Count, actual.Tilesets.Count); + AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count"); for (var i = 0; i < expected.Tilesets.Count; i++) AssertTileset(expected.Tilesets[i], actual.Tilesets[i]); Assert.NotNull(actual.Layers); - Assert.Equal(expected.Layers.Count, actual.Layers.Count); + AssertEqual(expected.Layers.Count, actual.Layers.Count, "Layers.Count"); for (var i = 0; i < expected.Layers.Count; i++) AssertLayer(expected.Layers[i], actual.Layers[i]); } diff --git a/DotTiled.Tests/Assert/AssertObject.cs b/DotTiled.Tests/Assert/AssertObject.cs index 3b08744..c49b6e7 100644 --- a/DotTiled.Tests/Assert/AssertObject.cs +++ b/DotTiled.Tests/Assert/AssertObject.cs @@ -5,17 +5,17 @@ public static partial class DotTiledAssert internal static void AssertObject(Object expected, Object actual) { // Attributes - Assert.Equal(expected.ID, actual.ID); - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); - Assert.Equal(expected.Rotation, actual.Rotation); - Assert.Equal(expected.GID, actual.GID); - Assert.Equal(expected.Visible, actual.Visible); - Assert.Equal(expected.Template, actual.Template); + AssertEqual(expected.ID, actual.ID, nameof(Object.ID)); + AssertEqual(expected.Name, actual.Name, nameof(Object.Name)); + AssertEqual(expected.Type, actual.Type, nameof(Object.Type)); + AssertEqual(expected.X, actual.X, nameof(Object.X)); + AssertEqual(expected.Y, actual.Y, nameof(Object.Y)); + AssertEqual(expected.Width, actual.Width, nameof(Object.Width)); + AssertEqual(expected.Height, actual.Height, nameof(Object.Height)); + AssertEqual(expected.Rotation, actual.Rotation, nameof(Object.Rotation)); + AssertEqual(expected.GID, actual.GID, nameof(Object.GID)); + AssertEqual(expected.Visible, actual.Visible, nameof(Object.Visible)); + AssertEqual(expected.Template, actual.Template, nameof(Object.Template)); AssertProperties(expected.Properties, actual.Properties); AssertObject((dynamic)expected, (dynamic)actual); @@ -38,29 +38,29 @@ public static partial class DotTiledAssert private static void AssertObject(PolygonObject expected, PolygonObject actual) { - Assert.Equal(expected.Points, actual.Points); + AssertEqual(expected.Points, actual.Points, nameof(PolygonObject.Points)); } private static void AssertObject(PolylineObject expected, PolylineObject actual) { - Assert.Equal(expected.Points, actual.Points); + AssertEqual(expected.Points, actual.Points, nameof(PolylineObject.Points)); } private static void AssertObject(TextObject expected, TextObject actual) { // Attributes - Assert.Equal(expected.FontFamily, actual.FontFamily); - Assert.Equal(expected.PixelSize, actual.PixelSize); - Assert.Equal(expected.Wrap, actual.Wrap); - Assert.Equal(expected.Color, actual.Color); - Assert.Equal(expected.Bold, actual.Bold); - Assert.Equal(expected.Italic, actual.Italic); - Assert.Equal(expected.Underline, actual.Underline); - Assert.Equal(expected.Strikeout, actual.Strikeout); - Assert.Equal(expected.Kerning, actual.Kerning); - Assert.Equal(expected.HorizontalAlignment, actual.HorizontalAlignment); - Assert.Equal(expected.VerticalAlignment, actual.VerticalAlignment); + AssertEqual(expected.FontFamily, actual.FontFamily, nameof(TextObject.FontFamily)); + AssertEqual(expected.PixelSize, actual.PixelSize, nameof(TextObject.PixelSize)); + AssertEqual(expected.Wrap, actual.Wrap, nameof(TextObject.Wrap)); + AssertEqual(expected.Color, actual.Color, nameof(TextObject.Color)); + AssertEqual(expected.Bold, actual.Bold, nameof(TextObject.Bold)); + AssertEqual(expected.Italic, actual.Italic, nameof(TextObject.Italic)); + AssertEqual(expected.Underline, actual.Underline, nameof(TextObject.Underline)); + AssertEqual(expected.Strikeout, actual.Strikeout, nameof(TextObject.Strikeout)); + AssertEqual(expected.Kerning, actual.Kerning, nameof(TextObject.Kerning)); + AssertEqual(expected.HorizontalAlignment, actual.HorizontalAlignment, nameof(TextObject.HorizontalAlignment)); + AssertEqual(expected.VerticalAlignment, actual.VerticalAlignment, nameof(TextObject.VerticalAlignment)); - Assert.Equal(expected.Text, actual.Text); + AssertEqual(expected.Text, actual.Text, nameof(TextObject.Text)); } } diff --git a/DotTiled.Tests/Assert/AssertProperties.cs b/DotTiled.Tests/Assert/AssertProperties.cs index afd28c2..740ba2b 100644 --- a/DotTiled.Tests/Assert/AssertProperties.cs +++ b/DotTiled.Tests/Assert/AssertProperties.cs @@ -11,7 +11,7 @@ public static partial class DotTiledAssert } Assert.NotNull(actual); - Assert.Equal(expected.Count, actual.Count); + AssertEqual(expected.Count, actual.Count, "Properties.Count"); foreach (var kvp in expected) { Assert.Contains(kvp.Key, actual.Keys); @@ -21,49 +21,49 @@ public static partial class DotTiledAssert private static void AssertProperty(IProperty expected, IProperty actual) { - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.Name, actual.Name); + AssertEqual(expected.Type, actual.Type, "Property.Type"); + AssertEqual(expected.Name, actual.Name, "Property.Name"); AssertProperties((dynamic)actual, (dynamic)expected); } private static void AssertProperty(StringProperty expected, StringProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "StringProperty.Value"); } private static void AssertProperty(IntProperty expected, IntProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "IntProperty.Value"); } private static void AssertProperty(FloatProperty expected, FloatProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "FloatProperty.Value"); } private static void AssertProperty(BoolProperty expected, BoolProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "BoolProperty.Value"); } private static void AssertProperty(ColorProperty expected, ColorProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "ColorProperty.Value"); } private static void AssertProperty(FileProperty expected, FileProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "FileProperty.Value"); } private static void AssertProperty(ObjectProperty expected, ObjectProperty actual) { - Assert.Equal(expected.Value, actual.Value); + AssertEqual(expected.Value, actual.Value, "ObjectProperty.Value"); } private static void AssertProperty(ClassProperty expected, ClassProperty actual) { - Assert.Equal(expected.PropertyType, actual.PropertyType); + AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType"); AssertProperties(expected.Properties, actual.Properties); } } diff --git a/DotTiled.Tests/Assert/AssertTileset.cs b/DotTiled.Tests/Assert/AssertTileset.cs index 8421bd0..e6b39bb 100644 --- a/DotTiled.Tests/Assert/AssertTileset.cs +++ b/DotTiled.Tests/Assert/AssertTileset.cs @@ -5,21 +5,21 @@ public static partial class DotTiledAssert internal static void AssertTileset(Tileset expected, Tileset actual) { // Attributes - Assert.Equal(expected.Version, actual.Version); - Assert.Equal(expected.TiledVersion, actual.TiledVersion); - Assert.Equal(expected.FirstGID, actual.FirstGID); - Assert.Equal(expected.Source, actual.Source); - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Class, actual.Class); - Assert.Equal(expected.TileWidth, actual.TileWidth); - Assert.Equal(expected.TileHeight, actual.TileHeight); - Assert.Equal(expected.Spacing, actual.Spacing); - Assert.Equal(expected.Margin, actual.Margin); - Assert.Equal(expected.TileCount, actual.TileCount); - Assert.Equal(expected.Columns, actual.Columns); - Assert.Equal(expected.ObjectAlignment, actual.ObjectAlignment); - Assert.Equal(expected.RenderSize, actual.RenderSize); - Assert.Equal(expected.FillMode, actual.FillMode); + AssertEqual(expected.Version, actual.Version, nameof(Tileset.Version)); + AssertEqual(expected.TiledVersion, actual.TiledVersion, nameof(Tileset.TiledVersion)); + AssertEqual(expected.FirstGID, actual.FirstGID, nameof(Tileset.FirstGID)); + AssertEqual(expected.Source, actual.Source, nameof(Tileset.Source)); + AssertEqual(expected.Name, actual.Name, nameof(Tileset.Name)); + AssertEqual(expected.Class, actual.Class, nameof(Tileset.Class)); + AssertEqual(expected.TileWidth, actual.TileWidth, nameof(Tileset.TileWidth)); + AssertEqual(expected.TileHeight, actual.TileHeight, nameof(Tileset.TileHeight)); + AssertEqual(expected.Spacing, actual.Spacing, nameof(Tileset.Spacing)); + AssertEqual(expected.Margin, actual.Margin, nameof(Tileset.Margin)); + AssertEqual(expected.TileCount, actual.TileCount, nameof(Tileset.TileCount)); + AssertEqual(expected.Columns, actual.Columns, nameof(Tileset.Columns)); + AssertEqual(expected.ObjectAlignment, actual.ObjectAlignment, nameof(Tileset.ObjectAlignment)); + AssertEqual(expected.RenderSize, actual.RenderSize, nameof(Tileset.RenderSize)); + AssertEqual(expected.FillMode, actual.FillMode, nameof(Tileset.FillMode)); // At most one of AssertImage(expected.Image, actual.Image); @@ -30,7 +30,7 @@ public static partial class DotTiledAssert if (expected.Wangsets is not null) { Assert.NotNull(actual.Wangsets); - Assert.Equal(expected.Wangsets.Count, actual.Wangsets.Count); + AssertEqual(expected.Wangsets.Count, actual.Wangsets.Count, "Wangsets.Count"); for (var i = 0; i < expected.Wangsets.Count; i++) AssertWangset(expected.Wangsets[i], actual.Wangsets[i]); } @@ -38,7 +38,7 @@ public static partial class DotTiledAssert // Any number of Assert.NotNull(actual.Tiles); - Assert.Equal(expected.Tiles.Count, actual.Tiles.Count); + AssertEqual(expected.Tiles.Count, actual.Tiles.Count, "Tiles.Count"); for (var i = 0; i < expected.Tiles.Count; i++) AssertTile(expected.Tiles[i], actual.Tiles[i]); } @@ -53,8 +53,8 @@ public static partial class DotTiledAssert // Attributes Assert.NotNull(actual); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); + AssertEqual(expected.X, actual.X, nameof(TileOffset.X)); + AssertEqual(expected.Y, actual.Y, nameof(TileOffset.Y)); } private static void AssertGrid(Grid? expected, Grid? actual) @@ -67,24 +67,24 @@ public static partial class DotTiledAssert // Attributes Assert.NotNull(actual); - Assert.Equal(expected.Orientation, actual.Orientation); - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); + AssertEqual(expected.Orientation, actual.Orientation, nameof(Grid.Orientation)); + AssertEqual(expected.Width, actual.Width, nameof(Grid.Width)); + AssertEqual(expected.Height, actual.Height, nameof(Grid.Height)); } private static void AssertWangset(Wangset expected, Wangset actual) { // Attributes - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Class, actual.Class); - Assert.Equal(expected.Tile, actual.Tile); + AssertEqual(expected.Name, actual.Name, nameof(Wangset.Name)); + AssertEqual(expected.Class, actual.Class, nameof(Wangset.Class)); + AssertEqual(expected.Tile, actual.Tile, nameof(Wangset.Tile)); // At most one of AssertProperties(expected.Properties, actual.Properties); if (expected.WangColors is not null) { Assert.NotNull(actual.WangColors); - Assert.Equal(expected.WangColors.Count, actual.WangColors.Count); + AssertEqual(expected.WangColors.Count, actual.WangColors.Count, "WangColors.Count"); for (var i = 0; i < expected.WangColors.Count; i++) AssertWangColor(expected.WangColors[i], actual.WangColors[i]); } @@ -95,11 +95,11 @@ public static partial class DotTiledAssert private static void AssertWangColor(WangColor expected, WangColor actual) { // Attributes - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Class, actual.Class); - Assert.Equal(expected.Color, actual.Color); - Assert.Equal(expected.Tile, actual.Tile); - Assert.Equal(expected.Probability, actual.Probability); + AssertEqual(expected.Name, actual.Name, nameof(WangColor.Name)); + AssertEqual(expected.Class, actual.Class, nameof(WangColor.Class)); + AssertEqual(expected.Color, actual.Color, nameof(WangColor.Color)); + AssertEqual(expected.Tile, actual.Tile, nameof(WangColor.Tile)); + AssertEqual(expected.Probability, actual.Probability, nameof(WangColor.Probability)); AssertProperties(expected.Properties, actual.Properties); } @@ -107,8 +107,8 @@ public static partial class DotTiledAssert private static void AssertWangTile(WangTile expected, WangTile actual) { // Attributes - Assert.Equal(expected.TileID, actual.TileID); - Assert.Equal(expected.WangID, actual.WangID); + AssertEqual(expected.TileID, actual.TileID, nameof(WangTile.TileID)); + AssertEqual(expected.WangID, actual.WangID, nameof(WangTile.WangID)); } private static void AssertTransformations(Transformations? expected, Transformations? actual) @@ -121,22 +121,22 @@ public static partial class DotTiledAssert // Attributes Assert.NotNull(actual); - Assert.Equal(expected.HFlip, actual.HFlip); - Assert.Equal(expected.VFlip, actual.VFlip); - Assert.Equal(expected.Rotate, actual.Rotate); - Assert.Equal(expected.PreferUntransformed, actual.PreferUntransformed); + AssertEqual(expected.HFlip, actual.HFlip, nameof(Transformations.HFlip)); + AssertEqual(expected.VFlip, actual.VFlip, nameof(Transformations.VFlip)); + AssertEqual(expected.Rotate, actual.Rotate, nameof(Transformations.Rotate)); + AssertEqual(expected.PreferUntransformed, actual.PreferUntransformed, nameof(Transformations.PreferUntransformed)); } private static void AssertTile(Tile expected, Tile actual) { // Attributes - Assert.Equal(expected.ID, actual.ID); - Assert.Equal(expected.Type, actual.Type); - Assert.Equal(expected.Probability, actual.Probability); - Assert.Equal(expected.X, actual.X); - Assert.Equal(expected.Y, actual.Y); - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); + AssertEqual(expected.ID, actual.ID, nameof(Tile.ID)); + AssertEqual(expected.Type, actual.Type, nameof(Tile.Type)); + AssertEqual(expected.Probability, actual.Probability, nameof(Tile.Probability)); + AssertEqual(expected.X, actual.X, nameof(Tile.X)); + AssertEqual(expected.Y, actual.Y, nameof(Tile.Y)); + AssertEqual(expected.Width, actual.Width, nameof(Tile.Width)); + AssertEqual(expected.Height, actual.Height, nameof(Tile.Height)); // Elements AssertProperties(actual.Properties, expected.Properties); @@ -145,7 +145,7 @@ public static partial class DotTiledAssert if (expected.Animation is not null) { Assert.NotNull(actual.Animation); - Assert.Equal(expected.Animation.Count, actual.Animation.Count); + AssertEqual(expected.Animation.Count, actual.Animation.Count, "Animation.Count"); for (var i = 0; i < expected.Animation.Count; i++) AssertFrame(expected.Animation[i], actual.Animation[i]); } @@ -154,7 +154,7 @@ public static partial class DotTiledAssert private static void AssertFrame(Frame expected, Frame actual) { // Attributes - Assert.Equal(expected.TileID, actual.TileID); - Assert.Equal(expected.Duration, actual.Duration); + AssertEqual(expected.TileID, actual.TileID, nameof(Frame.TileID)); + AssertEqual(expected.Duration, actual.Duration, nameof(Frame.Duration)); } } diff --git a/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs b/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs index 44c909e..eff73d9 100644 --- a/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.cs @@ -36,58 +36,18 @@ public partial class TestData Chunks = null, Compression = null, GlobalTileIDs = [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 ], FlippingFlags = [ - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None, - FlippingFlags.None + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None ] } } From 46d5127c72396a8901286f7bf0d4d4d4b7c14e01 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 22:07:30 +0200 Subject: [PATCH 28/39] Tests are automatically created when populating the available test data --- DotTiled.Tests/Serialization/TestData.cs | 40 +++++++++++++++ .../Serialization/Tmj/TmjMapReaderTests.cs | 49 +++---------------- .../Serialization/Tmx/TmxMapReaderTests.cs | 48 +++--------------- 3 files changed, 54 insertions(+), 83 deletions(-) diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 9b3b13f..8fc9b9e 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -27,4 +27,44 @@ public static partial class TestData using var stringReader = new StreamReader(stream); return stringReader.ReadToEnd(); } + + public static IEnumerable MapsThatHaveTmxAndTmj => + [ + ["Serialization.TestData.Map.default_map.default-map", TestData.DefaultMap(), Array.Empty()] + ]; + + private static CustomTypeDefinition[] typedefs = [ + new CustomClassDefinition + { + Name = "TestClass", + ID = 1, + UseAs = CustomClassUseAs.Property, + Members = [ + new StringProperty + { + Name = "Name", + Value = "" + }, + new FloatProperty + { + Name = "Amount", + Value = 0f + } + ] + }, + new CustomClassDefinition + { + Name = "Test", + ID = 2, + UseAs = CustomClassUseAs.All, + Members = [ + new ClassProperty + { + Name = "Yep", + PropertyType = "TestClass", + Properties = [] + } + ] + } + ]; } diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index e04423b..0cdad90 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -2,51 +2,16 @@ namespace DotTiled.Tests; public partial class TmjMapReaderTests { - public static IEnumerable DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => - [ - ["Serialization.TestData.Map.default_map.default-map.tmj", TestData.DefaultMap()] - ]; - + public static IEnumerable Maps => TestData.MapsThatHaveTmxAndTmj; [Theory] - [MemberData(nameof(DeserializeMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] - public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) + [MemberData(nameof(Maps))] + public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( + string testDataFile, + Map expectedMap, + IReadOnlyCollection customTypeDefinitions) { // Arrange - CustomTypeDefinition[] customTypeDefinitions = [ - new CustomClassDefinition - { - Name = "TestClass", - ID = 1, - UseAs = CustomClassUseAs.Property, - Members = [ - new StringProperty - { - Name = "Name", - Value = "" - }, - new FloatProperty - { - Name = "Amount", - Value = 0f - } - ] - }, - new CustomClassDefinition - { - Name = "Test", - ID = 2, - UseAs = CustomClassUseAs.All, - Members = [ - new ClassProperty - { - Name = "Yep", - PropertyType = "TestClass", - Properties = [] - } - ] - } - ]; - + testDataFile += ".tmj"; var json = TestData.GetRawStringFor(testDataFile); Template ResolveTemplate(string source) { diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 5ee162f..3134863 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -4,50 +4,16 @@ namespace DotTiled.Tests; public partial class TmxMapReaderTests { - public static IEnumerable DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data => - [ - ["Serialization.TestData.Map.default_map.default-map.tmx", TestData.DefaultMap()] - ]; - + public static IEnumerable Maps => TestData.MapsThatHaveTmxAndTmj; [Theory] - [MemberData(nameof(DeserializeMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected_Data))] - public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(string testDataFile, Map expectedMap) + [MemberData(nameof(Maps))] + public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( + string testDataFile, + Map expectedMap, + IReadOnlyCollection customTypeDefinitions) { // Arrange - CustomTypeDefinition[] customTypeDefinitions = [ - new CustomClassDefinition - { - Name = "TestClass", - ID = 1, - UseAs = CustomClassUseAs.Property, - Members = [ - new StringProperty - { - Name = "Name", - Value = "" - }, - new FloatProperty - { - Name = "Amount", - Value = 0f - } - ] - }, - new CustomClassDefinition - { - Name = "Test", - ID = 2, - UseAs = CustomClassUseAs.All, - Members = [ - new ClassProperty - { - Name = "Yep", - PropertyType = "TestClass", - Properties = [] - } - ] - } - ]; + testDataFile += ".tmx"; using var reader = TestData.GetXmlReaderFor(testDataFile); Template ResolveTemplate(string source) { From 5fd05683f61b61c0d57ca010b13b5bb07131e210 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 22:17:13 +0200 Subject: [PATCH 29/39] Add tests for common properties in maps --- DotTiled.Tests/Serialization/TestData.cs | 3 +- .../map-with-common-props.cs | 68 ++++++++++++++++++ .../map-with-common-props.tmj | 70 +++++++++++++++++++ .../map-with-common-props.tmx | 21 ++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmx diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 8fc9b9e..5cdf444 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -30,7 +30,8 @@ public static partial class TestData public static IEnumerable MapsThatHaveTmxAndTmj => [ - ["Serialization.TestData.Map.default_map.default-map", TestData.DefaultMap(), Array.Empty()] + ["Serialization.TestData.Map.default_map.default-map", TestData.DefaultMap(), Array.Empty()], + ["Serialization.TestData.Map.map_with_common_props.map-with-common-props", TestData.MapWithCommonProps(), Array.Empty()] ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs new file mode 100644 index 0000000..8c3283e --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs @@ -0,0 +1,68 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithCommonProps() => new Map + { + Class = "", + Orientation = MapOrientation.Isometric, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 16, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00ff00", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ], + Properties = new Dictionary + { + ["boolprop"] = new BoolProperty { Name = "boolprop", Value = true }, + ["colorprop"] = new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) }, + ["fileprop"] = new FileProperty { Name = "fileprop", Value = "file.txt" }, + ["floatprop"] = new FloatProperty { Name = "floatprop", Value = 4.2f }, + ["intprop"] = new IntProperty { Name = "intprop", Value = 8 }, + ["objectprop"] = new ObjectProperty { Name = "objectprop", Value = 5 }, + ["stringprop"] = new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" } + } + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmj new file mode 100644 index 0000000..c7182ef --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmj @@ -0,0 +1,70 @@ +{ "backgroundcolor":"#00ff00", + "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"isometric", + "properties":[ + { + "name":"boolprop", + "type":"bool", + "value":true + }, + { + "name":"colorprop", + "type":"color", + "value":"#ff55ffff" + }, + { + "name":"fileprop", + "type":"file", + "value":"file.txt" + }, + { + "name":"floatprop", + "type":"float", + "value":4.2 + }, + { + "name":"intprop", + "type":"int", + "value":8 + }, + + { + "name":"objectprop", + "type":"object", + "value":5 + }, + { + "name":"stringprop", + "type":"string", + "value":"This is a string, hello world!" + }], + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":16, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmx new file mode 100644 index 0000000..b4b36cd --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.tmx @@ -0,0 +1,21 @@ + + + + + + + + + + + + + +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + From 7b7e499b0de16325f067def1e3ba8fe61182737f Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 22:33:00 +0200 Subject: [PATCH 30/39] Add tests for custom type properties in maps --- DotTiled.Tests/Serialization/TestData.cs | 5 +- .../map-with-custom-type-props.cs | 122 ++++++++++++++++++ .../map-with-custom-type-props.tmj | 44 +++++++ .../map-with-custom-type-props.tmx | 21 +++ .../propertytypes.json | 49 +++++++ .../Serialization/Tmj/TmjMapReaderTests.cs | 2 +- .../Serialization/Tmx/TmxMapReaderTests.cs | 2 +- DotTiled/Model/Color.cs | 2 + 8 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/propertytypes.json diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 5cdf444..371e8b7 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -28,10 +28,11 @@ public static partial class TestData return stringReader.ReadToEnd(); } - public static IEnumerable MapsThatHaveTmxAndTmj => + public static IEnumerable MapTests => [ ["Serialization.TestData.Map.default_map.default-map", TestData.DefaultMap(), Array.Empty()], - ["Serialization.TestData.Map.map_with_common_props.map-with-common-props", TestData.MapWithCommonProps(), Array.Empty()] + ["Serialization.TestData.Map.map_with_common_props.map-with-common-props", TestData.MapWithCommonProps(), Array.Empty()], + ["Serialization.TestData.Map.map_with_custom_type_props.map-with-custom-type-props", TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()] ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs new file mode 100644 index 0000000..1343f62 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs @@ -0,0 +1,122 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithCustomTypeProps() => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ], + Properties = new Dictionary + { + ["customclassprop"] = new ClassProperty + { + Name = "customclassprop", + PropertyType = "CustomClass", + Properties = new Dictionary + { + ["boolinclass"] = new BoolProperty { Name = "boolinclass", Value = true }, + ["colorinclass"] = new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) }, + ["fileinclass"] = new FileProperty { Name = "fileinclass", Value = "" }, + ["floatinclass"] = new FloatProperty { Name = "floatinclass", Value = 13.37f }, + ["intinclass"] = new IntProperty { Name = "intinclass", Value = 0 }, + ["objectinclass"] = new ObjectProperty { Name = "objectinclass", Value = 0 }, + ["stringinclass"] = new StringProperty { Name = "stringinclass", Value = "This is a set string" } + } + } + } + }; + + // This comes from map-with-custom-type-props/propertytypes.json + public static IReadOnlyCollection MapWithCustomTypePropsCustomTypeDefinitions() => [ + new CustomClassDefinition + { + Name = "CustomClass", + UseAs = CustomClassUseAs.Property, + Members = [ + new BoolProperty + { + Name = "boolinclass", + Value = false + }, + new ColorProperty + { + Name = "colorinclass", + Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) + }, + new FileProperty + { + Name = "fileinclass", + Value = "" + }, + new FloatProperty + { + Name = "floatinclass", + Value = 0f + }, + new IntProperty + { + Name = "intinclass", + Value = 0 + }, + new ObjectProperty + { + Name = "objectinclass", + Value = 0 + }, + new StringProperty + { + Name = "stringinclass", + Value = "" + } + ] + } + ]; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmj new file mode 100644 index 0000000..a8c7f43 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmj @@ -0,0 +1,44 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "properties":[ + { + "name":"customclassprop", + "propertytype":"CustomClass", + "type":"class", + "value": + { + "boolinclass":true, + "floatinclass":13.37, + "stringinclass":"This is a set string" + } + }], + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmx new file mode 100644 index 0000000..c364577 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.tmx @@ -0,0 +1,21 @@ + + + + + + + + + + + + + +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/propertytypes.json b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/propertytypes.json new file mode 100644 index 0000000..16c42fb --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/propertytypes.json @@ -0,0 +1,49 @@ +[ + { + "color": "#ffa0a0a4", + "drawFill": true, + "id": 8, + "members": [ + { + "name": "boolinclass", + "type": "bool", + "value": false + }, + { + "name": "colorinclass", + "type": "color", + "value": "" + }, + { + "name": "fileinclass", + "type": "file", + "value": "" + }, + { + "name": "floatinclass", + "type": "float", + "value": 0 + }, + { + "name": "intinclass", + "type": "int", + "value": 0 + }, + { + "name": "objectinclass", + "type": "object", + "value": 0 + }, + { + "name": "stringinclass", + "type": "string", + "value": "" + } + ], + "name": "CustomClass", + "type": "class", + "useAs": [ + "property" + ] + } +] diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 0cdad90..71e5304 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -2,7 +2,7 @@ namespace DotTiled.Tests; public partial class TmjMapReaderTests { - public static IEnumerable Maps => TestData.MapsThatHaveTmxAndTmj; + public static IEnumerable Maps => TestData.MapTests; [Theory] [MemberData(nameof(Maps))] public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index 3134863..c0dc083 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -4,7 +4,7 @@ namespace DotTiled.Tests; public partial class TmxMapReaderTests { - public static IEnumerable Maps => TestData.MapsThatHaveTmxAndTmj; + public static IEnumerable Maps => TestData.MapTests; [Theory] [MemberData(nameof(Maps))] public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( diff --git a/DotTiled/Model/Color.cs b/DotTiled/Model/Color.cs index 29bafe9..ae74d0d 100644 --- a/DotTiled/Model/Color.cs +++ b/DotTiled/Model/Color.cs @@ -66,4 +66,6 @@ public class Color : IParsable, IEquatable public override bool Equals(object? obj) => obj is Color other && Equals(other); public override int GetHashCode() => HashCode.Combine(R, G, B, A); + + public override string ToString() => $"#{A:x2}{R:x2}{G:x2}{B:x2}"; } From f82487f46a6179cc742b1ef007ffd5de60150ed8 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 22:44:55 +0200 Subject: [PATCH 31/39] Add embedded tileset test case --- DotTiled.Tests/Serialization/TestData.cs | 3 +- .../map-with-embedded-tileset.cs | 76 ++++++++++++++++++ .../map-with-embedded-tileset.tmj | 45 +++++++++++ .../map-with-embedded-tileset.tmx | 15 ++++ .../Map/map-with-embedded-tileset/tileset.png | Bin 0 -> 11908 bytes 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/tileset.png diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 371e8b7..eac8d05 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -32,7 +32,8 @@ public static partial class TestData [ ["Serialization.TestData.Map.default_map.default-map", TestData.DefaultMap(), Array.Empty()], ["Serialization.TestData.Map.map_with_common_props.map-with-common-props", TestData.MapWithCommonProps(), Array.Empty()], - ["Serialization.TestData.Map.map_with_custom_type_props.map-with-custom-type-props", TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()] + ["Serialization.TestData.Map.map_with_custom_type_props.map-with-custom-type-props", TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()], + ["Serialization.TestData.Map.map_with_embedded_tileset.map-with-embedded-tileset", TestData.MapWithEmbeddedTileset(), Array.Empty()], ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.cs new file mode 100644 index 0000000..fb3c95f --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.cs @@ -0,0 +1,76 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithEmbeddedTileset() => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Tilesets = [ + new Tileset + { + FirstGID = 1, + Name = "tileset", + TileWidth = 32, + TileHeight = 32, + TileCount = 24, + Columns = 8, + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96, + } + } + ], + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 1, 1, 0, 0, 7, + 1, 1, 0, 0, 7, + 0, 0, 0, 0, 7, + 9, 10, 0, 0, 7, + 17, 18, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmj new file mode 100644 index 0000000..41d5e7b --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmj @@ -0,0 +1,45 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[1, 1, 0, 0, 7, + 1, 1, 0, 0, 7, + 0, 0, 0, 0, 7, + 9, 10, 0, 0, 7, + 17, 18, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":24, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmx new file mode 100644 index 0000000..43ca51c --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/map-with-embedded-tileset.tmx @@ -0,0 +1,15 @@ + + + + + + + +1,1,0,0,7, +1,1,0,0,7, +0,0,0,0,7, +9,10,0,0,7, +17,18,0,0,0 + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/tileset.png b/DotTiled.Tests/Serialization/TestData/Map/map-with-embedded-tileset/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 From a1a2e3d3733a75138c647facee32df521f99de80 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 23:03:53 +0200 Subject: [PATCH 32/39] Add tests for external tilesets in maps --- DotTiled.Tests/Serialization/TestData.cs | 18 +-- .../CustomTypes/large-propertytypes.json | 103 ------------------ ...ap-with-object-template-propertytypes.json | 24 ---- .../map-with-external-tileset.cs | 79 ++++++++++++++ .../map-with-external-tileset.tmj | 36 ++++++ .../map-with-external-tileset.tmx | 13 +++ .../Map/map-with-external-tileset/tileset.png | Bin 0 -> 11908 bytes .../Map/map-with-external-tileset/tileset.tsj | 14 +++ .../Map/map-with-external-tileset/tileset.tsx | 4 + .../Serialization/Tmj/TmjMapReaderTests.cs | 9 +- .../Serialization/Tmx/TmxMapReaderTests.cs | 9 +- DotTiled/Serialization/Tmx/Tmx.Tileset.cs | 2 +- 12 files changed, 167 insertions(+), 144 deletions(-) delete mode 100644 DotTiled.Tests/Serialization/TestData/CustomTypes/large-propertytypes.json delete mode 100644 DotTiled.Tests/Serialization/TestData/CustomTypes/map-with-object-template-propertytypes.json create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.png create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsx diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index eac8d05..254cba7 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -6,9 +6,7 @@ public static partial class TestData { public static XmlReader GetXmlReaderFor(string testDataFile) { - var names = typeof(TestData).Assembly.GetManifestResourceNames(); - - var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; + var fullyQualifiedTestDataFile = $"DotTiled.Tests.{ConvertPathToAssemblyResourcePath(testDataFile)}"; using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); @@ -20,7 +18,7 @@ public static partial class TestData public static string GetRawStringFor(string testDataFile) { - var fullyQualifiedTestDataFile = $"DotTiled.Tests.{testDataFile}"; + var fullyQualifiedTestDataFile = $"DotTiled.Tests.{ConvertPathToAssemblyResourcePath(testDataFile)}"; using var stream = typeof(TestData).Assembly.GetManifestResourceStream(fullyQualifiedTestDataFile) ?? throw new ArgumentException($"Test data file '{fullyQualifiedTestDataFile}' not found"); @@ -28,12 +26,16 @@ public static partial class TestData return stringReader.ReadToEnd(); } + private static string ConvertPathToAssemblyResourcePath(string path) => + path.Replace("/", ".").Replace("\\", ".").Replace(" ", "_"); + public static IEnumerable MapTests => [ - ["Serialization.TestData.Map.default_map.default-map", TestData.DefaultMap(), Array.Empty()], - ["Serialization.TestData.Map.map_with_common_props.map-with-common-props", TestData.MapWithCommonProps(), Array.Empty()], - ["Serialization.TestData.Map.map_with_custom_type_props.map-with-custom-type-props", TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()], - ["Serialization.TestData.Map.map_with_embedded_tileset.map-with-embedded-tileset", TestData.MapWithEmbeddedTileset(), Array.Empty()], + ["Serialization/TestData/Map/default_map/default-map", (string f) => TestData.DefaultMap(), Array.Empty()], + ["Serialization/TestData/Map/map_with_common_props/map-with-common-props", (string f) => TestData.MapWithCommonProps(), Array.Empty()], + ["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()], + ["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => TestData.MapWithEmbeddedTileset(), Array.Empty()], + ["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => TestData.MapWithExternalTileset(f), Array.Empty()], ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/CustomTypes/large-propertytypes.json b/DotTiled.Tests/Serialization/TestData/CustomTypes/large-propertytypes.json deleted file mode 100644 index e21cf83..0000000 --- a/DotTiled.Tests/Serialization/TestData/CustomTypes/large-propertytypes.json +++ /dev/null @@ -1,103 +0,0 @@ -[ - { - "id": 4, - "name": "Enum0String", - "storageType": "string", - "type": "enum", - "values": [ - "Enum0_1", - "Enum0_2", - "Enum0_3" - ], - "valuesAsFlags": false - }, - { - "id": 5, - "name": "Enum1Num", - "storageType": "int", - "type": "enum", - "values": [ - "Enum1Num_1", - "Enum1Num_2", - "Enum1Num_3", - "Enum1Num_4" - ], - "valuesAsFlags": false - }, - { - "id": 6, - "name": "Enum2StringFlags", - "storageType": "string", - "type": "enum", - "values": [ - "Enum2StringFlags_1", - "Enum2StringFlags_2", - "Enum2StringFlags_3", - "Enum2StringFlags_4" - ], - "valuesAsFlags": true - }, - { - "id": 7, - "name": "Enum3NumFlags", - "storageType": "int", - "type": "enum", - "values": [ - "Enum3NumFlags_1", - "Enum3NumFlags_2", - "Enum3NumFlags_3", - "Enum3NumFlags_4", - "Enum3NumFlags_5" - ], - "valuesAsFlags": true - }, - { - "color": "#ffa0a0a4", - "drawFill": true, - "id": 2, - "members": [ - { - "name": "Yep", - "propertyType": "TestClass", - "type": "class", - "value": { - } - } - ], - "name": "Test", - "type": "class", - "useAs": [ - "property", - "map", - "layer", - "object", - "tile", - "tileset", - "wangcolor", - "wangset", - "project" - ] - }, - { - "color": "#ffa0a0a4", - "drawFill": true, - "id": 1, - "members": [ - { - "name": "Amount", - "type": "float", - "value": 0 - }, - { - "name": "Name", - "type": "string", - "value": "" - } - ], - "name": "TestClass", - "type": "class", - "useAs": [ - "property" - ] - } -] diff --git a/DotTiled.Tests/Serialization/TestData/CustomTypes/map-with-object-template-propertytypes.json b/DotTiled.Tests/Serialization/TestData/CustomTypes/map-with-object-template-propertytypes.json deleted file mode 100644 index 3505dce..0000000 --- a/DotTiled.Tests/Serialization/TestData/CustomTypes/map-with-object-template-propertytypes.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "color": "#ffa0a0a4", - "drawFill": true, - "id": 1, - "members": [ - { - "name": "Amount", - "type": "float", - "value": 0 - }, - { - "name": "Name", - "type": "string", - "value": "" - } - ], - "name": "TestClass", - "type": "class", - "useAs": [ - "property" - ] - } -] diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.cs new file mode 100644 index 0000000..10c4d67 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.cs @@ -0,0 +1,79 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithExternalTileset(string fileExt) => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Tilesets = [ + new Tileset + { + Version = "1.10", + TiledVersion = "1.11.0", + FirstGID = 1, + Name = "tileset", + TileWidth = 32, + TileHeight = 32, + TileCount = 24, + Columns = 8, + Source = $"tileset.{(fileExt == "tmx" ? "tsx" : "tsj")}", + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96, + } + } + ], + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 1, 1, 0, 0, 7, + 1, 1, 0, 0, 7, + 0, 0, 1, 0, 7, + 0, 0, 0, 1, 7, + 21, 21, 21, 21, 1 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmj new file mode 100644 index 0000000..89bef93 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmj @@ -0,0 +1,36 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[1, 1, 0, 0, 7, + 1, 1, 0, 0, 7, + 0, 0, 1, 0, 7, + 0, 0, 0, 1, 7, + 21, 21, 21, 21, 1], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"tileset.tsj" + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx new file mode 100644 index 0000000..06114fb --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/map-with-external-tileset.tmx @@ -0,0 +1,13 @@ + + + + + +1,1,0,0,7, +1,1,0,0,7, +0,0,1,0,7, +0,0,0,1,7, +21,21,21,21,1 + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.png b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsj b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsj new file mode 100644 index 0000000..820e88f --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsj @@ -0,0 +1,14 @@ +{ "columns":8, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":24, + "tiledversion":"1.11.0", + "tileheight":32, + "tilewidth":32, + "type":"tileset", + "version":"1.10" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsx b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsx new file mode 100644 index 0000000..d730182 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-external-tileset/tileset.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs index 71e5304..670fdf6 100644 --- a/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmj/TmjMapReaderTests.cs @@ -7,21 +7,22 @@ public partial class TmjMapReaderTests [MemberData(nameof(Maps))] public void TmxMapReaderReadMap_ValidTmjExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( string testDataFile, - Map expectedMap, + Func expectedMap, IReadOnlyCollection customTypeDefinitions) { // Arrange testDataFile += ".tmj"; + var fileDir = Path.GetDirectoryName(testDataFile); var json = TestData.GetRawStringFor(testDataFile); Template ResolveTemplate(string source) { - var templateJson = TestData.GetRawStringFor($"Serialization.TestData.Template.{source}"); + var templateJson = TestData.GetRawStringFor($"{fileDir}/{source}"); using var templateReader = new TjTemplateReader(templateJson, ResolveTileset, ResolveTemplate, customTypeDefinitions); return templateReader.ReadTemplate(); } Tileset ResolveTileset(string source) { - var tilesetJson = TestData.GetRawStringFor($"Serialization.TestData.Tileset.{source}"); + var tilesetJson = TestData.GetRawStringFor($"{fileDir}/{source}"); using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTemplate, customTypeDefinitions); return tilesetReader.ReadTileset(); } @@ -32,6 +33,6 @@ public partial class TmjMapReaderTests // Assert Assert.NotNull(map); - DotTiledAssert.AssertMap(expectedMap, map); + DotTiledAssert.AssertMap(expectedMap("tmj"), map); } } diff --git a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs index c0dc083..a99ee9a 100644 --- a/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/DotTiled.Tests/Serialization/Tmx/TmxMapReaderTests.cs @@ -9,21 +9,22 @@ public partial class TmxMapReaderTests [MemberData(nameof(Maps))] public void TmxMapReaderReadMap_ValidXmlExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( string testDataFile, - Map expectedMap, + Func expectedMap, IReadOnlyCollection customTypeDefinitions) { // Arrange testDataFile += ".tmx"; + var fileDir = Path.GetDirectoryName(testDataFile); using var reader = TestData.GetXmlReaderFor(testDataFile); Template ResolveTemplate(string source) { - using var xmlTemplateReader = TestData.GetXmlReaderFor($"Serialization.TestData.Template.{source}"); + using var xmlTemplateReader = TestData.GetXmlReaderFor($"{fileDir}/{source}"); using var templateReader = new TxTemplateReader(xmlTemplateReader, ResolveTileset, ResolveTemplate, customTypeDefinitions); return templateReader.ReadTemplate(); } Tileset ResolveTileset(string source) { - using var xmlTilesetReader = TestData.GetXmlReaderFor($"Serialization.TestData.Tileset.{source}"); + using var xmlTilesetReader = TestData.GetXmlReaderFor($"{fileDir}/{source}"); using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTemplate, customTypeDefinitions); return tilesetReader.ReadTileset(); } @@ -34,6 +35,6 @@ public partial class TmxMapReaderTests // Assert Assert.NotNull(map); - DotTiledAssert.AssertMap(expectedMap, map); + DotTiledAssert.AssertMap(expectedMap("tmx"), map); } } diff --git a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs index 3885dac..9f576bb 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs @@ -83,7 +83,7 @@ internal partial class Tmx var resolvedTileset = externalTilesetResolver(source); resolvedTileset.FirstGID = firstGID; - resolvedTileset.Source = null; + resolvedTileset.Source = source; return resolvedTileset; } From e68f7c178a0018837df72e1d04179e4a525c1d4c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Mon, 12 Aug 2024 23:20:47 +0200 Subject: [PATCH 33/39] Add tests for map with rotated/flipped tiles --- DotTiled.Tests/Serialization/TestData.cs | 1 + .../map-with-flippingflags.cs | 79 ++++++++++++++++++ .../map-with-flippingflags.tmj | 36 ++++++++ .../map-with-flippingflags.tmx | 13 +++ .../Map/map-with-flippingflags/tileset.png | Bin 0 -> 11908 bytes .../Map/map-with-flippingflags/tileset.tsj | 14 ++++ .../Map/map-with-flippingflags/tileset.tsx | 4 + 7 files changed, 147 insertions(+) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.png create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsx diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 254cba7..98a59be 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -36,6 +36,7 @@ public static partial class TestData ["Serialization/TestData/Map/map_with_custom_type_props/map-with-custom-type-props", (string f) => TestData.MapWithCustomTypeProps(), TestData.MapWithCustomTypePropsCustomTypeDefinitions()], ["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => TestData.MapWithEmbeddedTileset(), Array.Empty()], ["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => TestData.MapWithExternalTileset(f), Array.Empty()], + ["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => TestData.MapWithFlippingFlags(f), Array.Empty()], ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.cs new file mode 100644 index 0000000..4e181c4 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.cs @@ -0,0 +1,79 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithFlippingFlags(string fileExt) => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Tilesets = [ + new Tileset + { + Version = "1.10", + TiledVersion = "1.11.0", + FirstGID = 1, + Name = "tileset", + TileWidth = 32, + TileHeight = 32, + TileCount = 24, + Columns = 8, + Source = $"tileset.{(fileExt == "tmx" ? "tsx" : "tsj")}", + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96, + } + } + ], + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 1, 1, 0, 0, 7, + 1, 1, 0, 0, 7, + 0, 0, 1, 0, 7, + 0, 0, 0, 1, 7, + 21, 21, 21, 21, 1 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.FlippedDiagonally | FlippingFlags.FlippedHorizontally, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically, + FlippingFlags.FlippedDiagonally | FlippingFlags.FlippedVertically, FlippingFlags.FlippedVertically | FlippingFlags.FlippedHorizontally, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically, + FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.None + ] + } + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmj new file mode 100644 index 0000000..3b74128 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmj @@ -0,0 +1,36 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[1, 2684354561, 0, 0, 1073741831, + 1610612737, 3221225473, 0, 0, 1073741831, + 0, 0, 1, 0, 1073741831, + 0, 0, 0, 1, 1073741831, + 2147483669, 2147483669, 2147483669, 2147483669, 1], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"tileset.tsj" + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmx new file mode 100644 index 0000000..a72cd1a --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/map-with-flippingflags.tmx @@ -0,0 +1,13 @@ + + + + + +1,2684354561,0,0,1073741831, +1610612737,3221225473,0,0,1073741831, +0,0,1,0,1073741831, +0,0,0,1,1073741831, +2147483669,2147483669,2147483669,2147483669,1 + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.png b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsj b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsj new file mode 100644 index 0000000..820e88f --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsj @@ -0,0 +1,14 @@ +{ "columns":8, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":24, + "tiledversion":"1.11.0", + "tileheight":32, + "tilewidth":32, + "type":"tileset", + "version":"1.10" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsx b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsx new file mode 100644 index 0000000..d730182 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-flippingflags/tileset.tsx @@ -0,0 +1,4 @@ + + + + From 3645f8c2a29f21c77f4602cedc44564a1728b0d8 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Tue, 13 Aug 2024 21:39:08 +0200 Subject: [PATCH 34/39] Add some tileset tests --- DotTiled.Tests/Serialization/TestData.cs | 1 + .../map-external-tileset-multi.cs | 121 ++++++++++++++++++ .../map-external-tileset-multi.tmj | 36 ++++++ .../map-external-tileset-multi.tmx | 13 ++ .../multi-tileset.tsj | 71 ++++++++++ .../multi-tileset.tsx | 20 +++ .../map-external-tileset-multi/tileset.png | Bin 0 -> 11908 bytes DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 8 +- 8 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/tileset.png diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 98a59be..005bc82 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -37,6 +37,7 @@ public static partial class TestData ["Serialization/TestData/Map/map_with_embedded_tileset/map-with-embedded-tileset", (string f) => TestData.MapWithEmbeddedTileset(), Array.Empty()], ["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => TestData.MapWithExternalTileset(f), Array.Empty()], ["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => TestData.MapWithFlippingFlags(f), Array.Empty()], + ["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => TestData.MapExternalTilesetMulti(f), Array.Empty()], ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.cs b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.cs new file mode 100644 index 0000000..24651b6 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.cs @@ -0,0 +1,121 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapExternalTilesetMulti(string fileExt) => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Tilesets = [ + new Tileset + { + TileOffset = new TileOffset + { + X = 1, + Y = 5 + }, + Version = "1.10", + TiledVersion = "1.11.0", + FirstGID = 1, + Name = "multi-tileset", + TileWidth = 256, + TileHeight = 96, + TileCount = 2, + Columns = 0, + Source = $"multi-tileset.{(fileExt == "tmx" ? "tsx" : "tsj")}", + Grid = new Grid + { + Orientation = GridOrientation.Orthogonal, + Width = 1, + Height = 1 + }, + Properties = new Dictionary + { + ["tilesetbool"] = new BoolProperty { Name = "tilesetbool", Value = true }, + ["tilesetcolor"] = new ColorProperty { Name = "tilesetcolor", Value = Color.Parse("#ffff0000", CultureInfo.InvariantCulture) }, + ["tilesetfile"] = new FileProperty { Name = "tilesetfile", Value = "" }, + ["tilesetfloat"] = new FloatProperty { Name = "tilesetfloat", Value = 5.2f }, + ["tilesetint"] = new IntProperty { Name = "tilesetint", Value = 9 }, + ["tilesetobject"] = new ObjectProperty { Name = "tilesetobject", Value = 0 }, + ["tilesetstring"] = new StringProperty { Name = "tilesetstring", Value = "hello world!" } + }, + Tiles = [ + new Tile + { + ID = 0, + Width = 256, + Height = 96, + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96 + } + }, + new Tile + { + ID = 1, + Width = 256, + Height = 96, + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96 + } + } + ] + } + ], + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmj new file mode 100644 index 0000000..da37182 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmj @@ -0,0 +1,36 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"multi-tileset.tsj" + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmx new file mode 100644 index 0000000..477c112 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/map-external-tileset-multi.tmx @@ -0,0 +1,13 @@ + + + + + +0,0,0,0,0, +0,0,0,0,0, +1,0,0,0,0, +0,0,0,0,0, +0,2,0,0,0 + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsj b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsj new file mode 100644 index 0000000..d190934 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsj @@ -0,0 +1,71 @@ +{ "columns":0, + "grid": + { + "height":1, + "orientation":"orthogonal", + "width":1 + }, + "margin":0, + "name":"multi-tileset", + "properties":[ + { + "name":"tilesetbool", + "type":"bool", + "value":true + }, + { + "name":"tilesetcolor", + "type":"color", + "value":"#ffff0000" + }, + { + "name":"tilesetfile", + "type":"file", + "value":"" + }, + { + "name":"tilesetfloat", + "type":"float", + "value":5.2 + }, + { + "name":"tilesetint", + "type":"int", + "value":9 + }, + { + "name":"tilesetobject", + "type":"object", + "value":0 + }, + { + "name":"tilesetstring", + "type":"string", + "value":"hello world!" + }], + "spacing":0, + "tilecount":2, + "tiledversion":"1.11.0", + "tileheight":96, + "tileoffset": + { + "x":1, + "y":5 + }, + "tiles":[ + { + "id":0, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256 + }, + { + "id":1, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256 + }], + "tilewidth":256, + "type":"tileset", + "version":"1.10" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsx b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsx new file mode 100644 index 0000000..a28bfac --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/multi-tileset.tsx @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/tileset.png b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-multi/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs index fd5088b..6eefdb7 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -77,14 +77,14 @@ internal partial class Tmj return resolvedTileset; } - var imageModel = new Image + var imageModel = image is not null ? new Image { - Format = Helpers.ParseImageFormatFromSource(image!), + Format = Helpers.ParseImageFormatFromSource(image), Source = image, Height = imageHeight, Width = imageWidth, TransparentColor = transparentColor - }; + } : null; return new Tileset { @@ -159,7 +159,7 @@ internal partial class Tmj var width = e.GetOptionalProperty("width", imageWidth ?? 0); var height = e.GetOptionalProperty("height", imageHeight ?? 0); var objectGroup = e.GetOptionalPropertyCustom("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null); - var probability = e.GetOptionalProperty("probability", 1.0f); + var probability = e.GetOptionalProperty("probability", 0.0f); var properties = e.GetOptionalPropertyCustom?>("properties", el => ReadProperties(el, customTypeDefinitions), null); // var terrain, replaced by wangsets var type = e.GetOptionalProperty("type", ""); From 292dc9b1b9e101b5bc59fd7d73c783a4b3aa3a40 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Tue, 13 Aug 2024 22:34:51 +0200 Subject: [PATCH 35/39] Add wangset test --- DotTiled.Tests/Serialization/TestData.cs | 1 + .../map-external-tileset-wangset.cs | 92 ++++++++++++++++++ .../map-external-tileset-wangset.tmj | 36 +++++++ .../map-external-tileset-wangset.tmx | 13 +++ .../map-external-tileset-wangset/tileset.png | Bin 0 -> 11908 bytes .../wangset-tileset.tsj | 69 +++++++++++++ .../wangset-tileset.tsx | 17 ++++ DotTiled/Model/Tileset/WangColor.cs | 2 +- DotTiled/Model/Tileset/Wangset.cs | 2 +- DotTiled/Serialization/Tmj/Tmj.Tileset.cs | 26 ++++- DotTiled/Serialization/Tmx/Tmx.Tileset.cs | 14 +-- 11 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/tileset.png create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsx diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index 005bc82..d2d3316 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -38,6 +38,7 @@ public static partial class TestData ["Serialization/TestData/Map/map_with_external_tileset/map-with-external-tileset", (string f) => TestData.MapWithExternalTileset(f), Array.Empty()], ["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => TestData.MapWithFlippingFlags(f), Array.Empty()], ["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => TestData.MapExternalTilesetMulti(f), Array.Empty()], + ["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => TestData.MapExternalTilesetWangset(f), Array.Empty()], ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.cs b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.cs new file mode 100644 index 0000000..9aaa7d7 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.cs @@ -0,0 +1,92 @@ +using System.Globalization; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapExternalTilesetWangset(string fileExt) => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 24, + TileHeight = 24, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = Color.Parse("#00000000", CultureInfo.InvariantCulture), + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 2, + NextObjectID = 1, + Tilesets = [ + new Tileset + { + Version = "1.10", + TiledVersion = "1.11.0", + FirstGID = 1, + Name = "tileset", + TileWidth = 24, + TileHeight = 24, + TileCount = 48, + Columns = 10, + Source = $"wangset-tileset.{(fileExt == "tmx" ? "tsx" : "tsj")}", + Transformations = new Transformations + { + HFlip = true, + VFlip = true, + Rotate = false, + PreferUntransformed = false + }, + Grid = new Grid + { + Orientation = GridOrientation.Orthogonal, + Width = 32, + Height = 32 + }, + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96, + } + } + ], + Layers = [ + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 2, 2, 12, 11, 0, + 1, 12, 1, 11, 0, + 2, 1, 0, 1, 0, + 12, 11, 12, 2, 0, + 0, 0, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.FlippedHorizontally, FlippingFlags.None, FlippingFlags.FlippedHorizontally, FlippingFlags.FlippedHorizontally, FlippingFlags.None, + FlippingFlags.FlippedVertically, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically | FlippingFlags.FlippedHorizontally, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedVertically | FlippingFlags.FlippedHorizontally, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.FlippedHorizontally, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmj new file mode 100644 index 0000000..cea9ad6 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmj @@ -0,0 +1,36 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "data":[2147483650, 2, 2147483660, 2147483659, 0, + 1073741825, 12, 1, 3221225483, 0, + 2, 1, 0, 3221225473, 0, + 12, 11, 12, 2147483650, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":24, + "tilesets":[ + { + "firstgid":1, + "source":"wangset-tileset.tsj" + }], + "tilewidth":24, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmx new file mode 100644 index 0000000..656fddb --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/map-external-tileset-wangset.tmx @@ -0,0 +1,13 @@ + + + + + +2147483650,2,2147483660,2147483659,0, +1073741825,12,1,3221225483,0, +2,1,0,3221225473,0, +12,11,12,2147483650,0, +0,0,0,0,0 + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/tileset.png b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsj b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsj new file mode 100644 index 0000000..511641a --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsj @@ -0,0 +1,69 @@ +{ "columns":10, + "grid": + { + "height":32, + "orientation":"orthogonal", + "width":32 + }, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":48, + "tiledversion":"1.11.0", + "tileheight":24, + "tilewidth":24, + "transformations": + { + "hflip":true, + "preferuntransformed":false, + "rotate":false, + "vflip":true + }, + "type":"tileset", + "version":"1.10", + "wangsets":[ + { + "colors":[ + { + "color":"#ff0000", + "name":"Water", + "probability":1, + "tile":0 + }, + { + "color":"#00ff00", + "name":"Grass", + "probability":1, + "tile":-1 + }, + { + "color":"#0000ff", + "name":"Stone", + "probability":1, + "tile":29 + }], + "name":"test-terrain", + "tile":-1, + "type":"mixed", + "wangtiles":[ + { + "tileid":0, + "wangid":[1, 1, 0, 0, 0, 1, 1, 1] + }, + { + "tileid":1, + "wangid":[1, 1, 1, 1, 0, 0, 0, 1] + }, + { + "tileid":10, + "wangid":[0, 0, 0, 1, 1, 1, 1, 1] + }, + { + "tileid":11, + "wangid":[0, 1, 1, 1, 1, 1, 0, 0] + }] + }] +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsx b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsx new file mode 100644 index 0000000..d2b8666 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-external-tileset-wangset/wangset-tileset.tsx @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/DotTiled/Model/Tileset/WangColor.cs b/DotTiled/Model/Tileset/WangColor.cs index e278bb9..9ac751c 100644 --- a/DotTiled/Model/Tileset/WangColor.cs +++ b/DotTiled/Model/Tileset/WangColor.cs @@ -8,7 +8,7 @@ public class WangColor public required string Name { get; set; } public string Class { get; set; } = ""; public required Color Color { get; set; } - public required uint Tile { get; set; } + public required int Tile { get; set; } public float Probability { get; set; } = 0f; // Elements diff --git a/DotTiled/Model/Tileset/Wangset.cs b/DotTiled/Model/Tileset/Wangset.cs index 8d4d1a5..61f8496 100644 --- a/DotTiled/Model/Tileset/Wangset.cs +++ b/DotTiled/Model/Tileset/Wangset.cs @@ -7,7 +7,7 @@ public class Wangset // Attributes public required string Name { get; set; } public string Class { get; set; } = ""; - public required uint Tile { get; set; } + public required int Tile { get; set; } // Elements // At most one of diff --git a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs index 6eefdb7..d455b42 100644 --- a/DotTiled/Serialization/Tmj/Tmj.Tileset.cs +++ b/DotTiled/Serialization/Tmj/Tmj.Tileset.cs @@ -64,7 +64,8 @@ internal partial class Tmj var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); var type = element.GetOptionalProperty("type", null); var version = element.GetOptionalProperty("version", null); - //var wangsets = element.GetOptionalPropertyCustom?>("wangsets", ReadWangSets, null); + var transformations = element.GetOptionalPropertyCustom("transformations", ReadTransformations, null); + var wangsets = element.GetOptionalPropertyCustom?>("wangsets", el => el.GetValueAsList(e => ReadWangset(e, customTypeDefinitions)), null); if (source is not null) { @@ -108,7 +109,24 @@ internal partial class Tmj Tiles = tiles, TileWidth = tileWidth, Version = version, - //Wangsets = wangsets + Wangsets = wangsets, + Transformations = transformations + }; + } + + internal static Transformations ReadTransformations(JsonElement element) + { + var hFlip = element.GetOptionalProperty("hflip", false); + var vFlip = element.GetOptionalProperty("vflip", false); + var rotate = element.GetOptionalProperty("rotate", false); + var preferUntransformed = element.GetOptionalProperty("preferuntransformed", false); + + return new Transformations + { + HFlip = hFlip, + VFlip = vFlip, + Rotate = rotate, + PreferUntransformed = preferUntransformed }; } @@ -208,7 +226,7 @@ internal partial class Tmj var colors = element.GetOptionalPropertyCustom>("colors", e => e.GetValueAsList(el => ReadWangColor(el, customTypeDefinitions)), []); var name = element.GetRequiredProperty("name"); var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - var tile = element.GetOptionalProperty("tile", 0); + var tile = element.GetOptionalProperty("tile", 0); var type = element.GetOptionalProperty("type", ""); var wangTiles = element.GetOptionalPropertyCustom>("wangtiles", e => e.GetValueAsList(ReadWangTile), []); @@ -232,7 +250,7 @@ internal partial class Tmj var name = element.GetRequiredProperty("name"); var probability = element.GetOptionalProperty("probability", 1.0f); var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); - var tile = element.GetOptionalProperty("tile", 0); + var tile = element.GetOptionalProperty("tile", 0); return new WangColor { diff --git a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs index 9f576bb..1912df2 100644 --- a/DotTiled/Serialization/Tmx/Tmx.Tileset.cs +++ b/DotTiled/Serialization/Tmx/Tmx.Tileset.cs @@ -193,10 +193,10 @@ internal partial class Tmx 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; + var hFlip = (reader.GetOptionalAttributeParseable("hflip") ?? 0) == 1; + var vFlip = (reader.GetOptionalAttributeParseable("vflip") ?? 0) == 1; + var rotate = (reader.GetOptionalAttributeParseable("rotate") ?? 0) == 1; + var preferUntransformed = (reader.GetOptionalAttributeParseable("preferuntransformed") ?? 0) == 1; reader.ReadStartElement("transformations"); return new Transformations { HFlip = hFlip, VFlip = vFlip, Rotate = rotate, PreferUntransformed = preferUntransformed }; @@ -266,7 +266,7 @@ internal partial class Tmx // Attributes var name = reader.GetRequiredAttribute("name"); var @class = reader.GetOptionalAttribute("class") ?? ""; - var tile = reader.GetRequiredAttributeParseable("tile"); + var tile = reader.GetRequiredAttributeParseable("tile"); // Elements Dictionary? properties = null; @@ -303,7 +303,7 @@ internal partial class Tmx var name = reader.GetRequiredAttribute("name"); var @class = reader.GetOptionalAttribute("class") ?? ""; var color = reader.GetRequiredAttributeParseable("color"); - var tile = reader.GetRequiredAttributeParseable("tile"); + var tile = reader.GetRequiredAttributeParseable("tile"); var probability = reader.GetOptionalAttributeParseable("probability") ?? 0f; // Elements @@ -339,6 +339,8 @@ internal partial class Tmx return indices; }); + reader.ReadStartElement("wangtile"); + return new WangTile { TileID = tileID, From 653e5b5326d5429252cac4b81120e619ec4f148c Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Tue, 13 Aug 2024 23:18:42 +0200 Subject: [PATCH 36/39] Add final tests --- DotTiled.Tests/Assert/AssertMap.cs | 40 ++++ DotTiled.Tests/Assert/AssertObject.cs | 7 +- DotTiled.Tests/Serialization/TestData.cs | 1 + .../map-with-many-layers.cs | 214 ++++++++++++++++++ .../map-with-many-layers.tmj | 181 +++++++++++++++ .../map-with-many-layers.tmx | 51 +++++ .../Map/map-with-many-layers/tileset.png | Bin 0 -> 11908 bytes .../Map/map-with-many-layers/tileset.tsj | 14 ++ .../Map/map-with-many-layers/tileset.tsx | 4 + DotTiled/Model/Layers/ImageLayer.cs | 4 +- DotTiled/Model/Layers/Objects/Object.cs | 1 - DotTiled/Model/Layers/Objects/TileObject.cs | 6 + DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs | 4 +- DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs | 26 ++- DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs | 9 +- DotTiled/Serialization/Tmx/Tmx.TileLayer.cs | 4 +- 16 files changed, 549 insertions(+), 17 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.png create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsx create mode 100644 DotTiled/Model/Layers/Objects/TileObject.cs diff --git a/DotTiled.Tests/Assert/AssertMap.cs b/DotTiled.Tests/Assert/AssertMap.cs index 6596ee8..e9ad8be 100644 --- a/DotTiled.Tests/Assert/AssertMap.cs +++ b/DotTiled.Tests/Assert/AssertMap.cs @@ -1,3 +1,6 @@ +using System.Collections; +using System.Numerics; + namespace DotTiled.Tests; public static partial class DotTiledAssert @@ -10,6 +13,29 @@ public static partial class DotTiledAssert return; } + if (typeof(T) == typeof(float)) + { + var expectedFloat = (float)(object)expected; + var actualFloat = (float)(object)actual!; + + var expecRounded = MathF.Round(expectedFloat, 3); + var actRounded = MathF.Round(actualFloat, 3); + + Assert.True(expecRounded == actRounded, $"Expected {nameof} '{expecRounded}' but got '{actRounded}'"); + return; + } + + if (expected is Vector2) + { + var expectedVector = (Vector2)(object)expected; + var actualVector = (Vector2)(object)actual!; + + AssertEqual(expectedVector.X, actualVector.X, $"{nameof}.X"); + AssertEqual(expectedVector.Y, actualVector.Y, $"{nameof}.Y"); + + return; + } + if (typeof(T).IsArray) { var expectedArray = (Array)(object)expected; @@ -24,6 +50,20 @@ public static partial class DotTiledAssert return; } + if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(List<>)) + { + var expectedList = (IList)(object)expected; + var actualList = (IList)(object)actual!; + + Assert.NotNull(actualList); + AssertEqual(expectedList.Count, actualList.Count, $"{nameof}.Count"); + + for (var i = 0; i < expectedList.Count; i++) + AssertEqual(expectedList[i], actualList[i], $"{nameof}[{i}]"); + + return; + } + Assert.True(expected.Equals(actual), $"Expected {nameof} '{expected}' but got '{actual}'"); } diff --git a/DotTiled.Tests/Assert/AssertObject.cs b/DotTiled.Tests/Assert/AssertObject.cs index c49b6e7..bd303f9 100644 --- a/DotTiled.Tests/Assert/AssertObject.cs +++ b/DotTiled.Tests/Assert/AssertObject.cs @@ -13,7 +13,6 @@ public static partial class DotTiledAssert AssertEqual(expected.Width, actual.Width, nameof(Object.Width)); AssertEqual(expected.Height, actual.Height, nameof(Object.Height)); AssertEqual(expected.Rotation, actual.Rotation, nameof(Object.Rotation)); - AssertEqual(expected.GID, actual.GID, nameof(Object.GID)); AssertEqual(expected.Visible, actual.Visible, nameof(Object.Visible)); AssertEqual(expected.Template, actual.Template, nameof(Object.Template)); @@ -63,4 +62,10 @@ public static partial class DotTiledAssert AssertEqual(expected.Text, actual.Text, nameof(TextObject.Text)); } + + private static void AssertObject(TileObject expected, TileObject actual) + { + // Attributes + AssertEqual(expected.GID, actual.GID, nameof(TileObject.GID)); + } } diff --git a/DotTiled.Tests/Serialization/TestData.cs b/DotTiled.Tests/Serialization/TestData.cs index d2d3316..c3d52f8 100644 --- a/DotTiled.Tests/Serialization/TestData.cs +++ b/DotTiled.Tests/Serialization/TestData.cs @@ -39,6 +39,7 @@ public static partial class TestData ["Serialization/TestData/Map/map_with_flippingflags/map-with-flippingflags", (string f) => TestData.MapWithFlippingFlags(f), Array.Empty()], ["Serialization/TestData/Map/map_external_tileset_multi/map-external-tileset-multi", (string f) => TestData.MapExternalTilesetMulti(f), Array.Empty()], ["Serialization/TestData/Map/map_external_tileset_wangset/map-external-tileset-wangset", (string f) => TestData.MapExternalTilesetWangset(f), Array.Empty()], + ["Serialization/TestData/Map/map_with_many_layers/map-with-many-layers", (string f) => TestData.MapWithManyLayers(f), Array.Empty()], ]; private static CustomTypeDefinition[] typedefs = [ diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs new file mode 100644 index 0000000..8ef6ce5 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs @@ -0,0 +1,214 @@ +using System.Numerics; + +namespace DotTiled.Tests; + +public partial class TestData +{ + public static Map MapWithManyLayers(string fileExt) => new Map + { + Class = "", + Orientation = MapOrientation.Orthogonal, + Width = 5, + Height = 5, + TileWidth = 32, + TileHeight = 32, + Infinite = false, + HexSideLength = null, + StaggerAxis = null, + StaggerIndex = null, + ParallaxOriginX = 0, + ParallaxOriginY = 0, + RenderOrder = RenderOrder.RightDown, + CompressionLevel = -1, + BackgroundColor = new Color { R = 0, G = 0, B = 0, A = 0 }, + Version = "1.10", + TiledVersion = "1.11.0", + NextLayerID = 8, + NextObjectID = 7, + Tilesets = [ + new Tileset + { + Version = "1.10", + TiledVersion = "1.11.0", + FirstGID = 1, + Name = "tileset", + TileWidth = 32, + TileHeight = 32, + TileCount = 24, + Columns = 8, + Source = $"tileset.{(fileExt == "tmx" ? "tsx" : "tsj")}", + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = 256, + Height = 96, + } + } + ], + Layers = [ + new Group + { + ID = 2, + Name = "Root", + Layers = [ + new ObjectLayer + { + ID = 3, + Name = "Objects", + Objects = [ + new RectangleObject + { + ID = 1, + Name = "Object 1", + X = 25.6667f, + Y = 28.6667f, + Width = 31.3333f, + Height = 31.3333f + }, + new PointObject + { + ID = 3, + Name = "P1", + X = 117.667f, + Y = 48.6667f + }, + new EllipseObject + { + ID = 4, + Name = "Circle1", + X = 77f, + Y = 72.3333f, + Width = 34.6667f, + Height = 34.6667f + }, + new PolygonObject + { + ID = 5, + Name = "Poly", + X = 20.6667f, + Y = 114.667f, + Points = [ + new Vector2(0, 0), + new Vector2(104,20), + new Vector2(35.6667f, 32.3333f) + ] + }, + new TileObject + { + ID = 6, + Name = "TileObj", + GID = 7, + X = -35, + Y = 110.333f, + Width = 64, + Height = 146 + } + ] + }, + new Group + { + ID = 5, + Name = "Sub", + Layers = [ + new TileLayer + { + ID = 7, + Name = "Tile 3", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + }, + new TileLayer + { + ID = 6, + Name = "Tile 2", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 0, 15, 15, 0, 0, + 0, 15, 15, 0, 0, + 0, 15, 15, 15, 0, + 15, 15, 15, 0, 0, + 0, 0, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ] + }, + new ImageLayer + { + ID = 4, + Name = "ImageLayer", + Image = new Image + { + Format = ImageFormat.Png, + Source = "tileset.png", + Width = fileExt == "tmx" ? 256u : 0, // Currently, json format does not + Height = fileExt == "tmx" ? 96u : 0 // include image dimensions in image layer https://github.com/mapeditor/tiled/issues/4028 + }, + RepeatX = true + }, + new TileLayer + { + ID = 1, + Name = "Tile Layer 1", + Width = 5, + Height = 5, + Data = new Data + { + Encoding = DataEncoding.Csv, + Chunks = null, + Compression = null, + GlobalTileIDs = [ + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + ], + FlippingFlags = [ + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, + FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None, FlippingFlags.None + ] + } + } + ] + } + ] + }; +} diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj new file mode 100644 index 0000000..16561f4 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj @@ -0,0 +1,181 @@ +{ "compressionlevel":-1, + "height":5, + "infinite":false, + "layers":[ + { + "id":2, + "layers":[ + { + "draworder":"topdown", + "id":3, + "name":"Objects", + "objects":[ + { + "height":31.3333333333333, + "id":1, + "name":"Object 1", + "rotation":0, + "type":"", + "visible":true, + "width":31.3333333333333, + "x":25.6666666666667, + "y":28.6666666666667 + }, + { + "height":0, + "id":3, + "name":"P1", + "point":true, + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":117.666666666667, + "y":48.6666666666667 + }, + { + "ellipse":true, + "height":34.6666666666667, + "id":4, + "name":"Circle1", + "rotation":0, + "type":"", + "visible":true, + "width":34.6666666666667, + "x":77, + "y":72.3333333333333 + }, + { + "height":0, + "id":5, + "name":"Poly", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":104, + "y":20 + }, + { + "x":35.6666666666667, + "y":32.3333333333333 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":20.6666666666667, + "y":114.666666666667 + }, + { + "gid":7, + "height":146, + "id":6, + "name":"TileObj", + "rotation":0, + "type":"", + "visible":true, + "width":64, + "x":-35, + "y":110.333333333333 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "id":5, + "layers":[ + { + "data":[0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":7, + "name":"Tile 3", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }, + { + "data":[0, 15, 15, 0, 0, + 0, 15, 15, 0, 0, + 0, 15, 15, 15, 0, + 15, 15, 15, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":6, + "name":"Tile 2", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "name":"Sub", + "opacity":1, + "type":"group", + "visible":true, + "x":0, + "y":0 + }, + { + "id":4, + "image":"tileset.png", + "name":"ImageLayer", + "opacity":1, + "repeatx":true, + "type":"imagelayer", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "height":5, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":5, + "x":0, + "y":0 + }], + "name":"Root", + "opacity":1, + "type":"group", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":8, + "nextobjectid":7, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.0", + "tileheight":32, + "tilesets":[ + { + "firstgid":1, + "source":"tileset.tsj" + }], + "tilewidth":32, + "type":"map", + "version":"1.10", + "width":5 +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx new file mode 100644 index 0000000..34cd91c --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + + +0,15,15,0,0, +0,15,15,0,0, +0,15,15,15,0, +15,15,15,0,0, +0,0,0,0,0 + + + + + + + + +1,1,1,1,1, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0, +0,0,0,0,0 + + + + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.png b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..97c1fb31b1fa4dcf1214b8fe9b2e2b8e287c72d0 GIT binary patch literal 11908 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 Pn-8F-q^(#jZyEZ3BaoJ| literal 0 HcmV?d00001 diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsj b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsj new file mode 100644 index 0000000..820e88f --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsj @@ -0,0 +1,14 @@ +{ "columns":8, + "image":"tileset.png", + "imageheight":96, + "imagewidth":256, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":24, + "tiledversion":"1.11.0", + "tileheight":32, + "tilewidth":32, + "type":"tileset", + "version":"1.10" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsx b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsx new file mode 100644 index 0000000..d730182 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/tileset.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/DotTiled/Model/Layers/ImageLayer.cs b/DotTiled/Model/Layers/ImageLayer.cs index 6489c22..a140b0d 100644 --- a/DotTiled/Model/Layers/ImageLayer.cs +++ b/DotTiled/Model/Layers/ImageLayer.cs @@ -5,8 +5,8 @@ public class ImageLayer : BaseLayer // Attributes public uint X { get; set; } = 0; public uint Y { get; set; } = 0; - public required bool RepeatX { get; set; } - public required bool RepeatY { get; set; } + public bool RepeatX { get; set; } = false; + public bool RepeatY { get; set; } = false; // At most one of public Image? Image { get; set; } diff --git a/DotTiled/Model/Layers/Objects/Object.cs b/DotTiled/Model/Layers/Objects/Object.cs index b3313d7..765de69 100644 --- a/DotTiled/Model/Layers/Objects/Object.cs +++ b/DotTiled/Model/Layers/Objects/Object.cs @@ -13,7 +13,6 @@ public abstract class Object public float Width { get; set; } = 0f; public float Height { get; set; } = 0f; public float Rotation { get; set; } = 0f; - public uint? GID { get; set; } public bool Visible { get; set; } = true; public string? Template { get; set; } diff --git a/DotTiled/Model/Layers/Objects/TileObject.cs b/DotTiled/Model/Layers/Objects/TileObject.cs new file mode 100644 index 0000000..c066780 --- /dev/null +++ b/DotTiled/Model/Layers/Objects/TileObject.cs @@ -0,0 +1,6 @@ +namespace DotTiled; + +public class TileObject : Object +{ + public uint GID { get; set; } +} diff --git a/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs b/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs index d315891..dbd75a1 100644 --- a/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs +++ b/DotTiled/Serialization/Tmj/Tmj.ImageLayer.cs @@ -25,8 +25,8 @@ internal partial class Tmj var properties = element.GetOptionalPropertyCustom?>("properties", e => ReadProperties(e, customTypeDefinitions), null); var image = element.GetRequiredProperty("image"); - var repeatX = element.GetRequiredProperty("repeatx"); - var repeatY = element.GetRequiredProperty("repeaty"); + var repeatX = element.GetOptionalProperty("repeatx", false); + var repeatY = element.GetOptionalProperty("repeaty", false); var transparentColor = element.GetOptionalPropertyParseable("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null); var x = element.GetOptionalProperty("x", 0); var y = element.GetOptionalProperty("y", 0); diff --git a/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs b/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs index 2fdf3c9..564f2db 100644 --- a/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs +++ b/DotTiled/Serialization/Tmj/Tmj.ObjectLayer.cs @@ -97,7 +97,6 @@ internal partial class Tmj widthDefault = templObj.Width; heightDefault = templObj.Height; rotationDefault = templObj.Rotation; - gidDefault = templObj.GID; visibleDefault = templObj.Visible; propertiesDefault = templObj.Properties; ellipseDefault = templObj is EllipseObject; @@ -123,6 +122,25 @@ internal partial class Tmj var x = element.GetOptionalProperty("x", xDefault); var y = element.GetOptionalProperty("y", yDefault); + if (gid is not null) + { + return new TileObject + { + ID = id, + Name = name, + Type = type, + X = x, + Y = y, + Width = width, + Height = height, + Rotation = rotation, + Visible = visible, + Template = template, + Properties = properties, + GID = gid.Value + }; + } + if (ellipse) { return new EllipseObject @@ -135,7 +153,6 @@ internal partial class Tmj Width = width, Height = height, Rotation = rotation, - GID = gid, Visible = visible, Template = template, Properties = properties @@ -154,7 +171,6 @@ internal partial class Tmj Width = width, Height = height, Rotation = rotation, - GID = gid, Visible = visible, Template = template, Properties = properties @@ -173,7 +189,6 @@ internal partial class Tmj Width = width, Height = height, Rotation = rotation, - GID = gid, Visible = visible, Template = template, Properties = properties, @@ -193,7 +208,6 @@ internal partial class Tmj Width = width, Height = height, Rotation = rotation, - GID = gid, Visible = visible, Template = template, Properties = properties, @@ -211,7 +225,6 @@ internal partial class Tmj text.Width = width; text.Height = height; text.Rotation = rotation; - text.GID = gid; text.Visible = visible; text.Template = template; text.Properties = properties; @@ -228,7 +241,6 @@ internal partial class Tmj Width = width, Height = height, Rotation = rotation, - GID = gid, Visible = visible, Template = template, Properties = properties diff --git a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs index 2367974..fa80805 100644 --- a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs @@ -105,7 +105,6 @@ internal partial class Tmx widthDefault = templObj.Width; heightDefault = templObj.Height; rotationDefault = templObj.Rotation; - gidDefault = templObj.GID; visibleDefault = templObj.Visible; propertiesDefault = templObj.Properties; } @@ -137,12 +136,19 @@ internal partial class Tmx _ => throw new Exception($"Unknown object marker '{elementName}'") }); + if (gid is not null) + { + obj = new TileObject { ID = id, GID = gid.Value }; + reader.Skip(); + } + if (obj is null) { obj = new RectangleObject { ID = id }; reader.Skip(); } + obj.ID = id; obj.Name = name; obj.Type = type; obj.X = x; @@ -150,7 +156,6 @@ internal partial class Tmx obj.Width = width; obj.Height = height; obj.Rotation = rotation; - obj.GID = gid; obj.Visible = visible; obj.Template = template; obj.Properties = properties; diff --git a/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs b/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs index 78096e3..6fc64fb 100644 --- a/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.TileLayer.cs @@ -74,8 +74,8 @@ internal partial class Tmx 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"); + var repeatX = (reader.GetOptionalAttributeParseable("repeatx") ?? 0) == 1; + var repeatY = (reader.GetOptionalAttributeParseable("repeaty") ?? 0) == 1; Dictionary? properties = null; Image? image = null; From aecd97bd7d1f72c4a81454e6739ed35095208614 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Wed, 14 Aug 2024 20:29:55 +0200 Subject: [PATCH 37/39] More tests --- DotTiled.Tests/Assert/AssertObject.cs | 2 + .../map-with-many-layers.cs | 7 +- .../map-with-many-layers.tmj | 44 ++---- .../map-with-many-layers.tmx | 4 +- .../TestData/Map/map-with-many-layers/poly.tj | 31 ++++ .../TestData/Map/map-with-many-layers/poly.tx | 9 ++ DotTiled/Serialization/Helpers.cs | 2 +- DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs | 148 ++++++++++++------ 8 files changed, 159 insertions(+), 88 deletions(-) create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tj create mode 100644 DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tx diff --git a/DotTiled.Tests/Assert/AssertObject.cs b/DotTiled.Tests/Assert/AssertObject.cs index bd303f9..6c586bb 100644 --- a/DotTiled.Tests/Assert/AssertObject.cs +++ b/DotTiled.Tests/Assert/AssertObject.cs @@ -17,6 +17,8 @@ public static partial class DotTiledAssert AssertEqual(expected.Template, actual.Template, nameof(Object.Template)); AssertProperties(expected.Properties, actual.Properties); + + Assert.True(expected.GetType() == actual.GetType(), $"Expected object type {expected.GetType()} but got {actual.GetType()}"); AssertObject((dynamic)expected, (dynamic)actual); } diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs index 8ef6ce5..2ef98d0 100644 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.cs @@ -92,7 +92,12 @@ public partial class TestData new Vector2(0, 0), new Vector2(104,20), new Vector2(35.6667f, 32.3333f) - ] + ], + Template = fileExt == "tmx" ? "poly.tx" : "poly.tj", + Properties = new Dictionary + { + ["templateprop"] = new StringProperty { Name = "templateprop", Value = "helo there" } + } }, new TileObject { diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj index 16561f4..9e9f669 100644 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmj @@ -11,15 +11,15 @@ "name":"Objects", "objects":[ { - "height":31.3333333333333, + "height":31.3333, "id":1, "name":"Object 1", "rotation":0, "type":"", "visible":true, - "width":31.3333333333333, - "x":25.6666666666667, - "y":28.6666666666667 + "width":31.3333, + "x":25.6667, + "y":28.6667 }, { "height":0, @@ -30,44 +30,26 @@ "type":"", "visible":true, "width":0, - "x":117.666666666667, - "y":48.6666666666667 + "x":117.667, + "y":48.6667 }, { "ellipse":true, - "height":34.6666666666667, + "height":34.6667, "id":4, "name":"Circle1", "rotation":0, "type":"", "visible":true, - "width":34.6666666666667, + "width":34.6667, "x":77, - "y":72.3333333333333 + "y":72.3333 }, { - "height":0, "id":5, - "name":"Poly", - "polygon":[ - { - "x":0, - "y":0 - }, - { - "x":104, - "y":20 - }, - { - "x":35.6666666666667, - "y":32.3333333333333 - }], - "rotation":0, - "type":"", - "visible":true, - "width":0, - "x":20.6666666666667, - "y":114.666666666667 + "template":"poly.tj", + "x":20.6667, + "y":114.667 }, { "gid":7, @@ -79,7 +61,7 @@ "visible":true, "width":64, "x":-35, - "y":110.333333333333 + "y":110.333 }], "opacity":1, "type":"objectgroup", diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx index 34cd91c..5888069 100644 --- a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/map-with-many-layers.tmx @@ -10,9 +10,7 @@ - - - + diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tj b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tj new file mode 100644 index 0000000..f23c7d9 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tj @@ -0,0 +1,31 @@ +{ "object": + { + "height":0, + "id":5, + "name":"Poly", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":104, + "y":20 + }, + { + "x":35.6667, + "y":32.3333 + }], + "properties":[ + { + "name":"templateprop", + "type":"string", + "value":"helo there" + }], + "rotation":0, + "type":"", + "visible":true, + "width":0 + }, + "type":"template" +} \ No newline at end of file diff --git a/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tx b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tx new file mode 100644 index 0000000..a0a2457 --- /dev/null +++ b/DotTiled.Tests/Serialization/TestData/Map/map-with-many-layers/poly.tx @@ -0,0 +1,9 @@ + + diff --git a/DotTiled/Serialization/Helpers.cs b/DotTiled/Serialization/Helpers.cs index 905cb9f..2e36124 100644 --- a/DotTiled/Serialization/Helpers.cs +++ b/DotTiled/Serialization/Helpers.cs @@ -72,7 +72,7 @@ internal static partial class Helpers }; } - internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary overrideProperties) + internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary? overrideProperties) { if (baseProperties is null) return overrideProperties ?? new Dictionary(); diff --git a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs index fa80805..4d70b91 100644 --- a/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs +++ b/DotTiled/Serialization/Tmx/Tmx.ObjectLayer.cs @@ -78,36 +78,21 @@ internal partial class Tmx { // 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 + Object? obj = null; if (template is not null) - { - var resolvedTemplate = externalTemplateResolver(template); - var templObj = resolvedTemplate.Object; + obj = externalTemplateResolver(template).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; - visibleDefault = templObj.Visible; - propertiesDefault = templObj.Properties; - } + uint? idDefault = obj?.ID ?? null; + string nameDefault = obj?.Name ?? ""; + string typeDefault = obj?.Type ?? ""; + float xDefault = obj?.X ?? 0f; + float yDefault = obj?.Y ?? 0f; + float widthDefault = obj?.Width ?? 0f; + float heightDefault = obj?.Height ?? 0f; + float rotationDefault = obj?.Rotation ?? 0f; + uint? gidDefault = obj is TileObject tileObj ? tileObj.GID : null; + bool visibleDefault = obj?.Visible ?? true; + Dictionary? propertiesDefault = obj?.Properties ?? null; var id = reader.GetOptionalAttributeParseable("id") ?? idDefault; var name = reader.GetOptionalAttribute("name") ?? nameDefault; @@ -121,46 +106,66 @@ internal partial class Tmx var visible = reader.GetOptionalAttributeParseable("visible") ?? visibleDefault; // Elements - Object? obj = null; + Object? foundObject = null; int propertiesCounter = 0; Dictionary? properties = propertiesDefault; reader.ProcessChildren("object", (r, elementName) => elementName switch { "properties" => () => Helpers.SetAtMostOnceUsingCounter(ref properties, Helpers.MergeProperties(properties, ReadProperties(r, customTypeDefinitions)), "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"), + "ellipse" => () => Helpers.SetAtMostOnce(ref foundObject, ReadEllipseObject(r), "Object marker"), + "point" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPointObject(r), "Object marker"), + "polygon" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolygonObject(r), "Object marker"), + "polyline" => () => Helpers.SetAtMostOnce(ref foundObject, ReadPolylineObject(r), "Object marker"), + "text" => () => Helpers.SetAtMostOnce(ref foundObject, ReadTextObject(r), "Object marker"), _ => throw new Exception($"Unknown object marker '{elementName}'") }); - if (gid is not null) + if (foundObject is null) { - obj = new TileObject { ID = id, GID = gid.Value }; - reader.Skip(); + if (gid is not null) + foundObject = new TileObject { ID = id, GID = gid.Value }; + else + foundObject = new RectangleObject { ID = id }; } + foundObject.ID = id; + foundObject.Name = name; + foundObject.Type = type; + foundObject.X = x; + foundObject.Y = y; + foundObject.Width = width; + foundObject.Height = height; + foundObject.Rotation = rotation; + foundObject.Visible = visible; + foundObject.Properties = properties; + foundObject.Template = template; + + return OverrideObject(obj, foundObject); + } + + internal static Object OverrideObject(Object? obj, Object foundObject) + { if (obj is null) + return foundObject; + + if (obj.GetType() != foundObject.GetType()) { - obj = new RectangleObject { ID = id }; - reader.Skip(); + obj.ID = foundObject.ID; + obj.Name = foundObject.Name; + obj.Type = foundObject.Type; + obj.X = foundObject.X; + obj.Y = foundObject.Y; + obj.Width = foundObject.Width; + obj.Height = foundObject.Height; + obj.Rotation = foundObject.Rotation; + obj.Visible = foundObject.Visible; + obj.Properties = Helpers.MergeProperties(obj.Properties, foundObject.Properties); + obj.Template = foundObject.Template; + return obj; } - obj.ID = id; - obj.Name = name; - obj.Type = type; - obj.X = x; - obj.Y = y; - obj.Width = width; - obj.Height = height; - obj.Rotation = rotation; - obj.Visible = visible; - obj.Template = template; - obj.Properties = properties; - - return obj; + return OverrideObject((dynamic)obj, (dynamic)foundObject); } internal static EllipseObject ReadEllipseObject(XmlReader reader) @@ -169,12 +174,16 @@ internal partial class Tmx return new EllipseObject { }; } + internal static EllipseObject OverrideObject(EllipseObject obj, EllipseObject foundObject) => obj; + internal static PointObject ReadPointObject(XmlReader reader) { reader.Skip(); return new PointObject { }; } + internal static PointObject OverrideObject(PointObject obj, PointObject foundObject) => obj; + internal static PolygonObject ReadPolygonObject(XmlReader reader) { // Attributes @@ -193,6 +202,12 @@ internal partial class Tmx return new PolygonObject { Points = points }; } + internal static PolygonObject OverrideObject(PolygonObject obj, PolygonObject foundObject) + { + obj.Points = foundObject.Points; + return obj; + } + internal static PolylineObject ReadPolylineObject(XmlReader reader) { // Attributes @@ -211,6 +226,12 @@ internal partial class Tmx return new PolylineObject { Points = points }; } + internal static PolylineObject OverrideObject(PolylineObject obj, PolylineObject foundObject) + { + obj.Points = foundObject.Points; + return obj; + } + internal static TextObject ReadTextObject(XmlReader reader) { // Attributes @@ -259,6 +280,29 @@ internal partial class Tmx }; } + internal static TextObject OverrideObject(TextObject obj, TextObject foundObject) + { + obj.FontFamily = foundObject.FontFamily; + obj.PixelSize = foundObject.PixelSize; + obj.Wrap = foundObject.Wrap; + obj.Color = foundObject.Color; + obj.Bold = foundObject.Bold; + obj.Italic = foundObject.Italic; + obj.Underline = foundObject.Underline; + obj.Strikeout = foundObject.Strikeout; + obj.Kerning = foundObject.Kerning; + obj.HorizontalAlignment = foundObject.HorizontalAlignment; + obj.VerticalAlignment = foundObject.VerticalAlignment; + obj.Text = foundObject.Text; + return obj; + } + + internal static TileObject OverrideObject(TileObject obj, TileObject foundObject) + { + obj.GID = foundObject.GID; + return obj; + } + internal static Template ReadTemplate( XmlReader reader, Func externalTilesetResolver, From 453d749098f8e1c15a673ca3335acfc224ad0530 Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Wed, 14 Aug 2024 21:07:50 +0200 Subject: [PATCH 38/39] Add feature coverage comparison to README --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a629121..0868a25 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ DotTiled is a simple and easy-to-use library for loading, saving, and managing [ 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) +- [Feature coverage comparison](#feature-coverage-comparison) - [Quickstart](#quickstart) - [Installing DotTiled](#installing-dottiled) @@ -27,25 +28,6 @@ Other similar libraries exist, and you may want to consider them for your projec > [!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 |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| Template files |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| Property custom types |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| Hierarchical layers (groups) |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| -| Infinite maps |✅/❌|✅/❌|✅/❌|✅/❌|✅/❌|✅/❌| - -
-
Benchmark details @@ -72,6 +54,24 @@ BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update) [MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline (with the DotTiled.MonoGame.Pipeline NuGet). However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. +# Feature coverage comparison + +Below is a comparison of the feature coverage of DotTiled and other similar libraries. This comparison is based on the features provided by the Tiled map editor and the support for those features in each library. The comparison is not exhaustive, and you may want to refer to the respective library's documentation or implementation for details. Due to some libraries not having obvious documentation or feature lists, some features may be incorrectly marked as not supported. If you find any inaccuracies, please let me know. + +| **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)| +|---------------------------------|:-:|:-:|:-:|:-:|:-:|:-:| +| XML format `.tmx` |✅ |⚠ïļ|⚠ïļ|⚠ïļ|⚠ïļ|⚠ïļ| +| JSON format `.tmj` |✅ |⚠ïļ|❌|❌|❌|❌| +| External tileset callback |✅ |✅|❌|✅|❌|❌| +| Object templates |✅ |❌|❌|❌|❌|❌| +| Custom types (properties) |✅ |❌|❌|❌|❌|❌| +| Hierarchical layers (groups) |✅ |❌|❌|✅|❌|✅| +| Infinite maps |✅ |❌|✅|✅|✅|❌| +| Wangsets |✅ |❌|⚠ïļ|⚠ïļ|❌|⚠ïļ| + +> [!NOTE] +> ✅ Full support. ⚠ïļ Partial support, see respective library for details about supported features. ❌ No support. + # Quickstart ### Installing DotTiled From 32de90a5667ff10272b1e2c48b686ba685c7957e Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Wed, 14 Aug 2024 22:17:14 +0200 Subject: [PATCH 39/39] Add more benchmarking information and fix paths in benchmarks --- DotTiled.Benchmark/Program.cs | 23 +++++++++++++++++++---- README.md | 22 ++++++++++++---------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/DotTiled.Benchmark/Program.cs b/DotTiled.Benchmark/Program.cs index b432b7e..2acbce2 100644 --- a/DotTiled.Benchmark/Program.cs +++ b/DotTiled.Benchmark/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using System.Xml; @@ -18,18 +19,24 @@ namespace MyBenchmarks [HideColumns(["StdDev", "Error", "RatioSD"])] public class MapLoading { - private string _tmxPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\TestData\Map\empty-map-csv.tmx"; + private string _tmxPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmx"; private string _tmxContents = ""; - private string _tmjPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\TestData\Map\empty-map-csv.tmj"; + private string _tmjPath = @"DotTiled.Tests/Serialization/TestData/Map/default-map/default-map.tmj"; private string _tmjContents = ""; public MapLoading() { - _tmxContents = System.IO.File.ReadAllText(_tmxPath); - _tmjContents = System.IO.File.ReadAllText(_tmjPath); + var basePath = Path.GetDirectoryName(WhereAmI())!; + var tmxPath = Path.Combine(basePath, $"../{_tmxPath}"); + var tmjPath = Path.Combine(basePath, $"../{_tmjPath}"); + + _tmxContents = System.IO.File.ReadAllText(tmxPath); + _tmjContents = System.IO.File.ReadAllText(tmjPath); } + static string WhereAmI([CallerFilePath] string callerFilePath = "") => callerFilePath; + [BenchmarkCategory("MapFromInMemoryTmxString")] [Benchmark(Baseline = true, Description = "DotTiled")] public DotTiled.Map LoadWithDotTiledFromInMemoryTmxString() @@ -56,6 +63,14 @@ namespace MyBenchmarks return TiledLib.Map.FromStream(memStream); } + [BenchmarkCategory("MapFromInMemoryTmjString")] + [Benchmark(Description = "TiledLib")] + public TiledLib.Map LoadWithTiledLibFromInMemoryTmjString() + { + using var memStream = new MemoryStream(Encoding.UTF8.GetBytes(_tmjContents)); + return TiledLib.Map.FromStream(memStream); + } + [BenchmarkCategory("MapFromInMemoryTmxString")] [Benchmark(Description = "TiledCSPlus")] public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromInMemoryTmxString() diff --git a/README.md b/README.md index 0868a25..27ee68c 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,17 @@ 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 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| 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`| +| Benchmark (time)* | 1.00 | 1.83 | 2.16 | - | - | - | +| Benchmark (memory)* | 1.00 | 1.43 | 2.03 | - | - | - | +| .NET Targets | `net8.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] > *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. +[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline (with the DotTiled.MonoGame.Pipeline NuGet). However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. +
Benchmark details @@ -44,16 +45,17 @@ BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update) ``` | Method | Categories | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | |------------ |------------------------- |---------:|------:|-------:|-------:|----------:|------------:| -| DotTiled | MapFromInMemoryTmjString | 4.292 Ξs | 1.00 | 0.4349 | - | 5.62 KB | 1.00 | +| DotTiled | MapFromInMemoryTmjString | 4.431 Ξs | 1.00 | 0.4349 | - | 5.58 KB | 1.00 | +| TiledLib | MapFromInMemoryTmjString | 6.369 Ξs | 1.44 | 0.7019 | 0.0153 | 9.01 KB | 1.61 | | | | | | | | | | -| DotTiled | MapFromInMemoryTmxString | 3.075 Ξs | 1.00 | 1.2817 | 0.0610 | 16.4 KB | 1.00 | -| TiledLib | MapFromInMemoryTmxString | 5.574 Ξs | 1.81 | 1.8005 | 0.0916 | 23.32 KB | 1.42 | -| TiledCSPlus | MapFromInMemoryTmxString | 6.546 Ξs | 2.13 | 2.5940 | 0.1831 | 33.16 KB | 2.02 | +| DotTiled | MapFromInMemoryTmxString | 3.125 Ξs | 1.00 | 1.2817 | 0.0610 | 16.36 KB | 1.00 | +| TiledLib | MapFromInMemoryTmxString | 5.709 Ξs | 1.83 | 1.8005 | 0.0916 | 23.32 KB | 1.43 | +| TiledCSPlus | MapFromInMemoryTmxString | 6.757 Ξs | 2.16 | 2.5940 | 0.1831 | 33.16 KB | 2.03 | + +It is important to note that the above benchmark results come from loading a very small map with a single tile layer as I had to find a common denominator between the libraries so that they all could load the same map. The results aim to be indicative of the performance of the libraries, but should be taken with a grain of salt. Only the actively maintained libraries are included in the benchmark results. TiledCSPlus does not support the `.tmj` format, so it was not included for that benchmark category.
-[MonoGame](https://www.monogame.net) users may also want to consider using [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended) for loading Tiled maps and tilesets. Like MonoGame.Extended, DotTiled also provides a way to properly import Tiled maps and tilesets with the MonoGame content pipeline (with the DotTiled.MonoGame.Pipeline NuGet). However, unlike MonoGame.Extended, DotTiled does *not* include any kind of rendering capabilities, and it is up to you as a developer to implement any kind of rendering for your maps when using DotTiled. The feature coverage by MonoGame.Extended is less than that of DotTiled, so you may want to consider using DotTiled if you need access to more Tiled features and flexibility. - # Feature coverage comparison Below is a comparison of the feature coverage of DotTiled and other similar libraries. This comparison is based on the features provided by the Tiled map editor and the support for those features in each library. The comparison is not exhaustive, and you may want to refer to the respective library's documentation or implementation for details. Due to some libraries not having obvious documentation or feature lists, some features may be incorrectly marked as not supported. If you find any inaccuracies, please let me know.