Contributor: Kartik Shrivastava
Project: Add Tiled compatibility to/from Enigma
Guided by: bjorn and fundies
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 versionIn 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 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 versionIn 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 versionAs 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.