1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-22 13:53:55 +00:00
openmw/apps/opencs/model/world/infocollection.cpp
elsid e892c62b10
Fix loading, inserting and moving topic info records
Topic info records need to have specific order defined via mNext and mPrev
fields (next and previous records). When loading multiple files a record may be
inserted into middle of the topic but neighborhood records may not be aware of
it. Having the order it's possible to move the records within one topic.

Sort the record once after loading all content files but preserve the order for
all other operations. Use std::map to group info ids by topic to make sure the
topics order is stable. Keep order within a topic for info ids on loading new
records. Use this order later for sorting the records.
2023-03-13 21:57:38 +01:00

141 lines
3.9 KiB
C++

#include "infocollection.hpp"
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include "components/debug/debuglog.hpp"
#include "components/esm3/infoorder.hpp"
#include "components/esm3/loaddial.hpp"
#include "components/esm3/loadinfo.hpp"
#include "collection.hpp"
#include "info.hpp"
namespace CSMWorld
{
namespace
{
ESM::RefId makeCompositeRefId(const ESM::RefId& topicId, const ESM::RefId& infoId)
{
return ESM::RefId::stringRefId(topicId.getRefIdString() + '#' + infoId.getRefIdString());
}
std::string_view getInfoTopicId(const ESM::RefId& infoId)
{
return parseInfoRefId(infoId).first;
}
}
}
void CSMWorld::InfoCollection::load(const Info& value, bool base)
{
const int index = searchId(value.mId);
if (index == -1)
{
// new record
auto record = std::make_unique<Record<Info>>();
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record->mBase : record->mModified) = value;
insertRecord(std::move(record), getSize());
}
else
{
// old record
auto record = std::make_unique<Record<Info>>(getRecord(index));
if (base)
record->mBase = value;
else
record->setModified(value);
setRecord(index, std::move(record));
}
}
void CSMWorld::InfoCollection::load(
ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfoOrderByTopic& infoOrders)
{
Info info;
bool isDeleted = false;
info.load(reader, isDeleted);
const ESM::RefId id = makeCompositeRefId(dialogue.mId, info.mId);
if (isDeleted)
{
const int index = searchId(id);
if (index == -1)
{
Log(Debug::Warning) << "Trying to delete absent info \"" << info.mId << "\" from topic \"" << dialogue.mId
<< "\"";
return;
}
if (base)
{
infoOrders.at(dialogue.mId).removeInfo(id);
removeRows(index, 1);
return;
}
auto record = std::make_unique<Record<Info>>(getRecord(index));
record->mState = RecordBase::State_Deleted;
setRecord(index, std::move(record));
return;
}
info.mTopicId = dialogue.mId;
info.mOriginalId = info.mId;
info.mId = id;
load(info, base);
infoOrders[dialogue.mId].insertInfo(OrderedInfo(info), isDeleted);
}
void CSMWorld::InfoCollection::sort(const InfoOrderByTopic& infoOrders)
{
std::vector<int> order;
order.reserve(getSize());
for (const auto& [topicId, infoOrder] : infoOrders)
for (const OrderedInfo& info : infoOrder.getOrderedInfo())
order.push_back(getIndex(makeCompositeRefId(topicId, info.mId)));
reorderRowsImp(order);
}
CSMWorld::InfosRecordPtrByTopic CSMWorld::InfoCollection::getInfosByTopic() const
{
InfosRecordPtrByTopic result;
for (const std::unique_ptr<Record<Info>>& record : getRecords())
result[record->get().mTopicId].push_back(record.get());
return result;
}
int CSMWorld::InfoCollection::getAppendIndex(const ESM::RefId& id, UniversalId::Type /*type*/) const
{
const auto lessByTopicId
= [](std::string_view lhs, const std::unique_ptr<Record<Info>>& rhs) { return lhs < rhs->get().mTopicId; };
const auto it = std::upper_bound(getRecords().begin(), getRecords().end(), getInfoTopicId(id), lessByTopicId);
return static_cast<int>(it - getRecords().begin());
}
bool CSMWorld::InfoCollection::reorderRows(int baseIndex, const std::vector<int>& newOrder)
{
const int lastIndex = baseIndex + static_cast<int>(newOrder.size()) - 1;
if (lastIndex >= getSize())
return false;
if (getRecord(baseIndex).get().mTopicId != getRecord(lastIndex).get().mTopicId)
return false;
return reorderRowsImp(baseIndex, newOrder);
}