mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-06 16:45:36 +00:00
Simplify InfoCollection
There was additional logic to create topic infos index by topic id to make getTopicInfos and removeDialogueInfos functions faster. In practice it makes loading slower. Move infos index by topic to CSMWorld::Data and use only on loading.
This commit is contained in:
parent
de24cdc12c
commit
b6a2fd8fc1
7 changed files with 90 additions and 437 deletions
|
@ -145,11 +145,11 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages
|
||||||
|
|
||||||
// Test, if we need to save anything associated info records.
|
// Test, if we need to save anything associated info records.
|
||||||
bool infoModified = false;
|
bool infoModified = false;
|
||||||
CSMWorld::InfoCollection::Range range = mInfos.getTopicRange(topic.get().mId.getRefIdString());
|
const auto infos = mInfos.getTopicInfos(topic.get().mId);
|
||||||
|
|
||||||
for (CSMWorld::InfoCollection::RecordConstIterator iter(range.first); iter != range.second; ++iter)
|
for (const auto& record : infos)
|
||||||
{
|
{
|
||||||
if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted)
|
if (record->isModified() || record->mState == CSMWorld::RecordBase::State_Deleted)
|
||||||
{
|
{
|
||||||
infoModified = true;
|
infoModified = true;
|
||||||
break;
|
break;
|
||||||
|
@ -173,35 +173,30 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages
|
||||||
}
|
}
|
||||||
|
|
||||||
// write modified selected info records
|
// write modified selected info records
|
||||||
for (CSMWorld::InfoCollection::RecordConstIterator iter(range.first); iter != range.second; ++iter)
|
for (auto iter = infos.begin(); iter != infos.end(); ++iter)
|
||||||
{
|
{
|
||||||
if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted)
|
const CSMWorld::Record<CSMWorld::Info>& record = **iter;
|
||||||
|
|
||||||
|
if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted)
|
||||||
{
|
{
|
||||||
ESM::DialInfo info = (*iter)->get();
|
ESM::DialInfo info = record.get();
|
||||||
std::string_view infoIdString = info.mId.getRefIdString();
|
info.mId = record.get().mOriginalId;
|
||||||
info.mId = ESM::RefId::stringRefId(infoIdString.substr(infoIdString.find_last_of('#') + 1));
|
|
||||||
|
|
||||||
info.mPrev = ESM::RefId::sEmpty;
|
info.mPrev = ESM::RefId::sEmpty;
|
||||||
if (iter != range.first)
|
if (iter != infos.begin())
|
||||||
{
|
{
|
||||||
CSMWorld::InfoCollection::RecordConstIterator prev = iter;
|
const auto prev = std::prev(iter);
|
||||||
--prev;
|
info.mPrev = (*prev)->get().mOriginalId;
|
||||||
std::string_view prevIdString = (*prev)->get().mId.getRefIdString();
|
|
||||||
info.mPrev = ESM::RefId::stringRefId(prevIdString.substr(prevIdString.find_last_of('#') + 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMWorld::InfoCollection::RecordConstIterator next = iter;
|
const auto next = std::next(iter);
|
||||||
++next;
|
|
||||||
|
|
||||||
info.mNext = ESM::RefId::sEmpty;
|
info.mNext = ESM::RefId::sEmpty;
|
||||||
if (next != range.second)
|
if (next != infos.end())
|
||||||
{
|
info.mNext = (*next)->get().mOriginalId;
|
||||||
std::string_view nextIdString = (*next)->get().mId.getRefIdString();
|
|
||||||
info.mNext = ESM::RefId::stringRefId(nextIdString.substr(nextIdString.find_last_of('#') + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.startRecord(info.sRecordId);
|
writer.startRecord(info.sRecordId);
|
||||||
info.save(writer, (*iter)->mState == CSMWorld::RecordBase::State_Deleted);
|
info.save(writer, record.mState == CSMWorld::RecordBase::State_Deleted);
|
||||||
writer.endRecord(info.sRecordId);
|
writer.endRecord(info.sRecordId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,16 +50,12 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
||||||
int totalInfoCount = 0;
|
int totalInfoCount = 0;
|
||||||
std::set<int> questIndices;
|
std::set<int> questIndices;
|
||||||
|
|
||||||
CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId.getRefIdString());
|
for (const CSMWorld::Record<CSMWorld::Info>* record : mJournalInfos.getTopicInfos(journal.mId))
|
||||||
|
|
||||||
for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it)
|
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<CSMWorld::Info> infoRecord = (*it->get());
|
if (record->isDeleted())
|
||||||
|
|
||||||
if (infoRecord.isDeleted())
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const CSMWorld::Info& journalInfo = infoRecord.get();
|
const CSMWorld::Info& journalInfo = record->get();
|
||||||
|
|
||||||
totalInfoCount += 1;
|
totalInfoCount += 1;
|
||||||
|
|
||||||
|
@ -69,7 +65,7 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip "Base" records (setting!)
|
// Skip "Base" records (setting!)
|
||||||
if (mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly)
|
if (mIgnoreBaseRecords && record->mState == CSMWorld::RecordBase::State_BaseOnly)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (journalInfo.mResponse.empty())
|
if (journalInfo.mResponse.empty())
|
||||||
|
|
|
@ -55,6 +55,41 @@
|
||||||
#include "resourcesmanager.hpp"
|
#include "resourcesmanager.hpp"
|
||||||
#include "resourcetable.hpp"
|
#include "resourcetable.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void removeDialogueInfos(const ESM::RefId& dialogueId, const CSMWorld::InfosByTopic& infosByTopic,
|
||||||
|
CSMWorld::InfoCollection& infoCollection)
|
||||||
|
{
|
||||||
|
const auto topicInfos = infosByTopic.find(dialogueId);
|
||||||
|
|
||||||
|
if (topicInfos == infosByTopic.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<int> erasedRecords;
|
||||||
|
|
||||||
|
for (const ESM::RefId& id : topicInfos->second)
|
||||||
|
{
|
||||||
|
const CSMWorld::Record<CSMWorld::Info>& record = infoCollection.getRecord(id);
|
||||||
|
|
||||||
|
if (record.mState == CSMWorld::RecordBase::State_ModifiedOnly)
|
||||||
|
{
|
||||||
|
erasedRecords.push_back(infoCollection.searchId(record.get().mId));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deletedRecord = std::make_unique<CSMWorld::Record<CSMWorld::Info>>(record);
|
||||||
|
deletedRecord->mState = CSMWorld::RecordBase::State_Deleted;
|
||||||
|
infoCollection.setRecord(infoCollection.searchId(record.get().mId), std::move(deletedRecord));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!erasedRecords.empty())
|
||||||
|
{
|
||||||
|
infoCollection.removeRows(erasedRecords.back(), 1);
|
||||||
|
erasedRecords.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CSMWorld::Data::addModel(QAbstractItemModel* model, UniversalId::Type type, bool update)
|
void CSMWorld::Data::addModel(QAbstractItemModel* model, UniversalId::Type type, bool update)
|
||||||
{
|
{
|
||||||
mModels.push_back(model);
|
mModels.push_back(model);
|
||||||
|
@ -1254,11 +1289,11 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages)
|
||||||
|
|
||||||
if (mJournals.tryDelete(recordIdString))
|
if (mJournals.tryDelete(recordIdString))
|
||||||
{
|
{
|
||||||
mJournalInfos.removeDialogueInfos(recordIdString);
|
removeDialogueInfos(record.mId, mJournalInfosByTopic, mJournalInfos);
|
||||||
}
|
}
|
||||||
else if (mTopics.tryDelete(recordIdString))
|
else if (mTopics.tryDelete(recordIdString))
|
||||||
{
|
{
|
||||||
mTopicInfos.removeDialogueInfos(recordIdString);
|
removeDialogueInfos(record.mId, mTopicInfosByTopic, mTopicInfos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1296,9 +1331,9 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDialogue->mType == ESM::Dialogue::Journal)
|
if (mDialogue->mType == ESM::Dialogue::Journal)
|
||||||
mJournalInfos.load(*mReader, mBase, *mDialogue);
|
mJournalInfos.load(*mReader, mBase, *mDialogue, mJournalInfosByTopic);
|
||||||
else
|
else
|
||||||
mTopicInfos.load(*mReader, mBase, *mDialogue);
|
mTopicInfos.load(*mReader, mBase, *mDialogue, mTopicInfosByTopic);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,9 @@ namespace CSMWorld
|
||||||
|
|
||||||
std::vector<std::shared_ptr<ESM::ESMReader>> mReaders;
|
std::vector<std::shared_ptr<ESM::ESMReader>> mReaders;
|
||||||
|
|
||||||
|
CSMWorld::InfosByTopic mJournalInfosByTopic;
|
||||||
|
CSMWorld::InfosByTopic mTopicInfosByTopic;
|
||||||
|
|
||||||
// not implemented
|
// not implemented
|
||||||
Data(const Data&);
|
Data(const Data&);
|
||||||
Data& operator=(const Data&);
|
Data& operator=(const Data&);
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace CSMWorld
|
||||||
struct Info : public ESM::DialInfo
|
struct Info : public ESM::DialInfo
|
||||||
{
|
{
|
||||||
ESM::RefId mTopicId;
|
ESM::RefId mTopicId;
|
||||||
|
ESM::RefId mOriginalId;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,80 +1,16 @@
|
||||||
#include "infocollection.hpp"
|
#include "infocollection.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <memory>
|
||||||
#include <cassert>
|
#include <string>
|
||||||
#include <iterator>
|
#include <utility>
|
||||||
#include <limits.h>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include <apps/opencs/model/world/collection.hpp>
|
|
||||||
#include <apps/opencs/model/world/info.hpp>
|
|
||||||
#include <components/misc/strings/lower.hpp>
|
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/esm3/loaddial.hpp>
|
#include <components/esm3/loaddial.hpp>
|
||||||
#include <components/misc/strings/algorithm.hpp>
|
|
||||||
|
|
||||||
namespace CSMWorld
|
#include "collection.hpp"
|
||||||
{
|
#include "info.hpp"
|
||||||
template <>
|
|
||||||
void Collection<Info, IdAccessor<Info>>::removeRows(int index, int count)
|
|
||||||
{
|
|
||||||
mRecords.erase(mRecords.begin() + index, mRecords.begin() + index + count);
|
|
||||||
|
|
||||||
// index map is updated in InfoCollection::removeRows()
|
bool CSMWorld::InfoCollection::load(const Info& record, bool base)
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void Collection<Info, IdAccessor<Info>>::insertRecord(
|
|
||||||
std::unique_ptr<RecordBase> record, int index, UniversalId::Type type)
|
|
||||||
{
|
|
||||||
int size = static_cast<int>(mRecords.size());
|
|
||||||
if (index < 0 || index > size)
|
|
||||||
throw std::runtime_error("index out of range");
|
|
||||||
|
|
||||||
std::unique_ptr<Record<Info>> record2(static_cast<Record<Info>*>(record.release()));
|
|
||||||
|
|
||||||
if (index == size)
|
|
||||||
mRecords.push_back(std::move(record2));
|
|
||||||
else
|
|
||||||
mRecords.insert(mRecords.begin() + index, std::move(record2));
|
|
||||||
|
|
||||||
// index map is updated in InfoCollection::insertRecord()
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
bool Collection<Info, IdAccessor<Info>>::reorderRowsImp(int baseIndex, const std::vector<int>& newOrder)
|
|
||||||
{
|
|
||||||
if (!newOrder.empty())
|
|
||||||
{
|
|
||||||
int size = static_cast<int>(newOrder.size());
|
|
||||||
|
|
||||||
// check that all indices are present
|
|
||||||
std::vector<int> test(newOrder);
|
|
||||||
std::sort(test.begin(), test.end());
|
|
||||||
if (*test.begin() != 0 || *--test.end() != size - 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// reorder records
|
|
||||||
std::vector<std::unique_ptr<Record<Info>>> buffer(size);
|
|
||||||
|
|
||||||
// FIXME: BUG: undo does not remove modified flag
|
|
||||||
for (int i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
buffer[newOrder[i]] = std::move(mRecords[baseIndex + i]);
|
|
||||||
if (buffer[newOrder[i]])
|
|
||||||
buffer[newOrder[i]]->setModified(buffer[newOrder[i]]->get());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::move(buffer.begin(), buffer.end(), mRecords.begin() + baseIndex);
|
|
||||||
|
|
||||||
// index map is updated in InfoCollection::reorderRows()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMWorld::InfoCollection::load(const Info& record, bool base)
|
|
||||||
{
|
{
|
||||||
int index = searchId(record.mId.getRefIdString());
|
int index = searchId(record.mId.getRefIdString());
|
||||||
|
|
||||||
|
@ -86,6 +22,8 @@ void CSMWorld::InfoCollection::load(const Info& record, bool base)
|
||||||
(base ? record2->mBase : record2->mModified) = record;
|
(base ? record2->mBase : record2->mModified) = record;
|
||||||
|
|
||||||
appendRecord(std::move(record2));
|
appendRecord(std::move(record2));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -98,107 +36,13 @@ void CSMWorld::InfoCollection::load(const Info& record, bool base)
|
||||||
record2->setModified(record);
|
record2->setModified(record);
|
||||||
|
|
||||||
setRecord(index, std::move(record2));
|
setRecord(index, std::move(record2));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMWorld::InfoCollection::getInfoIndex(std::string_view id, std::string_view topic) const
|
|
||||||
{
|
|
||||||
// find the topic first
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, int>>>::const_iterator iter
|
|
||||||
= mInfoIndex.find(Misc::StringUtils::lowerCase(topic));
|
|
||||||
|
|
||||||
if (iter == mInfoIndex.end())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// brute force loop
|
|
||||||
for (std::vector<std::pair<std::string, int>>::const_iterator it = iter->second.begin(); it != iter->second.end();
|
|
||||||
++it)
|
|
||||||
{
|
|
||||||
if (Misc::StringUtils::ciEqual(it->first, id))
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calling insertRecord() using index from getInsertIndex() needs to take into account of
|
|
||||||
// prev/next records; an example is deleting a record then undo
|
|
||||||
int CSMWorld::InfoCollection::getInsertIndex(const std::string& id, UniversalId::Type type, RecordBase* record) const
|
|
||||||
{
|
|
||||||
if (record == nullptr)
|
|
||||||
{
|
|
||||||
std::string::size_type separator = id.find_last_of('#');
|
|
||||||
|
|
||||||
if (separator == std::string::npos)
|
|
||||||
throw std::runtime_error("invalid info ID: " + id);
|
|
||||||
|
|
||||||
std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange(id.substr(0, separator));
|
|
||||||
|
|
||||||
if (range.first == range.second)
|
|
||||||
return Collection<Info, IdAccessor<Info>>::getAppendIndex(ESM::RefId::stringRefId(id), type);
|
|
||||||
|
|
||||||
return std::distance(getRecords().begin(), range.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = -1;
|
|
||||||
|
|
||||||
const Info& info = static_cast<Record<Info>*>(record)->get();
|
|
||||||
const std::string& topic = info.mTopicId.getRefIdString();
|
|
||||||
|
|
||||||
// if the record has a prev, find its index value
|
|
||||||
if (!info.mPrev.empty())
|
|
||||||
{
|
|
||||||
index = getInfoIndex(info.mPrev.getRefIdString(), topic);
|
|
||||||
|
|
||||||
if (index != -1)
|
|
||||||
++index; // if prev exists, set current index to one above prev
|
|
||||||
}
|
|
||||||
|
|
||||||
// if prev doesn't exist or not found and the record has a next, find its index value
|
|
||||||
if (index == -1 && !info.mNext.empty())
|
|
||||||
{
|
|
||||||
// if next exists, use its index as the current index
|
|
||||||
index = getInfoIndex(info.mNext.getRefIdString(), topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if next doesn't exist or not found (i.e. neither exist yet) then start a new one
|
|
||||||
if (index == -1)
|
|
||||||
{
|
|
||||||
Range range = getTopicRange(topic); // getTopicRange converts topic to lower case first
|
|
||||||
|
|
||||||
index = std::distance(getRecords().begin(), range.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CSMWorld::InfoCollection::reorderRows(int baseIndex, const std::vector<int>& newOrder)
|
|
||||||
{
|
|
||||||
// check if the range is valid
|
|
||||||
int lastIndex = baseIndex + newOrder.size() - 1;
|
|
||||||
|
|
||||||
if (lastIndex >= getSize())
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
// Check that topics match
|
|
||||||
if (!(getRecord(baseIndex).get().mTopicId == getRecord(lastIndex).get().mTopicId))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// reorder
|
|
||||||
if (!Collection<Info, IdAccessor<Info>>::reorderRowsImp(baseIndex, newOrder))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// adjust index
|
|
||||||
int size = static_cast<int>(newOrder.size());
|
|
||||||
for (auto& [hash, infos] : mInfoIndex)
|
|
||||||
for (auto& [a, b] : infos)
|
|
||||||
if (b >= baseIndex && b < baseIndex + size)
|
|
||||||
b = newOrder.at(b - baseIndex) + baseIndex;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMWorld::InfoCollection::load(ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue)
|
void CSMWorld::InfoCollection::load(
|
||||||
|
ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfosByTopic& infosByTopic)
|
||||||
{
|
{
|
||||||
Info info;
|
Info info;
|
||||||
bool isDeleted = false;
|
bool isDeleted = false;
|
||||||
|
@ -230,174 +74,19 @@ void CSMWorld::InfoCollection::load(ESM::ESMReader& reader, bool base, const ESM
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
info.mTopicId = dialogue.mId;
|
info.mTopicId = dialogue.mId;
|
||||||
|
info.mOriginalId = info.mId;
|
||||||
info.mId = ESM::RefId::stringRefId(id);
|
info.mId = ESM::RefId::stringRefId(id);
|
||||||
load(info, base);
|
|
||||||
|
if (load(info, base))
|
||||||
|
infosByTopic[dialogue.mId].push_back(info.mId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange(const std::string& topic) const
|
std::vector<CSMWorld::Record<CSMWorld::Info>*> CSMWorld::InfoCollection::getTopicInfos(const ESM::RefId& topic) const
|
||||||
{
|
{
|
||||||
std::string lowerTopic = Misc::StringUtils::lowerCase(topic);
|
std::vector<CSMWorld::Record<CSMWorld::Info>*> result;
|
||||||
|
for (const std::unique_ptr<Record<Info>>& record : getRecords())
|
||||||
// find the topic
|
if (record->mBase.mTopicId == topic)
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, int>>>::const_iterator iter
|
result.push_back(record.get());
|
||||||
= mInfoIndex.find(lowerTopic);
|
return result;
|
||||||
|
|
||||||
if (iter == mInfoIndex.end())
|
|
||||||
return Range(getRecords().end(), getRecords().end());
|
|
||||||
|
|
||||||
// topic found, find the starting index
|
|
||||||
int low = INT_MAX;
|
|
||||||
for (std::vector<std::pair<std::string, int>>::const_iterator it = iter->second.begin(); it != iter->second.end();
|
|
||||||
++it)
|
|
||||||
{
|
|
||||||
low = std::min(low, it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
RecordConstIterator begin = getRecords().begin() + low;
|
|
||||||
|
|
||||||
// Find end (one past the range)
|
|
||||||
RecordConstIterator end = begin + iter->second.size();
|
|
||||||
|
|
||||||
assert(static_cast<size_t>(std::distance(begin, end)) == iter->second.size());
|
|
||||||
|
|
||||||
return Range(begin, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId)
|
|
||||||
{
|
|
||||||
std::vector<int> erasedRecords;
|
|
||||||
|
|
||||||
Range range = getTopicRange(dialogueId); // getTopicRange converts dialogueId to lower case first
|
|
||||||
|
|
||||||
for (; range.first != range.second; ++range.first)
|
|
||||||
{
|
|
||||||
const Record<Info>& record = **range.first;
|
|
||||||
|
|
||||||
if ((ESM::RefId::stringRefId(dialogueId) == record.get().mTopicId))
|
|
||||||
{
|
|
||||||
if (record.mState == RecordBase::State_ModifiedOnly)
|
|
||||||
{
|
|
||||||
erasedRecords.push_back(range.first - getRecords().begin());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto record2 = std::make_unique<Record<Info>>(record);
|
|
||||||
record2->mState = RecordBase::State_Deleted;
|
|
||||||
setRecord(range.first - getRecords().begin(), std::move(record2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!erasedRecords.empty())
|
|
||||||
{
|
|
||||||
removeRows(erasedRecords.back(), 1);
|
|
||||||
erasedRecords.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: removing a record should adjust prev/next and mark those records as modified
|
|
||||||
// accordingly (also consider undo)
|
|
||||||
void CSMWorld::InfoCollection::removeRows(int index, int count)
|
|
||||||
{
|
|
||||||
Collection<Info, IdAccessor<Info>>::removeRows(index, count); // erase records only
|
|
||||||
|
|
||||||
for (std::unordered_map<std::string, std::vector<std::pair<std::string, int>>>::iterator iter = mInfoIndex.begin();
|
|
||||||
iter != mInfoIndex.end();)
|
|
||||||
{
|
|
||||||
for (std::vector<std::pair<std::string, int>>::iterator it = iter->second.begin(); it != iter->second.end();)
|
|
||||||
{
|
|
||||||
if (it->second >= index)
|
|
||||||
{
|
|
||||||
if (it->second >= index + count)
|
|
||||||
{
|
|
||||||
it->second -= count;
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
it = iter->second.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for an empty vector
|
|
||||||
if (iter->second.empty())
|
|
||||||
mInfoIndex.erase(iter++);
|
|
||||||
else
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMWorld::InfoCollection::appendBlankRecord(const ESM::RefId& id, UniversalId::Type type)
|
|
||||||
{
|
|
||||||
auto record2 = std::make_unique<Record<Info>>();
|
|
||||||
|
|
||||||
record2->mState = Record<Info>::State_ModifiedOnly;
|
|
||||||
record2->mModified.blank();
|
|
||||||
|
|
||||||
record2->get().mId = id;
|
|
||||||
|
|
||||||
insertRecord(std::move(record2), getInsertIndex(id.getRefIdString(), type, nullptr),
|
|
||||||
type); // call InfoCollection::insertRecord()
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMWorld::InfoCollection::searchId(std::string_view id) const
|
|
||||||
{
|
|
||||||
std::string::size_type separator = id.find_last_of('#');
|
|
||||||
|
|
||||||
if (separator == std::string::npos)
|
|
||||||
throw std::runtime_error("invalid info ID: " + std::string(id));
|
|
||||||
|
|
||||||
return getInfoIndex(id.substr(separator + 1), id.substr(0, separator));
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMWorld::InfoCollection::appendRecord(std::unique_ptr<RecordBase> record, UniversalId::Type type)
|
|
||||||
{
|
|
||||||
int index
|
|
||||||
= getInsertIndex(static_cast<Record<Info>*>(record.get())->get().mId.getRefIdString(), type, record.get());
|
|
||||||
|
|
||||||
insertRecord(std::move(record), index, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMWorld::InfoCollection::insertRecord(std::unique_ptr<RecordBase> record, int index, UniversalId::Type type)
|
|
||||||
{
|
|
||||||
int size = static_cast<int>(getRecords().size());
|
|
||||||
|
|
||||||
std::string id = static_cast<Record<Info>*>(record.get())->get().mId.getRefIdString();
|
|
||||||
std::string::size_type separator = id.find_last_of('#');
|
|
||||||
|
|
||||||
if (separator == std::string::npos)
|
|
||||||
throw std::runtime_error("invalid info ID: " + id);
|
|
||||||
|
|
||||||
Collection<Info, IdAccessor<Info>>::insertRecord(std::move(record), index, type); // add records only
|
|
||||||
|
|
||||||
// adjust index
|
|
||||||
if (index < size - 1)
|
|
||||||
{
|
|
||||||
for (std::unordered_map<std::string, std::vector<std::pair<std::string, int>>>::iterator iter
|
|
||||||
= mInfoIndex.begin();
|
|
||||||
iter != mInfoIndex.end(); ++iter)
|
|
||||||
{
|
|
||||||
for (std::vector<std::pair<std::string, int>>::iterator it = iter->second.begin(); it != iter->second.end();
|
|
||||||
++it)
|
|
||||||
{
|
|
||||||
if (it->second >= index)
|
|
||||||
++(it->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get iterator for existing topic or a new topic
|
|
||||||
std::string lowerId = Misc::StringUtils::lowerCase(id);
|
|
||||||
std::pair<std::unordered_map<std::string, std::vector<std::pair<std::string, int>>>::iterator, bool> res
|
|
||||||
= mInfoIndex.insert(
|
|
||||||
std::make_pair(lowerId.substr(0, separator), std::vector<std::pair<std::string, int>>())); // empty vector
|
|
||||||
|
|
||||||
// insert info and index
|
|
||||||
res.first->second.push_back(std::make_pair(lowerId.substr(separator + 1), index));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
#ifndef CSM_WOLRD_INFOCOLLECTION_H
|
#ifndef CSM_WOLRD_INFOCOLLECTION_H
|
||||||
#define CSM_WOLRD_INFOCOLLECTION_H
|
#define CSM_WOLRD_INFOCOLLECTION_H
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <apps/opencs/model/world/record.hpp>
|
|
||||||
#include <apps/opencs/model/world/universalid.hpp>
|
|
||||||
|
|
||||||
#include "collection.hpp"
|
#include "collection.hpp"
|
||||||
#include "info.hpp"
|
#include "info.hpp"
|
||||||
|
#include "record.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -23,77 +17,17 @@ namespace ESM
|
||||||
|
|
||||||
namespace CSMWorld
|
namespace CSMWorld
|
||||||
{
|
{
|
||||||
template <>
|
using InfosByTopic = std::unordered_map<ESM::RefId, std::vector<ESM::RefId>>;
|
||||||
void Collection<Info, IdAccessor<Info>>::removeRows(int index, int count);
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void Collection<Info, IdAccessor<Info>>::insertRecord(
|
|
||||||
std::unique_ptr<RecordBase> record, int index, UniversalId::Type type);
|
|
||||||
|
|
||||||
template <>
|
|
||||||
bool Collection<Info, IdAccessor<Info>>::reorderRowsImp(int baseIndex, const std::vector<int>& newOrder);
|
|
||||||
|
|
||||||
class InfoCollection : public Collection<Info, IdAccessor<Info>>
|
class InfoCollection : public Collection<Info, IdAccessor<Info>>
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
typedef std::vector<std::unique_ptr<Record<Info>>>::const_iterator RecordConstIterator;
|
|
||||||
typedef std::pair<RecordConstIterator, RecordConstIterator> Range;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The general strategy is to keep the records in Collection kept in order (within
|
bool load(const Info& record, bool base);
|
||||||
// a topic group) while the index lookup maps are not ordered. It is assumed that
|
|
||||||
// each topic has a small number of infos, which allows the use of vectors for
|
|
||||||
// iterating through them without too much penalty.
|
|
||||||
//
|
|
||||||
// NOTE: topic string as well as id string are stored in lower case.
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, int>>> mInfoIndex;
|
|
||||||
|
|
||||||
void load(const Info& record, bool base);
|
|
||||||
|
|
||||||
int getInfoIndex(std::string_view id, std::string_view topic) const;
|
|
||||||
///< Return index for record \a id or -1 (if not present; deleted records are considered)
|
|
||||||
///
|
|
||||||
/// \param id info ID without topic prefix
|
|
||||||
//
|
|
||||||
/// \attention id and topic are assumed to be in lower case
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
int getInsertIndex(const std::string& id, UniversalId::Type type = UniversalId::Type_None,
|
void load(ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfosByTopic& infosByTopic);
|
||||||
RecordBase* record = nullptr) const override;
|
|
||||||
///< \param type Will be ignored, unless the collection supports multiple record types
|
|
||||||
///
|
|
||||||
/// Works like getAppendIndex unless an overloaded method uses the record pointer
|
|
||||||
/// to get additional info about the record that results in an alternative index.
|
|
||||||
|
|
||||||
int getAppendIndex(const ESM::RefId& id, UniversalId::Type type) const override
|
std::vector<Record<Info>*> getTopicInfos(const ESM::RefId& topic) const;
|
||||||
{
|
|
||||||
return getInsertIndex(id.getRefIdString(), type);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool reorderRows(int baseIndex, const std::vector<int>& newOrder) override;
|
|
||||||
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
|
|
||||||
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
|
|
||||||
///
|
|
||||||
/// \return Success?
|
|
||||||
|
|
||||||
void load(ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue);
|
|
||||||
|
|
||||||
Range getTopicRange(const std::string& topic) const;
|
|
||||||
///< Return iterators that point to the beginning and past the end of the range for
|
|
||||||
/// the given topic.
|
|
||||||
|
|
||||||
void removeDialogueInfos(const std::string& dialogueId);
|
|
||||||
|
|
||||||
void removeRows(int index, int count) override;
|
|
||||||
|
|
||||||
void appendBlankRecord(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) override;
|
|
||||||
|
|
||||||
int searchId(std::string_view id) const override;
|
|
||||||
|
|
||||||
void appendRecord(std::unique_ptr<RecordBase> record, UniversalId::Type type = UniversalId::Type_None) override;
|
|
||||||
|
|
||||||
void insertRecord(
|
|
||||||
std::unique_ptr<RecordBase> record, int index, UniversalId::Type type = UniversalId::Type_None) override;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue