diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index caafa5f324..89a42bf2ea 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -47,19 +47,8 @@ namespace MWDialogue void tokenizeKeywords(const std::string & text, std::vector & tokens) { - const MWWorld::Store & dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); - - std::vector keywordList; - keywordList.reserve(dialogs.getSize()); - for (const auto& it : dialogs) - keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); - sort(keywordList.begin(), keywordList.end()); - - KeywordSearch keywordSearch; - - for (const auto& it : keywordList) - keywordSearch.seed(it, 0 /*unused*/); + const auto& keywordSearch = + MWBase::Environment::get().getWorld()->getStore().get().getDialogIdKeywordSearch(); std::vector::Match> matches; keywordSearch.highlightKeywords(text.begin(), text.end(), matches); diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 39599457ef..3f932084fe 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -74,13 +74,13 @@ public: return left.mBeg < right.mBeg; } - void highlightKeywords (Point beg, Point end, std::vector& out) + void highlightKeywords (Point beg, Point end, std::vector& out) const { std::vector matches; for (Point i = beg; i != end; ++i) { // check first character - typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); + typename Entry::childen_t::const_iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); // no match, on to next character if (candidate == mRoot.mChildren.end ()) @@ -91,11 +91,11 @@ public: // some keywords might be longer variations of other keywords, so we definitely need a list of candidates // the first element in the pair is length of the match, i.e. depth from the first character on - std::vector< typename std::pair > candidates; + std::vector< typename std::pair > candidates; while ((j + 1) != end) { - typename Entry::childen_t::iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); + typename Entry::childen_t::const_iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); if (next == candidate->second.mChildren.end ()) { @@ -116,7 +116,7 @@ public: // shorter candidates will be added to the vector first. however, we want to check against longer candidates first std::reverse(candidates.begin(), candidates.end()); - for (typename std::vector< std::pair >::iterator it = candidates.begin(); + for (typename std::vector< std::pair >::iterator it = candidates.begin(); it != candidates.end(); ++it) { candidate = it->second; diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 4d720a11a7..c767bd669a 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -981,8 +981,11 @@ namespace MWWorld // Dialogue //========================================================================= + Store::Store() + : mKeywordSearchModFlag(true) + { + } - template<> void Store::setUp() { // DialInfos marked as deleted are kept during the loading phase, so that the linked list @@ -997,9 +1000,46 @@ namespace MWWorld // TODO: verify and document this inconsistent behaviour // TODO: if we require this behaviour, maybe we should move it to the place that requires it std::sort(mShared.begin(), mShared.end(), [](const ESM::Dialogue* l, const ESM::Dialogue* r) -> bool { return l->mId < r->mId; }); + + mKeywordSearchModFlag = true; + } + + const ESM::Dialogue *Store::search(const std::string &id) const + { + typename Static::const_iterator it = mStatic.find(id); + if (it != mStatic.end()) + return &(it->second); + + return nullptr; + } + + const ESM::Dialogue *Store::find(const std::string &id) const + { + const ESM::Dialogue *ptr = search(id); + if (ptr == nullptr) + { + std::stringstream msg; + msg << ESM::Dialogue::getRecordType() << " '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + typename Store::iterator Store::begin() const + { + return mShared.begin(); + } + + typename Store::iterator Store::end() const + { + return mShared.end(); + } + + size_t Store::getSize() const + { + return mShared.size(); } - template <> inline RecordId Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; @@ -1018,17 +1058,40 @@ namespace MWWorld found->second.loadData(esm, isDeleted); dialogue.mId = found->second.mId; } + + mKeywordSearchModFlag = true; return RecordId(dialogue.mId, isDeleted); } - template<> bool Store::eraseStatic(const std::string &id) { - mStatic.erase(id); + if (mStatic.erase(id)) + mKeywordSearchModFlag = true; + return true; } + const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() const + { + if (mKeywordSearchModFlag) + { + mKeywordSearch.clear(); + + std::vector keywordList; + keywordList.reserve(getSize()); + for (const auto& it : *this) + keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); + sort(keywordList.begin(), keywordList.end()); + + for (const auto& it : keywordList) + mKeywordSearch.seed(it, 0 /*unused*/); + + mKeywordSearchModFlag = false; + } + + return mKeywordSearch; + } } template class MWWorld::Store; @@ -1044,7 +1107,7 @@ template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 22f36da690..1ec51ad5fd 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -11,6 +11,8 @@ #include #include +#include "../mwdialogue/keywordsearch.hpp" + namespace ESM { struct Land; @@ -154,7 +156,6 @@ namespace MWWorld /// @par mShared usually preserves the record order as it came from the content files (this /// is relevant for the spell autocalc code and selection order /// for heads/hairs in the character creation) - /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. std::vector mShared; typedef std::unordered_map Dynamic; Dynamic mDynamic; @@ -440,6 +441,41 @@ namespace MWWorld iterator end() const; }; + template <> + class Store : public StoreBase + { + typedef std::unordered_map Static; + Static mStatic; + /// @par mShared usually preserves the record order as it came from the content files (this + /// is relevant for the spell autocalc code and selection order + /// for heads/hairs in the character creation) + /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. + std::vector mShared; + + mutable bool mKeywordSearchModFlag; + mutable MWDialogue::KeywordSearch mKeywordSearch; + + public: + Store(); + + typedef SharedIterator iterator; + + void setUp() override; + + const ESM::Dialogue *search(const std::string &id) const; + const ESM::Dialogue *find(const std::string &id) const; + + iterator begin() const; + iterator end() const; + + size_t getSize() const override; + + bool eraseStatic(const std::string &id) override; + + RecordId load(ESM::ESMReader &esm) override; + + const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; + }; } //end namespace