What’s Tiled?
Tiled is a popular open source map editor for 2D games. It supports several file export formats, often times even specialized ones for specific game engines. I recommend you check it out if you haven’t already. It’s a great tool for creating 2D maps for your games and then exporting them in a format like JSON or XML to be used in your game engine of choice.
The problem
In my never-ending game development journey, I’ve used Tiled for several projects. I’ve therefore also used many of the existing libraries for parsing Tiled maps in my .NET projects. However, a lot of these libraries seem to lack in the features that they support, or they are outdated and no longer maintained, or lack in documentation making them difficult to use.
I wanted to use many of the newer features in Tiled, e.g. custom types. Unfortunately, none of the libraries I found supported the features I wanted. So I decided to create my own library, and made it a challenge for myself to make it as fast and memory efficient as possible, using BenchmarkDotNet to measure and compare performance.
Note: This article is not about the library I created itself, but rather about the process of creating it. I will not go into detail about the library’s features, but rather focus on the process of creating a fast and memory efficient Tiled parsing library in C#. If you just want a link to the library -> DotTiled.
Reflection-based parsers
Many of the existing libraries use reflection to parse the Tiled maps. This is a very flexible way of parsing the maps, but it comes with a relatively heavy performance cost. Here’s what a reflection-based parser might look like for a Tiled map:
| |
A class like this can then be used with an XmlSerializer to parse the Tiled map from an XML string or file. This is a very simple example and does not represent an entire Tiled map, but you can imagine how this could be extended to support entire maps and all their properties. You could apply similar attributes to classes like these to also support the different types of map formats that Tiled support.
What other options besides reflection are there?
Reflection is a powerful tool, and is often times a very appropriate one when it comes to parsing data and mapping them to corresponding classes. However, in this case, I wanted to make a library that was as fast and memory efficient as possible. Reflection is not the best tool for that job. Instead, I decided to use a more manual approach, where I would parse the XML or JSON files myself, without reflection.
Manual parsing
C# has a useful XmlReader class that allows you to read XML strings in a forward-only manner. This is a very efficient way of parsing XML, as you only read the parts of the XML that you need, and you don’t have to load the entire XML file into memory.
For JSON, there is a similar forward-only reader called Utf8JsonReader. While it is fast and efficient, I decided against using it for this project as it is a bit more cumbersome than the relatively lightweight alternative: JsonDocument. JsonDocument is a bit heavier on memory, but it is easier to work with and is still relatively fast. And as benchmarks showed later, it was quite a lot faster than using reflection.
Here’s how I parse a Tiled map using XmlReader:
| |
Pretty verbose, right? In fact, it would have been much more verbose if it wasn’t for the amount of extension methods I’ve created for XmlReader. The GetRequiredAttribute and GetOptionalAttribute are examples of such extension methods that make it easier for me to semantically argue about the attributes I expect to be there.
However, even though it is quite verbose: it’s readable and fast. The ReadTileLayer, ReadObjectLayer, ReadImageLayer, and ReadGroup all look very similar to ReadMap and read their corresponding expected attributes and elements.
For JSON, it is extremely similar; I made identical extension methods to JsonElement to be able to retrieve values in a required or optional manner.
Benchmarking my library DotTiled against other libraries
After all this effort, it would be a shame if it turned out that it was only marginally faster than the reflection-based parsing. However, it turned out to be quite a lot faster.
Below is the output from BenchmarkDotNet when comparing DotTiled against two other similar libraries that use reflection-based parsing.
| |
From the above table, it’s clear that DotTiled is about 45% faster at parsing a small and simple map (in the XML format) than the runner-up TiledLib, and more than 50% faster than TiledCSPlus.
The JSON format is not as drastic, likely due to the usage of JsonElement, which has a larger overhead than the forward-only reader Utf8JsonReader. Still, a close to 30% increase in speed for a simple map is a great result.
Note:
TiledCSPlusdoes not have support for the JSON map format, which is why it isn’t included in the benchmarks for JSON maps.
When it comes to memory, the JSON parsing outperformed TiledLib by almost 40%, which is very impressive! The XML parsing also managed to outperform TiledLib by around 30%.
Conclusion
So what’s the verdict? Well, DotTiled performed about 40% better than TiledLib in speed on average, and about 35% better than TiledLib in memory usage on average.
It seems like my efforts on manual parsing really did pay off in the end, and the result is a fast and memory efficient Tiled map parsing library for anyone to use. The maintainer of Tiled also mentions the library in their documentation.