rapidyaml has difficulties with Enums.
You can do
node << (int)myEnumVar;but you can't do:
node >> (int)myEnumVar;Or maybe I'm stupid and I just don't know how lol... only thing I figured out is:
int temp;
node >> temp;
myEnumVar = (MyEnum)temp;So for now I'm just specifying a custom read overload:
bool read(ryml::ConstNodeRef const& n, MyEnum* val){ int temp; n >> temp; *val = (MyEnum)temp; return true; }and then "node >> myEnumVar"; works fine
rapidyaml already has a read function defined for all integral types, also for vector and map, so parsing most types is easy. But for custom types like enums, you gotta specify your own read(), and maybe write overload.
>next_sibling() is nonstarter
Sorry, this was one of the exceptional places where I used next_sibling(), I don't normally use it. Forget you saw that! I'll change it to use keys instead.
>>Many save objects save optional values only if they do not have default values.
Yeah, don't worry about it. I'm careful, which is why converting to rapidyaml is taking me so long. I've already been working on it for two weeks!
BattleUnit::load()
Before:
void BattleUnit::load(const YAML::Node &node, const Mod *mod, const ScriptGlobal *shared)
{
_id = node["id"].as<int>(_id);
_faction = (UnitFaction)node["faction"].as<int>(_faction);
_status = (UnitStatus)node["status"].as<int>(_status);
_wantsToSurrender = node["wantsToSurrender"].as<bool>(_wantsToSurrender);
_isSurrendering = node["isSurrendering"].as<bool>(_isSurrendering);
_pos = node["position"].as<Position>(_pos);
_direction = _toDirection = node["direction"].as<int>(_direction);
_directionTurret = _toDirectionTurret = node["directionTurret"].as<int>(_directionTurret);
_tu = node["tu"].as<int>(_tu);
_health = node["health"].as<int>(_health);
_mana = node["mana"].as<int>(_mana);
...
After:
void BattleUnit::load(const ryml::ConstNodeRef &node, const Mod *mod, const ScriptGlobal *shared)
{
const ryml::Tree tree = *node.tree();
std::unordered_map<ryml::csubstr, ryml::id_type> index(node.num_children()); //build and use an index to avoid [] operator's O(n) complexity
for (const ryml::ConstNodeRef& childNode : node.children())
index[childNode.key()] = childNode.id();
tree.cref(index["id"]) >> _id;
tree.cref(index["faction"]) >> _faction;
tree.cref(index["status"]) >> _status;
if (index.count("wantsToSurrender"))
tree.cref(index["wantsToSurrender"]) >> _wantsToSurrender;
if (index.count("isSurrendering"))
tree.cref(index["isSurrendering"]) >> _isSurrendering;
tree.cref(index["position"]) >> _pos;
tree.cref(index["direction"]) >> _direction;
_toDirection = _direction;
tree.cref(index["directionTurret"]) >> _directionTurret;
_toDirectionTurret = _directionTurret;
tree.cref(index["tu"]) >> _tu;
tree.cref(index["health"]) >> _health;
tree.cref(index["mana"]) >> _mana;
...
I checked out BattleUnit::save() to find out which keys are only saved if not default value, so I know which keys I have to be careful about. It's a lot of work...
>Even if current code work fine, you need consider different versions (past and future).
I'm being as careful as I can be~
The real test will be after I'm finished. I'll make sure that all the mods and saved games serialize and deserialize correctly, so they produce identical files.
>If we make loading too fast then changing format become hard as is in binary files.
Haha, I know what you mean. While I am trying to be optimize things here and there, I'm not going to sacrifice readability (ignore that next_sibling case). The code won't look worse than it is after I'm done, I promise!
>If you could replace all with `load(node, "name", value);` I would be for it.
I would be for it also, but, right now there is no abstraction layer. Creating an abstraction layer would be a lot of work, but then it would be even more work to convert all the code to use the abstraction layer. Basically, converting to abstraction layer is more work than direct conversion to rapidyaml.
>Best case could be your pull request:
https://github.com/MeridianOXC/OpenXcom/pull/132I've closed that pull request because it won't matter after I'm done with this conversion.
>that use most optimal code
Even the most optimal code with yaml-cpp is very slow, so any possible improvements are marginal at best. If you'd seen the internal code of rapidyaml, you'd know what I'm talking about. For instance, ryml::to_chars() is faster than even std::to_chars()
>I think probably best way I would see to start all this is add new layer of indirection.
I agree with you, that conversion to an common API would be the best but, that would be a lot of extra work.
Also, I'm already over half way done! In the beginning I was only trying out stuff to see how compatible the libraries were, but I got pretty absorbed into the coding. So the best I can do is finish a direct conversion before I lose the will to code.
>Btw there are other yaml libs, could have less performance as rapidyaml but could have closer semantic to yaml and easier to swich
I couldn't find any. Either they were similar to rapidyaml, or they had bad performance, or no performance data. It seems like writing a yaml library is something people did as a high school project. If the library works, you got a good grade in school!
I think lot better would be wrapper `OpenXcom::Yaml`.
In case of rapid it could store first, last and current nodes. each time you check for `"X"` it check if its current node, if not it loop `next_sibling()` to next one and check if its same, this loop until it reach end, when its reach end, it start from beginning unit reach previous current node.
This reduce cost to one `O(n)` and in "best" case it could have cost `O(1)` if loading order is same as saving.
For rulesets we could make "random" version that allocate flat buffer that sort all nodes based on it.
That's a pretty nifty algorithm. But what if we can guarantee O(1)? In the above code example I loop through children to build an index, and then all use it to get O(1). This is what rapidyaml author wrote we should do when we want fast access. I only build index for large objects tho due to overhead. In general rapidyaml has a lot of options to speed up parsing, reusing parsers, reusing trees and buffers... stuff that is specific to rapidyaml which can't be used with an abstraction layer.