mirror of https://github.com/OpenMW/openmw.git
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.depth-refraction
parent
899c302b14
commit
e892c62b10
@ -1,91 +1,141 @@
|
||||
#include "infocollection.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/loaddial.hpp>
|
||||
#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"
|
||||
|
||||
bool CSMWorld::InfoCollection::load(const Info& record, bool base)
|
||||
namespace CSMWorld
|
||||
{
|
||||
const int index = searchId(record.mId);
|
||||
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 record2 = std::make_unique<Record<Info>>();
|
||||
record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
|
||||
(base ? record2->mBase : record2->mModified) = record;
|
||||
auto record = std::make_unique<Record<Info>>();
|
||||
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
|
||||
(base ? record->mBase : record->mModified) = value;
|
||||
|
||||
appendRecord(std::move(record2));
|
||||
|
||||
return true;
|
||||
insertRecord(std::move(record), getSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
// old record
|
||||
auto record2 = std::make_unique<Record<Info>>(getRecord(index));
|
||||
auto record = std::make_unique<Record<Info>>(getRecord(index));
|
||||
|
||||
if (base)
|
||||
record2->mBase = record;
|
||||
record->mBase = value;
|
||||
else
|
||||
record2->setModified(record);
|
||||
|
||||
setRecord(index, std::move(record2));
|
||||
record->setModified(value);
|
||||
|
||||
return false;
|
||||
setRecord(index, std::move(record));
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::InfoCollection::load(
|
||||
ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfosByTopic& infosByTopic)
|
||||
ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue, InfoOrderByTopic& infoOrders)
|
||||
{
|
||||
Info info;
|
||||
bool isDeleted = false;
|
||||
|
||||
info.load(reader, isDeleted);
|
||||
const ESM::RefId id = ESM::RefId::stringRefId(dialogue.mId.getRefIdString() + "#" + info.mId.getRefIdString());
|
||||
|
||||
const ESM::RefId id = makeCompositeRefId(dialogue.mId, info.mId);
|
||||
|
||||
if (isDeleted)
|
||||
{
|
||||
int index = searchId(id);
|
||||
const int index = searchId(id);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
// deleting a record that does not exist
|
||||
// ignore it for now
|
||||
/// \todo report the problem to the user
|
||||
Log(Debug::Warning) << "Trying to delete absent info \"" << info.mId << "\" from topic \"" << dialogue.mId
|
||||
<< "\"";
|
||||
return;
|
||||
}
|
||||
else if (base)
|
||||
|
||||
if (base)
|
||||
{
|
||||
infoOrders.at(dialogue.mId).removeInfo(id);
|
||||
removeRows(index, 1);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto record = std::make_unique<Record<Info>>(getRecord(index));
|
||||
record->mState = RecordBase::State_Deleted;
|
||||
setRecord(index, std::move(record));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info.mTopicId = dialogue.mId;
|
||||
info.mOriginalId = info.mId;
|
||||
info.mId = id;
|
||||
|
||||
if (load(info, base))
|
||||
infosByTopic[dialogue.mId].push_back(info.mId);
|
||||
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->mBase.mTopicId].push_back(record.get());
|
||||
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);
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
#include "infoorder.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
const std::list<OrderedInfo>* InfoOrder::findInfosByTopic(const ESM::RefId& refId) const
|
||||
{
|
||||
const auto it = mOrderByTopic.find(refId);
|
||||
if (it == mOrderByTopic.end())
|
||||
return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
void InfoOrder::insertInfo(const OrderedInfo& info)
|
||||
{
|
||||
auto it = mInfoPositions.find(info.mId);
|
||||
|
||||
if (it != mInfoPositions.end() && it->second->mPrev == info.mPrev)
|
||||
{
|
||||
it->second->mNext = info.mNext;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& infos = mOrderByTopic[info.mTopicId];
|
||||
|
||||
if (it == mInfoPositions.end())
|
||||
it = mInfoPositions.emplace(info.mId, infos.end()).first;
|
||||
|
||||
std::list<OrderedInfo>::iterator& position = it->second;
|
||||
|
||||
const auto insertOrSplice = [&](std::list<OrderedInfo>::const_iterator before) {
|
||||
if (position == infos.end())
|
||||
position = infos.insert(before, info);
|
||||
else
|
||||
infos.splice(before, infos, position);
|
||||
};
|
||||
|
||||
if (info.mPrev.empty())
|
||||
{
|
||||
insertOrSplice(infos.begin());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto prevIt = mInfoPositions.find(info.mPrev);
|
||||
if (prevIt != mInfoPositions.end())
|
||||
{
|
||||
insertOrSplice(std::next(prevIt->second));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto nextIt = mInfoPositions.find(info.mNext);
|
||||
if (nextIt != mInfoPositions.end())
|
||||
{
|
||||
insertOrSplice(nextIt->second);
|
||||
return;
|
||||
}
|
||||
|
||||
insertOrSplice(infos.end());
|
||||
}
|
||||
|
||||
void InfoOrder::removeInfo(const ESM::RefId& infoRefId)
|
||||
{
|
||||
const auto it = mInfoPositions.find(infoRefId);
|
||||
|
||||
if (it == mInfoPositions.end())
|
||||
return;
|
||||
|
||||
const auto topicIt = mOrderByTopic.find(it->second->mTopicId);
|
||||
|
||||
if (topicIt != mOrderByTopic.end())
|
||||
topicIt->second.erase(it->second);
|
||||
|
||||
mInfoPositions.erase(it);
|
||||
}
|
||||
|
||||
void InfoOrder::removeTopic(const ESM::RefId& topicRefId)
|
||||
{
|
||||
const auto it = mOrderByTopic.find(topicRefId);
|
||||
|
||||
if (it == mOrderByTopic.end())
|
||||
return;
|
||||
|
||||
for (const OrderedInfo& info : it->second)
|
||||
mInfoPositions.erase(info.mId);
|
||||
|
||||
mOrderByTopic.erase(it);
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
#ifndef OPENMW_COMPONENTS_ESM3_INFOORDER_H
|
||||
#define OPENMW_COMPONENTS_ESM3_INFOORDER_H
|
||||
|
||||
#include "components/esm/refid.hpp"
|
||||
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
template <class T>
|
||||
class InfoOrder
|
||||
{
|
||||
public:
|
||||
const std::list<T>& getOrderedInfo() const { return mOrderedInfo; }
|
||||
|
||||
template <class V>
|
||||
void insertInfo(V&& value, bool deleted)
|
||||
{
|
||||
static_assert(std::is_same_v<std::decay_t<V>, T>);
|
||||
|
||||
auto it = mInfoPositions.find(value.mId);
|
||||
|
||||
if (it != mInfoPositions.end() && it->second.mPosition->mPrev == value.mPrev)
|
||||
{
|
||||
*it->second.mPosition = std::forward<V>(value);
|
||||
it->second.mDeleted = deleted;
|
||||
return;
|
||||
}
|
||||
|
||||
if (it == mInfoPositions.end())
|
||||
it = mInfoPositions.emplace(value.mId, Item{ .mPosition = mOrderedInfo.end(), .mDeleted = deleted })
|
||||
.first;
|
||||
|
||||
Item& item = it->second;
|
||||
|
||||
const auto insertOrSplice = [&](typename std::list<T>::const_iterator before) {
|
||||
if (item.mPosition == mOrderedInfo.end())
|
||||
item.mPosition = mOrderedInfo.insert(before, std::forward<V>(value));
|
||||
else
|
||||
mOrderedInfo.splice(before, mOrderedInfo, item.mPosition);
|
||||
};
|
||||
|
||||
if (value.mPrev.empty())
|
||||
{
|
||||
insertOrSplice(mOrderedInfo.begin());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto prevIt = mInfoPositions.find(value.mPrev);
|
||||
if (prevIt != mInfoPositions.end())
|
||||
{
|
||||
insertOrSplice(std::next(prevIt->second.mPosition));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto nextIt = mInfoPositions.find(value.mNext);
|
||||
if (nextIt != mInfoPositions.end())
|
||||
{
|
||||
insertOrSplice(nextIt->second.mPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
insertOrSplice(mOrderedInfo.end());
|
||||
}
|
||||
|
||||
void removeInfo(const RefId& infoRefId)
|
||||
{
|
||||
const auto it = mInfoPositions.find(infoRefId);
|
||||
|
||||
if (it == mInfoPositions.end())
|
||||
return;
|
||||
|
||||
mOrderedInfo.erase(it->second.mPosition);
|
||||
mInfoPositions.erase(it);
|
||||
}
|
||||
|
||||
void removeDeleted()
|
||||
{
|
||||
for (auto it = mInfoPositions.begin(); it != mInfoPositions.end();)
|
||||
{
|
||||
if (!it->second.mDeleted)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
mOrderedInfo.erase(it->second.mPosition);
|
||||
it = mInfoPositions.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void extractOrderedInfo(std::list<T>& info)
|
||||
{
|
||||
info = mOrderedInfo;
|
||||
mInfoPositions.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Item
|
||||
{
|
||||
typename std::list<T>::iterator mPosition;
|
||||
bool mDeleted = false;
|
||||
};
|
||||
|
||||
std::list<T> mOrderedInfo;
|
||||
std::unordered_map<RefId, Item> mInfoPositions;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue