Google Summer of Code 2022 - The ENIGMA Team

Week 1: Importing Tiled .tsx files, the better way

Contributor: Kartik Shrivastava
Project: Add Tiled compatibility to/from Enigma
Guided by: bjorn and fundies

Highlights:
  1. Added initial support to import .tsx files #a723e88
  2. Updated .tsx importer to use protocol buffer reflections (the better way) #3e1e699
  3. Added support to load .tsx file in RadialGM IDE (minor contri) #45f4993



A side note

What you can learn from this as a casual reader?

Details

How does initial importer looks like🤔?

Tiled .tsx files uses xml format to organize its data. So we use pugi xml parsing library for this task. To traverse through all tsx xml nodes we use a handy method pugi::xml_document::traverse(...), which recursively calls following for_each method with each node passed as an argument.

Original version

In each iteration of above method we fill the proto fields with the correct xml node attribute value using carefully written if-else chain (the proto fields we are talking about here are refering to Background.proto). And why we are filling the proto fields? GYA, because Enigma's libEGM uses protocol buffers to store data internally (find more details in next section).

Now its clear that there is a lot of hardcoded node & attribute names in the above method. Which can be harder to maintain and needs to be updated whenever Tiled revises its .tsx format.
So can we do better?



đź’ˇProtocol buffer has a better way, reflections!

Protocol buffers are used to serialize structured data and offer loads of features such as language neutral serializing, automatic class generator, reflection mechanism to process data, etc. Enigma's libEGM which is built to read-write game projects internally uses protocol buffers to store data. So we are going to use reflection mechanism of protocol buffers to improve our .tsx importer.

The method for iterating over xml nodes of .tsx file will remain the same i.e. pugi::xml_document::traverse(...). Now the main problem is to establish the mapping between xml nodes and proto fields. And here comes another handy feature of protocol buffers to the rescue. It's called proto field extensions. Take a look at following snippet.

Original version

In the above code, id field of Background.proto uses two extensions viz. (gmx) = "GMX_DEPRECATED" and (tmx) = "firstgid", enclosed within pair of sqaure brackets[]. In second extension, text after tmx within square brackets"" represents xml node attribute value. We are using tmx for both .tsx and .tmx files.

Now to establish the mapping we will store xml node attribute names in the appropriate proto field as an extension value. Following code snippet sums up the basic working of reflection, code is edited and unnecessary details are stripped away.

Original version

As you can see for_each method passes the xml node and proto node pointer(right now it is not decided which proto message it belongs to) to AddResource(...) method. And AddResource(...) performs some processing and finds appropriate proto message which in this case is Background.proto and pases the result to PackRes(...) method. PackRes method gets the pointer to the reflection and starts iterating over all fields of Background.proto. In each iteration correct xml node attribute name is deduced using opts.GetExtension(...) call and a handy method from pugi fetches the reference to xml attribute with same extension name. Now all is left to update the proto field value using refl pointer.
You might argue that we still hardcoded attribute names, yes we did but they are much descriptive now as extensions and also we eliminated that complex if-else chain. So that's an improvement!

So, that's all for Week 1 updates, see you in next one. That will extend this reflection knowledge to handle much bigger and complicated .tmx files.