From b7fdca0fe65278af11f62941faba8a2c836244b3 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 24 Mar 2023 12:31:37 +0100 Subject: [PATCH] Use serialized ESM::RefId for Lua records --- apps/benchmarks/esm/benchrefid.cpp | 121 +++++++++++++++++++++++ apps/openmw/mwlua/cellbindings.cpp | 4 +- apps/openmw/mwlua/objectbindings.cpp | 2 +- apps/openmw/mwlua/types/activator.cpp | 4 +- apps/openmw/mwlua/types/apparatus.cpp | 4 +- apps/openmw/mwlua/types/armor.cpp | 8 +- apps/openmw/mwlua/types/book.cpp | 6 +- apps/openmw/mwlua/types/clothing.cpp | 6 +- apps/openmw/mwlua/types/container.cpp | 4 +- apps/openmw/mwlua/types/creature.cpp | 4 +- apps/openmw/mwlua/types/door.cpp | 8 +- apps/openmw/mwlua/types/ingredient.cpp | 6 +- apps/openmw/mwlua/types/light.cpp | 6 +- apps/openmw/mwlua/types/lockpick.cpp | 4 +- apps/openmw/mwlua/types/misc.cpp | 4 +- apps/openmw/mwlua/types/npc.cpp | 10 +- apps/openmw/mwlua/types/potion.cpp | 6 +- apps/openmw/mwlua/types/probe.cpp | 4 +- apps/openmw/mwlua/types/repair.cpp | 6 +- apps/openmw/mwlua/types/static.cpp | 2 +- apps/openmw/mwlua/types/types.cpp | 1 - apps/openmw/mwlua/types/types.hpp | 3 +- apps/openmw/mwlua/types/weapon.cpp | 8 +- apps/openmw_test_suite/esm/testrefid.cpp | 61 ++++++++++-- components/CMakeLists.txt | 1 + components/esm/formidrefid.cpp | 16 +-- components/esm/generatedrefid.cpp | 16 +-- components/esm/indexrefid.cpp | 23 +++-- components/esm/refid.cpp | 47 +++++++++ components/esm/refid.hpp | 8 +- components/esm/serializerefid.hpp | 64 ++++++++++++ components/esm/stringrefid.cpp | 4 +- 32 files changed, 383 insertions(+), 88 deletions(-) create mode 100644 components/esm/serializerefid.hpp diff --git a/apps/benchmarks/esm/benchrefid.cpp b/apps/benchmarks/esm/benchrefid.cpp index 6b76740602..c2cd41e093 100644 --- a/apps/benchmarks/esm/benchrefid.cpp +++ b/apps/benchmarks/esm/benchrefid.cpp @@ -48,6 +48,40 @@ namespace return generateSerializedRefIds(generateStringRefIds(size, random), serialize); } + template + std::vector generateIndexRefIds(Random& random) + { + std::vector result; + result.reserve(refIdsCount); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::generate_n(std::back_inserter(result), refIdsCount, + [&] { return ESM::IndexRefId(ESM::REC_ARMO, distribution(random)); }); + return result; + } + + template + std::vector generateSerializedIndexRefIds(Random& random, Serialize&& serialize) + { + return generateSerializedRefIds(generateIndexRefIds(random), serialize); + } + + template + std::vector generateGeneratedRefIds(Random& random) + { + std::vector result; + result.reserve(refIdsCount); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::generate_n( + std::back_inserter(result), refIdsCount, [&] { return ESM::GeneratedRefId(distribution(random)); }); + return result; + } + + template + std::vector generateSerializedGeneratedRefIds(Random& random, Serialize&& serialize) + { + return generateSerializedRefIds(generateGeneratedRefIds(random), serialize); + } + void serializeRefId(benchmark::State& state) { std::minstd_rand random; @@ -74,9 +108,96 @@ namespace i = 0; } } + + void serializeTextStringRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateStringRefIds(state.range(0), random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextStringRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedStringRefIds(state.range(0), random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } + + void serializeTextGeneratedRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateGeneratedRefIds(random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextGeneratedRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedGeneratedRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } + + void serializeTextIndexRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector refIds = generateIndexRefIds(random); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(refIds[i].serializeText()); + if (++i >= refIds.size()) + i = 0; + } + } + + void deserializeTextIndexRefId(benchmark::State& state) + { + std::minstd_rand random; + std::vector serializedRefIds + = generateSerializedIndexRefIds(random, [](ESM::RefId v) { return v.serializeText(); }); + std::size_t i = 0; + for (auto _ : state) + { + benchmark::DoNotOptimize(ESM::RefId::deserializeText(serializedRefIds[i])); + if (++i >= serializedRefIds.size()) + i = 0; + } + } } BENCHMARK(serializeRefId)->RangeMultiplier(4)->Range(8, 64); BENCHMARK(deserializeRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(serializeTextStringRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(deserializeTextStringRefId)->RangeMultiplier(4)->Range(8, 64); +BENCHMARK(serializeTextGeneratedRefId); +BENCHMARK(deserializeTextGeneratedRefId); +BENCHMARK(serializeTextIndexRefId); +BENCHMARK(deserializeTextIndexRefId); BENCHMARK_MAIN(); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index dc50a79eef..f144336fa0 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -40,8 +40,8 @@ namespace MWLua }; cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getNameId(); }); - cellT["region"] - = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getRegion().getRefIdString(); }); + cellT["region"] = sol::readonly_property( + [](const CellT& c) -> std::string { return c.mStore->getCell()->getRegion().serializeText(); }); cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); }); cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); }); cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); }); diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 5a7791c635..127d9b98e3 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -167,7 +167,7 @@ namespace MWLua { objectT["isValid"] = [](const ObjectT& o) { return !o.ptrOrNull().isEmpty(); }; objectT["recordId"] = sol::readonly_property( - [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().toDebugString(); }); + [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { const MWWorld::Ptr& ptr = o.ptr(); if (ptr.isInCell()) diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index 936d438361..88cf4d0f29 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -29,12 +29,12 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Activator& rec) { return "ESM3_Activator[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Activator& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Activator& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Activator& rec) -> std::string { return rec.mScript.serializeText(); }); } } diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp index d4be3776bf..0747d97d4c 100644 --- a/apps/openmw/mwlua/types/apparatus.cpp +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -36,13 +36,13 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Apparatus& rec) { return "ESM3_Apparatus[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Apparatus& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Apparatus& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index e112139ba7..7c5b477e10 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -43,7 +43,7 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Armor& rec) -> std::string { return "ESM3_Armor[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); @@ -51,10 +51,10 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["enchant"] = sol::readonly_property( - [](const ESM::Armor& rec) -> std::string { return rec.mEnchant.getRefIdString(); }); + record["enchant"] + = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mEnchant.serializeText(); }); record["mwscript"] - = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mScript.getRefIdString(); }); + = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mValue; }); record["type"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mType; }); diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index defc70d50c..02602bd6a7 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -38,19 +38,19 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Book& rec) { return "ESM3_Book[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] - = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.getRefIdString(); }); + = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; }); record["enchant"] - = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mEnchant.getRefIdString(); }); + = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mEnchant.serializeText(); }); record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; }); record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; }); diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index c5169440cb..35634d5fc1 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -42,7 +42,7 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Clothing& rec) -> std::string { return "ESM3_Clothing[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); @@ -51,9 +51,9 @@ namespace MWLua return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["enchant"] = sol::readonly_property( - [](const ESM::Clothing& rec) -> std::string { return rec.mEnchant.getRefIdString(); }); + [](const ESM::Clothing& rec) -> std::string { return rec.mEnchant.serializeText(); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Clothing& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Clothing& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mValue; }); record["type"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mType; }); diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index 76616eef54..cd6d2e3d24 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -55,13 +55,13 @@ namespace MWLua return "ESM3_Container[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Container& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Container& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Container& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; }); } } diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 3691b7912d..ecba2b7eba 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -33,8 +33,8 @@ namespace MWLua return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Creature& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Creature& rec) -> std::string { return rec.mScript.serializeText(); }); record["baseCreature"] = sol::readonly_property( - [](const ESM::Creature& rec) -> std::string { return rec.mOriginal.getRefIdString(); }); + [](const ESM::Creature& rec) -> std::string { return rec.mOriginal.serializeText(); }); } } diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index cf2880886e..9473ddf565 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -50,17 +50,17 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Door& rec) -> std::string { return "ESM3_Door[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] - = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.getRefIdString(); }); + = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.serializeText(); }); record["openSound"] = sol::readonly_property( - [](const ESM::Door& rec) -> std::string { return rec.mOpenSound.getRefIdString(); }); + [](const ESM::Door& rec) -> std::string { return rec.mOpenSound.serializeText(); }); record["closeSound"] = sol::readonly_property( - [](const ESM::Door& rec) -> std::string { return rec.mCloseSound.getRefIdString(); }); + [](const ESM::Door& rec) -> std::string { return rec.mCloseSound.serializeText(); }); } } diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index 8534526922..ed66ec9a9b 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -29,14 +29,14 @@ namespace MWLua sol::usertype record = context.mLua->sol().new_usertype(("ESM3_Ingredient")); record[sol::meta_function::to_string] = [](const ESM::Ingredient& rec) { return "ESM3_Ingredient[" + rec.mId.toDebugString() + "]"; }; - record["id"] = sol::readonly_property( - [](const ESM::Ingredient& rec) -> std::string { return rec.mId.getRefIdString(); }); + record["id"] + = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Ingredient& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Ingredient& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index 512eda3e33..bc9630289b 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -29,7 +29,7 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Light& rec) -> std::string { return "ESM3_Light[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); @@ -38,9 +38,9 @@ namespace MWLua return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["sound"] - = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.getRefIdString(); }); + = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.serializeText(); }); record["mwscript"] - = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mScript.getRefIdString(); }); + = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Light& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mValue; }); record["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; }); diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp index 133f228bb5..c5d9c6983d 100644 --- a/apps/openmw/mwlua/types/lockpick.cpp +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -29,13 +29,13 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Lockpick& rec) { return "ESM3_Lockpick[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Lockpick& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Lockpick& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index e25bf85b36..7b0b5934ec 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -30,13 +30,13 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Miscellaneous& rec) { return "ESM3_Miscellaneous[" + rec.mId.toDebugString() + "]"; }; record["id"] = sol::readonly_property( - [](const ESM::Miscellaneous& rec) -> std::string { return rec.mId.getRefIdString(); }); + [](const ESM::Miscellaneous& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] = sol::readonly_property( - [](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 0691aed27d..71d611f99c 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -32,15 +32,15 @@ namespace MWLua = [](const ESM::NPC& rec) { return "ESM3_NPC[" + rec.mId.toDebugString() + "]"; }; record["name"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mName; }); record["race"] - = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.getRefIdString(); }); + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.serializeText(); }); record["class"] - = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.getRefIdString(); }); + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.serializeText(); }); record["mwscript"] - = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript.getRefIdString(); }); + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript.serializeText(); }); record["hair"] - = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.getRefIdString(); }); + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); }); record["head"] - = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.getRefIdString(); }); + = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 43bb29308d..36a6c523d9 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -28,7 +28,7 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Potion& rec) { return "ESM3_Potion[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); @@ -36,8 +36,8 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Potion& rec) -> std::string { return rec.mScript.getRefIdString(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Potion& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Potion& rec) -> int { return rec.mData.mValue; }); } diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp index f1d72c6bcb..e042e4a0ab 100644 --- a/apps/openmw/mwlua/types/probe.cpp +++ b/apps/openmw/mwlua/types/probe.cpp @@ -29,13 +29,13 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Probe& rec) { return "ESM3_Probe[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); record["mwscript"] - = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.getRefIdString(); }); + = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp index a1c04eca5a..f38b7e2992 100644 --- a/apps/openmw/mwlua/types/repair.cpp +++ b/apps/openmw/mwlua/types/repair.cpp @@ -29,13 +29,13 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Repair& rec) { return "ESM3_Repair[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Repair& rec) -> std::string { return rec.mScript.getRefIdString(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/static.cpp b/apps/openmw/mwlua/types/static.cpp index b69735e896..ad7ee6b640 100644 --- a/apps/openmw/mwlua/types/static.cpp +++ b/apps/openmw/mwlua/types/static.cpp @@ -29,7 +29,7 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Static& rec) -> std::string { return "ESM3_Static[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Static& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Static& rec) -> std::string { return rec.mId.serializeText(); }); record["model"] = sol::readonly_property([vfs](const ESM::Static& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index 75cc072ce2..8b5fdba713 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -58,7 +58,6 @@ namespace MWLua { ESM::REC_PROB, ObjectTypeName::Probe }, { ESM::REC_REPA, ObjectTypeName::Repair }, }; - } unsigned int getLiveCellRefType(const MWWorld::LiveCellRefBase* ref) diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index 8716c73eee..498c5baa28 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -57,8 +57,7 @@ namespace MWLua const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, - [&store]( - const std::string& recordId) -> const T* { return store.find(ESM::RefId::stringRefId(recordId)); }); + [&store](std::string_view id) -> const T* { return store.find(ESM::RefId::deserializeText(id)); }); } } diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index de8ec1d392..b333f4ef57 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -46,7 +46,7 @@ namespace MWLua record[sol::meta_function::to_string] = [](const ESM::Weapon& rec) -> std::string { return "ESM3_Weapon[" + rec.mId.toDebugString() + "]"; }; record["id"] - = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId.getRefIdString(); }); + = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); @@ -55,9 +55,9 @@ namespace MWLua return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["enchant"] = sol::readonly_property( - [](const ESM::Weapon& rec) -> std::string { return rec.mEnchant.getRefIdString(); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Weapon& rec) -> std::string { return rec.mScript.getRefIdString(); }); + [](const ESM::Weapon& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["mwscript"] + = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript.serializeText(); }); record["isMagical"] = sol::readonly_property( [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; }); record["isSilver"] = sol::readonly_property( diff --git a/apps/openmw_test_suite/esm/testrefid.cpp b/apps/openmw_test_suite/esm/testrefid.cpp index 48262b3640..51dad424d0 100644 --- a/apps/openmw_test_suite/esm/testrefid.cpp +++ b/apps/openmw_test_suite/esm/testrefid.cpp @@ -211,9 +211,9 @@ namespace ESM { RefId(), std::string() }, { RefId::stringRefId("foo"), "foo" }, { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), { 'a', 0, -1, '\n', '\t' } }, - { RefId::formIdRefId(42), "42" }, - { RefId::generated(42), "42" }, - { RefId::index(REC_ARMO, 42), "ARMO, 42" }, + { RefId::formIdRefId(42), "0x2a" }, + { RefId::generated(42), "0x2a" }, + { RefId::index(REC_ARMO, 42), "ARMO:0x2a" }, }; INSTANTIATE_TEST_SUITE_P(ESMRefIdToString, ESMRefIdToStringTest, ValuesIn(toStringParams)); @@ -240,15 +240,51 @@ namespace ESM const std::vector> toDebugStringParams = { { RefId(), "Empty{}" }, - { RefId::stringRefId("foo"), "String{foo}" }, - { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), "String{a\\x0\\xFF\\xA\\x9}" }, - { RefId::formIdRefId(42), "FormId{42}" }, - { RefId::generated(42), "Generated{42}" }, - { RefId::index(REC_ARMO, 42), "Index{ARMO, 42}" }, + { RefId::stringRefId("foo"), "\"foo\"" }, + { RefId::stringRefId("BAR"), "\"BAR\"" }, + { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), "\"a\\x0\\xFF\\xA\\x9\"" }, + { RefId::formIdRefId(42), "FormId:0x2a" }, + { RefId::generated(42), "Generated:0x2a" }, + { RefId::index(REC_ARMO, 42), "Index:ARMO:0x2a" }, }; INSTANTIATE_TEST_SUITE_P(ESMRefIdToDebugString, ESMRefIdToDebugStringTest, ValuesIn(toDebugStringParams)); + struct ESMRefIdTextTest : TestWithParam> + { + }; + + TEST_P(ESMRefIdTextTest, serializeTextShouldReturnString) + { + EXPECT_EQ(GetParam().first.serializeText(), GetParam().second); + } + + TEST_P(ESMRefIdTextTest, deserializeTextShouldReturnRefId) + { + EXPECT_EQ(RefId::deserializeText(GetParam().second), GetParam().first); + } + + const std::vector> serializedRefIds = { + { RefId(), "" }, + { RefId::stringRefId("foo"), "foo" }, + { RefId::stringRefId("BAR"), "bar" }, + { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), { 'a', 0, -1, '\n', '\t' } }, + { RefId::formIdRefId(0), "FormId:0x0" }, + { RefId::formIdRefId(1), "FormId:0x1" }, + { RefId::formIdRefId(0x1f), "FormId:0x1f" }, + { RefId::formIdRefId(std::numeric_limits::max()), "FormId:0xffffffff" }, + { RefId::generated(0), "Generated:0x0" }, + { RefId::generated(1), "Generated:0x1" }, + { RefId::generated(0x1f), "Generated:0x1f" }, + { RefId::generated(std::numeric_limits::max()), "Generated:0xffffffffffffffff" }, + { RefId::index(REC_INGR, 0), "Index:INGR:0x0" }, + { RefId::index(REC_INGR, 1), "Index:INGR:0x1" }, + { RefId::index(REC_INGR, 0x1f), "Index:INGR:0x1f" }, + { RefId::index(REC_INGR, std::numeric_limits::max()), "Index:INGR:0xffffffff" }, + }; + + INSTANTIATE_TEST_SUITE_P(ESMRefIdText, ESMRefIdTextTest, ValuesIn(serializedRefIds)); + template RefId generateRefId(); @@ -295,7 +331,14 @@ namespace ESM EXPECT_EQ(RefId::deserialize(refId.serialize()), refId); } - REGISTER_TYPED_TEST_SUITE_P(ESMRefIdSerializeDeserializeTest, serializeThenDeserializeShouldProduceSameValue); + TYPED_TEST_P(ESMRefIdSerializeDeserializeTest, serializeTextThenDeserializeTextShouldProduceSameValue) + { + const RefId refId = generateRefId(); + EXPECT_EQ(RefId::deserializeText(refId.serializeText()), refId); + } + + REGISTER_TYPED_TEST_SUITE_P(ESMRefIdSerializeDeserializeTest, serializeThenDeserializeShouldProduceSameValue, + serializeTextThenDeserializeTextShouldProduceSameValue); using RefIdTypeParams = Types; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f62274d0e9..c64c7fed65 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -90,6 +90,7 @@ add_component_dir(esm attr common defs esmcommon records util luascripts format stringrefid generatedrefid indexrefid + serializerefid ) add_component_dir(fx pass technique lexer widgets stateupdater) diff --git a/components/esm/formidrefid.cpp b/components/esm/formidrefid.cpp index de430698e2..f4635f1a87 100644 --- a/components/esm/formidrefid.cpp +++ b/components/esm/formidrefid.cpp @@ -1,24 +1,28 @@ #include "formidrefid.hpp" +#include "serializerefid.hpp" + #include -#include namespace ESM { std::string FormIdRefId::toString() const { - return std::to_string(mValue); + std::string result; + result.resize(getIntegralSize(mValue) + 2, '\0'); + serializeIntegral(mValue, 0, result); + return result; } std::string FormIdRefId::toDebugString() const { - std::ostringstream stream; - stream << *this; - return stream.str(); + std::string result; + serializeRefIdValue(mValue, formIdRefIdPrefix, result); + return result; } std::ostream& operator<<(std::ostream& stream, FormIdRefId value) { - return stream << "FormId{" << value.mValue << '}'; + return stream << value.toDebugString(); } } diff --git a/components/esm/generatedrefid.cpp b/components/esm/generatedrefid.cpp index 434e04bc5f..03d9732107 100644 --- a/components/esm/generatedrefid.cpp +++ b/components/esm/generatedrefid.cpp @@ -1,24 +1,28 @@ #include "generatedrefid.hpp" +#include "serializerefid.hpp" + #include -#include namespace ESM { std::string GeneratedRefId::toString() const { - return std::to_string(mValue); + std::string result; + result.resize(getIntegralSize(mValue) + 2, '\0'); + serializeIntegral(mValue, 0, result); + return result; } std::string GeneratedRefId::toDebugString() const { - std::ostringstream stream; - stream << *this; - return stream.str(); + std::string result; + serializeRefIdValue(mValue, generatedRefIdPrefix, result); + return result; } std::ostream& operator<<(std::ostream& stream, GeneratedRefId value) { - return stream << "Generated{" << value.mValue << '}'; + return stream << value.toDebugString(); } } diff --git a/components/esm/indexrefid.cpp b/components/esm/indexrefid.cpp index 4a71b64662..ee99f10e68 100644 --- a/components/esm/indexrefid.cpp +++ b/components/esm/indexrefid.cpp @@ -1,26 +1,33 @@ #include "indexrefid.hpp" -#include -#include +#include "serializerefid.hpp" -#include "esmcommon.hpp" +#include namespace ESM { std::string IndexRefId::toString() const { - return ESM::NAME(mRecordType).toString() + ", " + std::to_string(mValue); + std::string result; + result.resize(sizeof(mRecordType) + getIntegralSize(mValue) + 3, '\0'); + std::memcpy(result.data(), &mRecordType, sizeof(mRecordType)); + result[sizeof(mRecordType)] = ':'; + serializeIntegral(mValue, sizeof(mRecordType) + 1, result); + return result; } std::string IndexRefId::toDebugString() const { - std::ostringstream stream; - stream << *this; - return stream.str(); + std::string result; + serializeRefIdPrefix(sizeof(mRecordType) + getIntegralSize(mValue) + 1, indexRefIdPrefix, result); + std::memcpy(result.data() + indexRefIdPrefix.size(), &mRecordType, sizeof(mRecordType)); + result[indexRefIdPrefix.size() + sizeof(mRecordType)] = ':'; + serializeIntegral(mValue, indexRefIdPrefix.size() + sizeof(mRecordType) + 1, result); + return result; } std::ostream& operator<<(std::ostream& stream, IndexRefId value) { - return stream << "Index{" << ESM::NAME(value.mRecordType).toStringView() << ", " << value.mValue << '}'; + return stream << value.toDebugString(); } } diff --git a/components/esm/refid.cpp b/components/esm/refid.cpp index 8d5cdfd5ab..d04ad4a845 100644 --- a/components/esm/refid.cpp +++ b/components/esm/refid.cpp @@ -1,8 +1,15 @@ #include "refid.hpp" +#include "serializerefid.hpp" + +#include "components/misc/strings/lower.hpp" + +#include #include #include #include +#include +#include namespace ESM { @@ -102,6 +109,19 @@ namespace ESM return false; } }; + + struct SerializeText + { + std::string operator()(ESM::EmptyRefId /*v*/) const { return std::string(); } + + std::string operator()(ESM::StringRefId v) const { return Misc::StringUtils::lowerCase(v.getValue()); } + + template + std::string operator()(const T& v) const + { + return v.toDebugString(); + } + }; } const RefId RefId::sEmpty = {}; @@ -193,4 +213,31 @@ namespace ESM std::memcpy(&result.mValue, value.data(), sizeof(result.mValue)); return result; } + + std::string RefId::serializeText() const + { + return std::visit(SerializeText{}, mValue); + } + + ESM::RefId RefId::deserializeText(std::string_view value) + { + if (value.empty()) + return ESM::RefId(); + + if (value.starts_with(formIdRefIdPrefix)) + return ESM::RefId::formIdRefId(deserializeIntegral(formIdRefIdPrefix.size(), value)); + + if (value.starts_with(generatedRefIdPrefix)) + return ESM::RefId::generated(deserializeIntegral(generatedRefIdPrefix.size(), value)); + + if (value.starts_with(indexRefIdPrefix)) + { + ESM::RecNameInts recordType{}; + std::memcpy(&recordType, value.data() + indexRefIdPrefix.size(), sizeof(recordType)); + return ESM::RefId::index(recordType, + deserializeIntegral(indexRefIdPrefix.size() + sizeof(recordType) + 1, value)); + } + + return ESM::RefId::stringRefId(value); + } } diff --git a/components/esm/refid.hpp b/components/esm/refid.hpp index dc90be5760..4a24839061 100644 --- a/components/esm/refid.hpp +++ b/components/esm/refid.hpp @@ -48,9 +48,12 @@ namespace ESM public: const static RefId sEmpty; - // Constructs RefId from a string containing byte by byte copy of RefId::mValue. + // Constructs RefId from a serialized string containing byte by byte copy of RefId::mValue. static ESM::RefId deserialize(std::string_view value); + // Constructs RefId from a serialized text. + static ESM::RefId deserializeText(std::string_view value); + // Constructs RefId from a string using a pointer to a static set of strings. static RefId stringRefId(std::string_view value); @@ -118,6 +121,9 @@ namespace ESM // Copy mValue byte by byte into a string. Use result only during within the same process. std::string serialize() const; + // Serialize into stable text format. + std::string serializeText() const; + friend constexpr bool operator==(const RefId& l, const RefId& r) { return l.mValue == r.mValue; } bool operator==(std::string_view rhs) const; diff --git a/components/esm/serializerefid.hpp b/components/esm/serializerefid.hpp new file mode 100644 index 0000000000..9f43511622 --- /dev/null +++ b/components/esm/serializerefid.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_COMPONENTS_ESM_SERIALIZEREFID_HPP +#define OPENMW_COMPONENTS_ESM_SERIALIZEREFID_HPP + +#include +#include +#include +#include +#include + +namespace ESM +{ + constexpr std::string_view formIdRefIdPrefix = "FormId:"; + constexpr std::string_view generatedRefIdPrefix = "Generated:"; + constexpr std::string_view indexRefIdPrefix = "Index:"; + + template + std::size_t getIntegralSize(T value) + { + std::size_t result = sizeof(T) * 2; + while (true) + { + if (result == 1 || (value & static_cast(0xf) << ((result - 1) * 4)) != 0) + break; + --result; + } + return result; + } + + inline void serializeRefIdPrefix(std::size_t valueSize, std::string_view prefix, std::string& out) + { + out.resize(prefix.size() + valueSize + 2, '\0'); + std::memcpy(out.data(), prefix.data(), prefix.size()); + } + + template + void serializeIntegral(T value, std::size_t shift, std::string& out) + { + out[shift] = '0'; + out[shift + 1] = 'x'; + const auto r = std::to_chars(out.data() + shift + 2, out.data() + out.size(), value, 16); + if (r.ec != std::errc()) + throw std::system_error(std::make_error_code(r.ec), "Failed to serialize ESM::RefId integral value"); + } + + template + void serializeRefIdValue(T value, std::string_view prefix, std::string& out) + { + serializeRefIdPrefix(getIntegralSize(value), prefix, out); + serializeIntegral(value, prefix.size(), out); + } + + template + T deserializeIntegral(std::size_t shift, std::string_view value) + { + T result{}; + const auto r = std::from_chars(value.data() + shift + 2, value.data() + value.size(), result, 16); + if (r.ec != std::errc()) + throw std::system_error(std::make_error_code(r.ec), + "Failed to deserialize ESM::RefId integral value: \"" + std::string(value) + '"'); + return result; + } +} + +#endif diff --git a/components/esm/stringrefid.cpp b/components/esm/stringrefid.cpp index 22cf045a32..ad7d35903e 100644 --- a/components/esm/stringrefid.cpp +++ b/components/esm/stringrefid.cpp @@ -60,13 +60,13 @@ namespace ESM std::ostream& operator<<(std::ostream& stream, StringRefId value) { - stream << "String{"; + stream << '"'; for (char c : *value.mValue) if (std::isprint(c) && c != '\t' && c != '\n' && c != '\r') stream << c; else stream << "\\x" << std::hex << std::uppercase << static_cast(static_cast(c)); - return stream << '}'; + return stream << '"'; } std::string StringRefId::toDebugString() const