From cc57b172a89a067e5d881fcfe6c86066f3dabd6d Mon Sep 17 00:00:00 2001 From: Daniel Cronqvist Date: Sun, 11 Aug 2024 12:10:56 +0200 Subject: [PATCH] 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 |