diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 52227f2544..8d667082bc 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -409,7 +409,7 @@ namespace MWGui mSpellStatus->setProgressPosition(successChancePercent); mSpellBox->setUserString("ToolTipType", "Spell"); - mSpellBox->setUserString("Spell", spellId.getRefIdString()); + mSpellBox->setUserString("Spell", spellId.serialize()); // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find( diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e42c6cfdf6..a820eeaf5c 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -296,7 +296,7 @@ namespace MWGui mSelected->button->setItem(MWWorld::Ptr()); mSelected->button->setUserString("ToolTipType", "Spell"); - mSelected->button->setUserString("Spell", spellId.getRefIdString()); + mSelected->button->setUserString("Spell", spellId.serialize()); // use the icon of the first effect const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); @@ -517,7 +517,7 @@ namespace MWGui break; } case ESM::QuickKeys::Type::Magic: - key.mId = ESM::RefId::stringRefId(button->getUserString("Spell")); + key.mId = ESM::RefId::deserialize(button->getUserString("Spell")); break; } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 5d21cb00bd..3f8b8f5d9d 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -452,13 +452,13 @@ namespace MWGui const ESM::Race* race = store.get().find(mCurrentRaceId); int i = 0; - for (const auto& spellpower : race->mPowers.mList) + for (const ESM::RefId& spellpower : race->mPowers.mList) { Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget( "MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i)); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); - spellPowerWidget->setUserString("Spell", spellpower.getRefIdString()); + spellPowerWidget->setUserString("Spell", spellpower.serialize()); mSpellPowerItems.push_back(spellPowerWidget); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 60594fbe7b..7219fa2273 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -315,7 +315,7 @@ namespace MWGui "MW_StatName", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); widget->setSpellId(spell->mId); widget->setUserString("ToolTipType", "Spell"); - widget->setUserString("Spell", spell->mId.getRefIdString()); + widget->setUserString("Spell", spell->mId.serialize()); widget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillWidgets.push_back(widget); diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 8117f758ca..8190a81f97 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -66,7 +66,7 @@ namespace MWGui toAdd->setSize(mSpellsView->getWidth(), lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel); toAdd->setUserString("ToolTipType", "Spell"); - toAdd->setUserString("Spell", spell.mId.getRefIdString()); + toAdd->setUserString("Spell", spell.mId.serialize()); toAdd->setUserString("SpellCost", std::to_string(spell.mData.mCost)); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onSpellButtonClick); mSpellsWidgetMap.insert(std::make_pair(toAdd, spell.mId)); diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 9b568800ef..64af057d4d 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -286,7 +286,7 @@ namespace MWGui else { widget->setUserString("ToolTipType", "Spell"); - widget->setUserString("Spell", spell.mId.getRefIdString()); + widget->setUserString("Spell", spell.mId.serialize()); } widget->setUserString(sSpellModelIndex, MyGUI::utility::toString(index)); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 9d0cbee1eb..c829fdb2c4 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -227,7 +227,7 @@ namespace MWGui ToolTipInfo info; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find( - ESM::RefId::stringRefId(focus->getUserString("Spell"))); + ESM::RefId::deserialize(focus->getUserString("Spell"))); info.caption = spell->mName; Widgets::SpellEffectList effects; for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) diff --git a/apps/openmw_test_suite/esm/testrefid.cpp b/apps/openmw_test_suite/esm/testrefid.cpp index e809daa12f..48262b3640 100644 --- a/apps/openmw_test_suite/esm/testrefid.cpp +++ b/apps/openmw_test_suite/esm/testrefid.cpp @@ -179,14 +179,14 @@ namespace ESM TEST(ESMRefIdTest, canBeUsedAsMapKeyWithLookupByStringView) { - const std::map> map({ { ESM::RefId::stringRefId("a"), 42 } }); + const std::map> map({ { RefId::stringRefId("a"), 42 } }); EXPECT_EQ(map.count("A"), 1); } TEST(ESMRefIdTest, canBeUsedAsLookupKeyForMapWithStringKey) { const std::map> map({ { "a", 42 } }); - EXPECT_EQ(map.count(ESM::RefId::stringRefId("A")), 1); + EXPECT_EQ(map.count(RefId::stringRefId("A")), 1); } TEST(ESMRefIdTest, stringRefIdIsNotEqualToFormId) @@ -196,7 +196,7 @@ namespace ESM EXPECT_NE(stringRefId, formIdRefId); } - struct ESMRefIdToStringTest : TestWithParam> + struct ESMRefIdToStringTest : TestWithParam> { }; @@ -213,12 +213,12 @@ namespace ESM { RefId::stringRefId(std::string({ 'a', 0, -1, '\n', '\t' })), { 'a', 0, -1, '\n', '\t' } }, { RefId::formIdRefId(42), "42" }, { RefId::generated(42), "42" }, - { RefId::index(ESM::REC_ARMO, 42), "ARMO, 42" }, + { RefId::index(REC_ARMO, 42), "ARMO, 42" }, }; INSTANTIATE_TEST_SUITE_P(ESMRefIdToString, ESMRefIdToStringTest, ValuesIn(toStringParams)); - struct ESMRefIdToDebugStringTest : TestWithParam> + struct ESMRefIdToDebugStringTest : TestWithParam> { }; @@ -244,9 +244,61 @@ namespace ESM { 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(ESM::REC_ARMO, 42), "Index{ARMO, 42}" }, + { RefId::index(REC_ARMO, 42), "Index{ARMO, 42}" }, }; INSTANTIATE_TEST_SUITE_P(ESMRefIdToDebugString, ESMRefIdToDebugStringTest, ValuesIn(toDebugStringParams)); + + template + RefId generateRefId(); + + template <> + RefId generateRefId() + { + return RefId(); + } + + template <> + RefId generateRefId() + { + return RefId::stringRefId("StringRefId"); + } + + template <> + RefId generateRefId() + { + return RefId::formIdRefId(42); + } + + template <> + RefId generateRefId() + { + return RefId::generated(13); + } + + template <> + RefId generateRefId() + { + return RefId::index(REC_BOOK, 7); + } + + template + struct ESMRefIdSerializeDeserializeTest : Test + { + }; + + TYPED_TEST_SUITE_P(ESMRefIdSerializeDeserializeTest); + + TYPED_TEST_P(ESMRefIdSerializeDeserializeTest, serializeThenDeserializeShouldProduceSameValue) + { + const RefId refId = generateRefId(); + EXPECT_EQ(RefId::deserialize(refId.serialize()), refId); + } + + REGISTER_TYPED_TEST_SUITE_P(ESMRefIdSerializeDeserializeTest, serializeThenDeserializeShouldProduceSameValue); + + using RefIdTypeParams = Types; + + INSTANTIATE_TYPED_TEST_SUITE_P(RefIdTypes, ESMRefIdSerializeDeserializeTest, RefIdTypeParams); } } diff --git a/components/esm/refid.cpp b/components/esm/refid.cpp index e6bdaa2df0..8d5cdfd5ab 100644 --- a/components/esm/refid.cpp +++ b/components/esm/refid.cpp @@ -177,4 +177,20 @@ namespace ESM { return std::visit(Contains{ subString }, mValue); } + + std::string RefId::serialize() const + { + std::string result(sizeof(mValue), '\0'); + std::memcpy(result.data(), &mValue, sizeof(mValue)); + return result; + } + + ESM::RefId RefId::deserialize(std::string_view value) + { + if (value.size() != sizeof(ESM::RefId::mValue)) + throw std::runtime_error("Invalid value size to deserialize: " + std::to_string(value.size())); + ESM::RefId result; + std::memcpy(&result.mValue, value.data(), sizeof(result.mValue)); + return result; + } } diff --git a/components/esm/refid.hpp b/components/esm/refid.hpp index 351a7630e5..dc90be5760 100644 --- a/components/esm/refid.hpp +++ b/components/esm/refid.hpp @@ -48,6 +48,9 @@ namespace ESM public: const static RefId sEmpty; + // Constructs RefId from a string containing byte by byte copy of RefId::mValue. + static ESM::RefId deserialize(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); @@ -112,6 +115,9 @@ namespace ESM // Otherwise returns false. bool contains(std::string_view subString) const; + // Copy mValue byte by byte into a string. Use result only during within the same process. + std::string serialize() const; + friend constexpr bool operator==(const RefId& l, const RefId& r) { return l.mValue == r.mValue; } bool operator==(std::string_view rhs) const;