1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-28 16:09:43 +00:00

Make ESM::RefId to be fixed size cheap to copy

Use std::variant. Store refId strings in unordered_set and use pointer to an
item there. Inserts to unordered_set do not invalidate pointers to values so the
pointer is always valid. Elements are not removed. Assume there is finite number
of string refIds.
This commit is contained in:
elsid 2023-02-20 23:11:18 +01:00
parent 3a0443c472
commit 069d4255b9
No known key found for this signature in database
GPG key ID: 4DE04C198CBA7625
30 changed files with 571 additions and 106 deletions

View file

@ -25,5 +25,5 @@ int CSMTools::MandatoryIdStage::setup()
void CSMTools::MandatoryIdStage::perform(int stage, CSMDoc::Messages& messages)
{
if (mIdCollection.searchId(mIds.at(stage)) == -1 || mIdCollection.getRecord(mIds.at(stage)).isDeleted())
messages.add(mCollectionId, "Missing mandatory record: " + mIds.at(stage).getRefIdString());
messages.add(mCollectionId, "Missing mandatory record: " + mIds.at(stage).toDebugString());
}

View file

@ -658,7 +658,7 @@ int CSMWorld::RefIdCollection::getIndex(const ESM::RefId& id) const
int index = searchId(id);
if (index == -1)
throw std::runtime_error("invalid ID: " + id.getRefIdString());
throw std::runtime_error("invalid ID: " + id.toDebugString());
return index;
}

View file

@ -40,8 +40,7 @@ namespace MWDialogue
return;
}
throw std::runtime_error(
"unknown info ID " + mInfoId.getRefIdString() + " for topic " + topic.getRefIdString());
throw std::runtime_error("unknown info ID " + mInfoId.toDebugString() + " for topic " + topic.toDebugString());
}
Entry::Entry(const ESM::JournalEntry& record)
@ -98,7 +97,7 @@ namespace MWDialogue
return iter->mId;
}
throw std::runtime_error("unknown journal index for topic " + topic.getRefIdString());
throw std::runtime_error("unknown journal index for topic " + topic.toDebugString());
}
StampedJournalEntry::StampedJournalEntry()

View file

@ -75,7 +75,7 @@ namespace MWDialogue
[&](const auto& info) { return info.mId == entry.mInfoId; });
if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1)
throw std::runtime_error("unknown journal entry for topic " + mTopic.getRefIdString());
throw std::runtime_error("unknown journal entry for topic " + mTopic.toDebugString());
if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart)
mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished;

View file

@ -21,7 +21,7 @@ namespace MWDialogue
bool Topic::addEntry(const JournalEntry& entry)
{
if (entry.mTopic != mTopic)
throw std::runtime_error("topic does not match: " + mTopic.getRefIdString());
throw std::runtime_error("topic does not match: " + mTopic.toDebugString());
// bail out if we already have heard this
for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it)

View file

@ -896,7 +896,7 @@ namespace MWGui
= Misc::ResourceHelpers::correctTexturePath("textures\\levelup\\" + classId.getRefIdString() + ".dds", vfs);
if (!vfs->exists(classImage))
{
Log(Debug::Warning) << "No class image for " << classId.getRefIdString() << ", falling back to default";
Log(Debug::Warning) << "No class image for " << classId << ", falling back to default";
classImage = "textures\\levelup\\warrior.dds";
}
imageBox->setImageTexture(classImage);

View file

@ -683,7 +683,7 @@ namespace MWGui
if (!mConsoleMode.empty())
title = mConsoleMode + " " + title;
if (!mPtr.isEmpty())
title.append(" (" + mPtr.getCellRef().getRefId().getRefIdString() + ")");
title.append(" (" + mPtr.getCellRef().getRefId().toDebugString() + ")");
setTitle(title);
}

View file

@ -160,12 +160,7 @@ namespace
if (result != 0)
return result > 0;
// compare items by Id
leftName = left.mBase.getCellRef().getRefId().getRefIdString();
rightName = right.mBase.getCellRef().getRefId().getRefIdString();
result = leftName.compare(rightName);
return result < 0;
return left.mBase.getCellRef().getRefId() < right.mBase.getCellRef().getRefId();
}
};
}

View file

@ -124,7 +124,7 @@ namespace MWGui
ToolTipInfo info;
info.caption = mFocusObject.getClass().getName(mFocusObject);
if (info.caption.empty())
info.caption = mFocusObject.getCellRef().getRefId().getRefIdString();
info.caption = mFocusObject.getCellRef().getRefId().toDebugString();
info.icon.clear();
tooltipSize = createToolTip(info, checkOwned());
}
@ -686,7 +686,7 @@ namespace MWGui
if (!creature)
return {};
if (creature->mName.empty())
return " (" + creature->mId.getRefIdString() + ")";
return " (" + creature->mId.toDebugString() + ")";
return " (" + creature->mName + ")";
}
@ -720,10 +720,10 @@ namespace MWGui
for (std::pair<ESM::RefId, int>& owner : itemOwners)
{
if (owner.second == std::numeric_limits<int>::max())
ret += std::string("\nStolen from ") + owner.first.getRefIdString(); // for legacy (ESS) savegames
ret += std::string("\nStolen from ") + owner.first.toDebugString(); // for legacy (ESS) savegames
else
ret += std::string("\nStolen ") + MyGUI::utility::toString(owner.second) + " from "
+ owner.first.getRefIdString();
+ owner.first.toDebugString();
}
ret += getMiscString(cellref.getGlobalVariable(), "Global");

View file

@ -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().getRefIdString(); });
[](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().toDebugString(); });
objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>> {
const MWWorld::Ptr& ptr = o.ptr();
if (ptr.isInCell())

View file

@ -49,7 +49,7 @@ namespace MWMechanics
case ESM::REC_NPC_:
return ::withBaseRecord<ESM::NPC>(mId, function);
default:
throw std::logic_error("failed to update base record for " + mId.getRefIdString());
throw std::logic_error("failed to update base record for " + mId.toDebugString());
}
}
@ -62,7 +62,7 @@ namespace MWMechanics
case ESM::REC_NPC_:
return getSpellList<ESM::NPC>(mId);
default:
throw std::logic_error("failed to get spell list for " + mId.getRefIdString());
throw std::logic_error("failed to get spell list for " + mId.toDebugString());
}
}

View file

@ -208,7 +208,7 @@ namespace MWScript
return iter->second;
}
throw std::logic_error("script " + name.getRefIdString() + " does not exist");
throw std::logic_error("script " + name.toDebugString() + " does not exist");
}
GlobalScripts& ScriptManager::getGlobalScripts()

View file

@ -65,8 +65,8 @@ namespace MWScript
from = container;
else
{
std::string error = "Failed to find the container of object '"
+ from.getCellRef().getRefId().getRefIdString() + "'";
const std::string error
= "Failed to find the container of object " + from.getCellRef().getRefId().toDebugString();
runtime.getContext().report(error);
Log(Debug::Error) << error;
runtime.push(0.f);
@ -77,7 +77,7 @@ namespace MWScript
const MWWorld::Ptr to = MWBase::Environment::get().getWorld()->searchPtr(name, false);
if (to.isEmpty())
{
std::string error = "Failed to find an instance of object '" + name.getRefIdString() + "'";
const std::string error = "Failed to find an instance of object " + name.toDebugString();
runtime.getContext().report(error);
Log(Debug::Error) << error;
runtime.push(0.f);

View file

@ -62,7 +62,7 @@ namespace MWWorld
}
if (it == invStore.end())
throw std::runtime_error("ActionEquip can't find item " + object.getCellRef().getRefId().getRefIdString());
throw std::runtime_error("ActionEquip can't find item " + object.getCellRef().getRefId().toDebugString());
// equip the item in the first free slot
std::vector<int>::const_iterator slot = slots_.first.begin();

View file

@ -950,13 +950,13 @@ namespace MWWorld
}
else
{
Log(Debug::Error) << "Cell reference '" + ref.mRefID.getRefIdString() + "' not found!";
Log(Debug::Error) << "Cell reference " << ref.mRefID << " is not found!";
return;
}
if (!handledType)
{
Log(Debug::Error) << "Error: Ignoring reference '" << ref.mRefID.getRefIdString() << "' of unhandled type";
Log(Debug::Error) << "Error: Ignoring reference " << ref.mRefID << " of unhandled type";
return;
}

View file

@ -783,7 +783,7 @@ int MWWorld::ContainerStore::getType(const ConstPtr& ptr)
if (ptr.getType() == ESM::Weapon::sRecordId)
return Type_Weapon;
throw std::runtime_error("Object '" + ptr.getCellRef().getRefId().getRefIdString() + "' of type "
throw std::runtime_error("Object " + ptr.getCellRef().getRefId().toDebugString() + " of type "
+ std::string(ptr.getTypeDescription()) + " can not be placed into a container");
}

View file

@ -731,10 +731,7 @@ namespace MWWorld
}
const ESM::RefId id = ESM::RefId::stringRefId("$dynamic" + std::to_string(mDynamicCount++));
if (npcs.search(id) != nullptr)
{
const std::string msg = "Try to override existing record '" + id.getRefIdString() + "'";
throw std::runtime_error(msg);
}
throw std::runtime_error("Try to override existing record: " + id.toDebugString());
ESM::NPC record = npc;
record.mId = id;

View file

@ -189,10 +189,7 @@ namespace MWWorld
Store<T>& store = getWritable<T>();
if (store.search(id) != nullptr)
{
const std::string msg = "Try to override existing record '" + id.getRefIdString() + "'";
throw std::runtime_error(msg);
}
throw std::runtime_error("Try to override existing record: " + id.toDebugString());
T record = x;
record.mId = id;

View file

@ -94,10 +94,10 @@ MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const ESM::RefId&
create(store.get<ESM4::Static>(), name, mRef, mPtr);
break;
case 0:
throw std::logic_error("failed to create manual cell ref for " + name.getRefIdString() + " (unknown ID)");
throw std::logic_error("failed to create manual cell ref for " + name.toDebugString() + " (unknown ID)");
default:
throw std::logic_error("failed to create manual cell ref for " + name.getRefIdString() + " (unknown type)");
throw std::logic_error("failed to create manual cell ref for " + name.toDebugString() + " (unknown type)");
}
mPtr.getRefData().setCount(count);

View file

@ -16,7 +16,7 @@ namespace MWWorld
res.append(" (");
res.append(getTypeDescription());
res.append(", ");
res.append(getCellRef().getRefId().getRefIdString());
res.append(getCellRef().getRefId().toDebugString());
res.append(")");
return res;
}

View file

@ -950,11 +950,8 @@ namespace MWWorld
const ESM::Pathgrid* Store<ESM::Pathgrid>::find(const ESM::RefId& name) const
{
const ESM::Pathgrid* pathgrid = search(name);
if (!pathgrid)
{
const std::string msg = "Pathgrid in cell '" + name.getRefIdString() + "' not found";
throw std::runtime_error(msg);
}
if (pathgrid == nullptr)
throw std::runtime_error("Pathgrid in cell " + name.toDebugString() + " is not found");
return pathgrid;
}
const ESM::Pathgrid* Store<ESM::Pathgrid>::search(const ESM::Cell& cell) const

View file

@ -729,7 +729,7 @@ namespace MWWorld
Ptr ret = searchPtr(name, activeOnly);
if (!ret.isEmpty())
return ret;
std::string error = "failed to find an instance of object '" + name.getRefIdString() + "'";
std::string error = "Failed to find an instance of object " + name.toDebugString();
if (activeOnly)
error += " in active cells";
throw std::runtime_error(error);
@ -1146,12 +1146,12 @@ namespace MWWorld
MWWorld::Ptr newPtr = ptr;
if (!isPlayer && !currCell)
throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId().getRefIdString()
+ "\" to another cell: current cell is nullptr");
throw std::runtime_error("Can not move actor " + ptr.getCellRef().getRefId().toDebugString()
+ " to another cell: current cell is nullptr");
if (!newCell)
throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId().getRefIdString()
+ "\" to another cell: new cell is nullptr");
throw std::runtime_error("Can not move actor " + ptr.getCellRef().getRefId().toDebugString()
+ " to another cell: new cell is nullptr");
if (currCell != newCell)
{

View file

@ -89,6 +89,20 @@ namespace ESM
EXPECT_EQ(lower, upper);
}
TEST(ESMRefIdTest, equalityIsDefinedForStringRefIdAndRefId)
{
const StringRefId stringRefId("ref_id");
const RefId refId = RefId::stringRefId("REF_ID");
EXPECT_EQ(stringRefId, refId);
}
TEST(ESMRefIdTest, equalityIsDefinedForFormRefIdAndRefId)
{
const FormIdRefId formIdRefId(42);
const RefId refId = RefId::formIdRefId(42);
EXPECT_EQ(formIdRefId, refId);
}
TEST(ESMRefIdTest, stringRefIdIsEqualToItself)
{
const RefId refId = RefId::stringRefId("ref_id");
@ -102,6 +116,20 @@ namespace ESM
EXPECT_LT(a, b);
}
TEST(ESMRefIdTest, lessThanIsDefinedForStringRefIdAndRefId)
{
const StringRefId stringRefId("a");
const RefId refId = RefId::stringRefId("B");
EXPECT_LT(stringRefId, refId);
}
TEST(ESMRefIdTest, lessThanIsDefinedForFormRefIdAndRefId)
{
const FormIdRefId formIdRefId(13);
const RefId refId = RefId::formIdRefId(42);
EXPECT_LT(formIdRefId, refId);
}
TEST(ESMRefIdTest, stringRefIdHasCaseInsensitiveHash)
{
const RefId lower = RefId::stringRefId("a");
@ -133,6 +161,22 @@ namespace ESM
EXPECT_LT(b, c);
}
TEST(ESMRefIdTest, stringRefIdHasStrongOrderWithFormId)
{
const RefId stringRefId = RefId::stringRefId("a");
const RefId formIdRefId = RefId::formIdRefId(42);
EXPECT_TRUE(stringRefId < formIdRefId);
EXPECT_FALSE(formIdRefId < stringRefId);
}
TEST(ESMRefIdTest, formIdRefIdHasStrongOrderWithStringView)
{
const RefId formIdRefId = RefId::formIdRefId(42);
const std::string_view stringView = "42";
EXPECT_TRUE(stringView < formIdRefId);
EXPECT_FALSE(formIdRefId < stringView);
}
TEST(ESMRefIdTest, canBeUsedAsMapKeyWithLookupByStringView)
{
const std::map<ESM::RefId, int, std::less<>> map({ { ESM::RefId::stringRefId("a"), 42 } });
@ -144,5 +188,61 @@ namespace ESM
const std::map<std::string, int, std::less<>> map({ { "a", 42 } });
EXPECT_EQ(map.count(ESM::RefId::stringRefId("A")), 1);
}
TEST(ESMRefIdTest, stringRefIdIsNotEqualToFormId)
{
const RefId stringRefId = RefId::stringRefId("\0");
const RefId formIdRefId = RefId::formIdRefId(0);
EXPECT_NE(stringRefId, formIdRefId);
}
struct ESMRefIdToStringTest : TestWithParam<std::pair<ESM::RefId, std::string>>
{
};
TEST_P(ESMRefIdToStringTest, toString)
{
const RefId& refId = GetParam().first;
const std::string& string = GetParam().second;
EXPECT_EQ(refId.toString(), string);
}
const std::vector<std::pair<RefId, std::string>> toStringParams = {
{ 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" },
};
INSTANTIATE_TEST_SUITE_P(ESMRefIdToString, ESMRefIdToStringTest, ValuesIn(toStringParams));
struct ESMRefIdToDebugStringTest : TestWithParam<std::pair<ESM::RefId, std::string>>
{
};
TEST_P(ESMRefIdToDebugStringTest, toDebugString)
{
const RefId& refId = GetParam().first;
const std::string& debugString = GetParam().second;
EXPECT_EQ(refId.toDebugString(), debugString);
}
TEST_P(ESMRefIdToDebugStringTest, toStream)
{
const RefId& refId = GetParam().first;
const std::string& debugString = GetParam().second;
std::ostringstream stream;
stream << refId;
EXPECT_EQ(stream.str(), debugString);
}
const std::vector<std::pair<RefId, std::string>> 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}" },
};
INSTANTIATE_TEST_SUITE_P(ESMRefIdToDebugString, ESMRefIdToDebugStringTest, ValuesIn(toDebugStringParams));
}
}

View file

@ -85,7 +85,10 @@ add_component_dir (to_utf8
to_utf8
)
add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge)
add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge
formidrefid
stringrefid
)
add_component_dir(fx pass technique lexer widgets stateupdater)

View file

@ -0,0 +1,24 @@
#include "formidrefid.hpp"
#include <ostream>
#include <sstream>
namespace ESM
{
std::string FormIdRefId::toString() const
{
return std::to_string(mValue);
}
std::string FormIdRefId::toDebugString() const
{
std::ostringstream stream;
stream << *this;
return stream.str();
}
std::ostream& operator<<(std::ostream& stream, FormIdRefId value)
{
return stream << "FormId{" << value.mValue << '}';
}
}

View file

@ -0,0 +1,52 @@
#ifndef OPENMW_COMPONENTS_ESM_FORMIDREFID_HPP
#define OPENMW_COMPONENTS_ESM_FORMIDREFID_HPP
#include <functional>
#include <iosfwd>
#include <components/esm4/formid.hpp>
namespace ESM
{
class FormIdRefId
{
public:
constexpr FormIdRefId() = default;
constexpr explicit FormIdRefId(ESM4::FormId value) noexcept
: mValue(value)
{
}
ESM4::FormId getValue() const { return mValue; }
std::string toString() const;
std::string toDebugString() const;
constexpr bool operator==(FormIdRefId rhs) const noexcept { return mValue == rhs.mValue; }
constexpr bool operator<(FormIdRefId rhs) const noexcept { return mValue < rhs.mValue; }
friend std::ostream& operator<<(std::ostream& stream, FormIdRefId value);
friend struct std::hash<FormIdRefId>;
private:
ESM4::FormId mValue = 0;
};
}
namespace std
{
template <>
struct hash<ESM::FormIdRefId>
{
std::size_t operator()(ESM::FormIdRefId value) const noexcept
{
return std::hash<ESM4::FormId>{}(value.mValue);
}
};
}
#endif

View file

@ -1,68 +1,162 @@
#include "refid.hpp"
#include <iostream>
#include "components/misc/strings/algorithm.hpp"
#include <ostream>
#include <sstream>
#include <stdexcept>
namespace ESM
{
bool RefId::operator==(const RefId& rhs) const
namespace
{
return Misc::StringUtils::ciEqual(mId, rhs.mId);
const std::string emptyString;
struct GetRefString
{
const std::string& operator()(EmptyRefId /*v*/) const { return emptyString; }
const std::string& operator()(StringRefId v) const { return v.getValue(); }
template <class T>
const std::string& operator()(const T& v) const
{
std::ostringstream stream;
stream << "RefId is not a string: " << v;
throw std::runtime_error(stream.str());
}
};
struct IsEqualToString
{
const std::string_view mRhs;
bool operator()(StringRefId v) const noexcept { return v == mRhs; }
template <class T>
bool operator()(const T& /*v*/) const noexcept
{
return false;
}
};
struct IsLessThanString
{
const std::string_view mRhs;
bool operator()(StringRefId v) const noexcept { return v < mRhs; }
template <class T>
bool operator()(const T& /*v*/) const noexcept
{
return false;
}
};
struct IsGreaterThanString
{
const std::string_view mLhs;
bool operator()(StringRefId v) const noexcept { return mLhs < v; }
template <class T>
bool operator()(const T& /*v*/) const noexcept
{
return true;
}
};
struct StartsWith
{
const std::string_view mPrefix;
bool operator()(StringRefId v) const { return v.startsWith(mPrefix); }
template <class T>
bool operator()(const T& /*v*/) const
{
return false;
}
};
struct Contains
{
const std::string_view mSubString;
bool operator()(StringRefId v) const { return v.contains(mSubString); }
template <class T>
bool operator()(const T& /*v*/) const
{
return false;
}
};
}
bool RefId::operator<(const RefId& rhs) const
const RefId RefId::sEmpty = {};
std::string EmptyRefId::toString() const
{
return Misc::StringUtils::ciLess(mId, rhs.mId);
return std::string();
}
bool operator<(const RefId& lhs, std::string_view rhs)
std::string EmptyRefId::toDebugString() const
{
return Misc::StringUtils::ciLess(lhs.mId, rhs);
return "Empty{}";
}
bool operator<(std::string_view lhs, const RefId& rhs)
std::ostream& operator<<(std::ostream& stream, EmptyRefId value)
{
return Misc::StringUtils::ciLess(lhs, rhs.mId);
}
std::ostream& operator<<(std::ostream& os, const RefId& refId)
{
os << refId.getRefIdString();
return os;
}
RefId RefId::stringRefId(std::string_view id)
{
RefId newRefId;
newRefId.mId = id;
return newRefId;
}
RefId RefId::formIdRefId(const ESM4::FormId id)
{
return ESM::RefId::stringRefId(ESM4::formIdToString(id));
return stream << value.toDebugString();
}
bool RefId::operator==(std::string_view rhs) const
{
return Misc::StringUtils::ciEqual(mId, rhs);
return std::visit(IsEqualToString{ rhs }, mValue);
}
bool operator<(RefId lhs, std::string_view rhs)
{
return std::visit(IsLessThanString{ rhs }, lhs.mValue);
}
bool operator<(std::string_view lhs, RefId rhs)
{
return std::visit(IsGreaterThanString{ lhs }, rhs.mValue);
}
std::ostream& operator<<(std::ostream& stream, RefId value)
{
return std::visit([&](auto v) -> std::ostream& { return stream << v; }, value.mValue);
}
RefId RefId::stringRefId(std::string_view value)
{
if (value.empty())
return RefId();
return RefId(StringRefId(value));
}
const std::string& RefId::getRefIdString() const
{
return std::visit(GetRefString{}, mValue);
}
std::string RefId::toString() const
{
return std::visit([](auto v) { return v.toString(); }, mValue);
}
std::string RefId::toDebugString() const
{
return std::visit([](auto v) { return v.toDebugString(); }, mValue);
}
bool RefId::startsWith(std::string_view prefix) const
{
return Misc::StringUtils::ciStartsWith(mId, prefix);
return std::visit(StartsWith{ prefix }, mValue);
}
bool RefId::contains(std::string_view subString) const
{
return Misc::StringUtils::ciFind(mId, subString) != std::string_view::npos;
return std::visit(Contains{ subString }, mValue);
}
const RefId RefId::sEmpty = {};
}
std::size_t std::hash<ESM::RefId>::operator()(const ESM::RefId& k) const
{
return Misc::StringUtils::CiHash()(k.getRefIdString());
}

View file

@ -5,11 +5,28 @@
#include <iosfwd>
#include <string>
#include <string_view>
#include <variant>
#include <components/esm4/formid.hpp>
#include <components/misc/notnullptr.hpp>
#include "formidrefid.hpp"
#include "stringrefid.hpp"
namespace ESM
{
struct EmptyRefId
{
constexpr bool operator==(EmptyRefId /*rhs*/) const { return true; }
constexpr bool operator<(EmptyRefId /*rhs*/) const { return false; }
std::string toString() const;
std::string toDebugString() const;
friend std::ostream& operator<<(std::ostream& stream, EmptyRefId value);
};
// RefId is used to represent an Id that identifies an ESM record. These Ids can then be used in
// ESM::Stores to find the actual record. These Ids can be serialized/de-serialized, stored on disk and remain
// valid. They are used by ESM files, by records to reference other ESM records.
@ -18,45 +35,83 @@ namespace ESM
public:
const static RefId sEmpty;
// The 2 following functions are used to move back and forth between string and RefID. Used for hard coded
// RefIds that are as string in the code. For serialization, and display. Using explicit conversions make it
// very clear where in the code we need to convert from string to RefId and Vice versa.
static RefId stringRefId(std::string_view id);
// Constructs RefId from a string using a pointer to a static set of strings.
static RefId stringRefId(std::string_view value);
static RefId formIdRefId(const ESM4::FormId id);
// Constructs RefId from ESM4 FormId storing the value in-place.
static RefId formIdRefId(ESM4::FormId value) noexcept { return RefId(FormIdRefId(value)); }
const std::string& getRefIdString() const { return mId; }
constexpr RefId() = default;
bool empty() const { return mId.empty(); }
constexpr RefId(EmptyRefId value) noexcept
: mValue(value)
{
}
RefId(StringRefId value) noexcept
: mValue(value)
{
}
constexpr RefId(FormIdRefId value) noexcept
: mValue(value)
{
}
// Returns a reference to the value of StringRefId if it's the underlying value or throws an exception.
const std::string& getRefIdString() const;
// Returns a string with serialized underlying value.
std::string toString() const;
// Returns a string with serialized underlying value with information about underlying type.
std::string toDebugString() const;
constexpr bool empty() const noexcept { return std::holds_alternative<EmptyRefId>(mValue); }
// Returns true if underlying value is StringRefId and its underlying std::string starts with given prefix.
// Otherwise returns false.
bool startsWith(std::string_view prefix) const;
// Returns true if underlying value is StringRefId and its underlying std::string contains given subString.
// Otherwise returns false.
bool contains(std::string_view subString) const;
bool operator==(const RefId& rhs) const;
friend constexpr bool operator==(const RefId& l, const RefId& r) { return l.mValue == r.mValue; }
bool operator==(std::string_view rhs) const;
bool operator<(const RefId& rhs) const;
friend constexpr bool operator<(const RefId& l, const RefId& r) { return l.mValue < r.mValue; }
friend bool operator<(const RefId& lhs, std::string_view rhs);
friend bool operator<(RefId lhs, std::string_view rhs);
friend bool operator<(std::string_view lhs, const RefId& rhs);
friend bool operator<(std::string_view lhs, RefId rhs);
friend std::ostream& operator<<(std::ostream& os, const RefId& dt);
friend std::ostream& operator<<(std::ostream& stream, RefId value);
friend struct std::hash<ESM::RefId>;
private:
std::string mId;
std::variant<EmptyRefId, StringRefId, FormIdRefId> mValue{ EmptyRefId{} };
};
}
namespace std
{
template <>
struct hash<ESM::EmptyRefId>
{
std::size_t operator()(ESM::EmptyRefId /*value*/) const noexcept { return 0; }
};
template <>
struct hash<ESM::RefId>
{
std::size_t operator()(const ESM::RefId& k) const;
std::size_t operator()(ESM::RefId value) const noexcept
{
return std::hash<decltype(value.mValue)>{}(value.mValue);
}
};
}
#endif

View file

@ -0,0 +1,88 @@
#include "stringrefid.hpp"
#include <iomanip>
#include <mutex>
#include <ostream>
#include <sstream>
#include <unordered_set>
#include "components/misc/guarded.hpp"
#include "components/misc/strings/algorithm.hpp"
namespace ESM
{
namespace
{
using StringsSet = std::unordered_set<std::string, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>;
const std::string emptyString;
Misc::NotNullPtr<const std::string> getOrInsertString(std::string_view id)
{
static Misc::ScopeGuarded<StringsSet> refIds;
const auto locked = refIds.lock();
auto it = locked->find(id);
if (it == locked->end())
it = locked->emplace(id).first;
return &*it;
}
}
StringRefId::StringRefId()
: mValue(&emptyString)
{
}
StringRefId::StringRefId(std::string_view value)
: mValue(getOrInsertString(value))
{
}
bool StringRefId::operator==(std::string_view rhs) const noexcept
{
return Misc::StringUtils::ciEqual(*mValue, rhs);
}
bool StringRefId::operator<(StringRefId rhs) const noexcept
{
return Misc::StringUtils::ciLess(*mValue, *rhs.mValue);
}
bool operator<(StringRefId lhs, std::string_view rhs) noexcept
{
return Misc::StringUtils::ciLess(*lhs.mValue, rhs);
}
bool operator<(std::string_view lhs, StringRefId rhs) noexcept
{
return Misc::StringUtils::ciLess(lhs, *rhs.mValue);
}
std::ostream& operator<<(std::ostream& stream, StringRefId value)
{
stream << "String{";
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<unsigned>(static_cast<unsigned char>(c));
return stream << '}';
}
std::string StringRefId::toDebugString() const
{
std::ostringstream stream;
stream << *this;
return stream.str();
}
bool StringRefId::startsWith(std::string_view prefix) const
{
return Misc::StringUtils::ciStartsWith(*mValue, prefix);
}
bool StringRefId::contains(std::string_view subString) const
{
return Misc::StringUtils::ciFind(*mValue, subString) != std::string_view::npos;
}
}

View file

@ -0,0 +1,64 @@
#ifndef OPENMW_COMPONENTS_ESM_STRINGREFID_HPP
#define OPENMW_COMPONENTS_ESM_STRINGREFID_HPP
#include <functional>
#include <iosfwd>
#include <string>
#include <string_view>
#include <variant>
#include <components/misc/notnullptr.hpp>
namespace ESM
{
class StringRefId
{
public:
StringRefId();
// Constructs StringRefId from string using pointer to a static set of strings.
explicit StringRefId(std::string_view value);
const std::string& getValue() const { return *mValue; }
std::string toString() const { return *mValue; }
std::string toDebugString() const;
bool startsWith(std::string_view prefix) const;
bool contains(std::string_view subString) const;
bool operator==(StringRefId rhs) const noexcept { return mValue == rhs.mValue; }
bool operator==(std::string_view rhs) const noexcept;
// Compares values to provide stable order
bool operator<(StringRefId rhs) const noexcept;
friend bool operator<(StringRefId lhs, std::string_view rhs) noexcept;
friend bool operator<(std::string_view lhs, StringRefId rhs) noexcept;
friend std::ostream& operator<<(std::ostream& stream, StringRefId value);
friend struct std::hash<StringRefId>;
private:
Misc::NotNullPtr<const std::string> mValue;
};
}
namespace std
{
template <>
struct hash<ESM::StringRefId>
{
std::size_t operator()(ESM::StringRefId value) const noexcept
{
return std::hash<const std::string*>{}(value.mValue);
}
};
}
#endif