1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-01 09:15:36 +00:00
openmw/apps/opencs/model/world/infocollection.cpp
fteppe 20da0892ef openMW_test_suite compiles and runs
Slowly moving through the open-cs errors

Good progress in openCS

Very good progress on openCS

Getting closer with openCS

OpenCS compiles and runs! Didn't have time to test it all though

ix openMW

everything compiles on windows??

Fix gcc

Fix Clang
2022-12-27 19:15:55 +01:00

401 lines
13 KiB
C++

#include "infocollection.hpp"
#include <algorithm>
#include <cassert>
#include <iterator>
#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/esm3/loaddial.hpp>
#include <components/misc/strings/algorithm.hpp>
namespace CSMWorld
{
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()
}
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());
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;
appendRecord(std::move(record2));
}
else
{
// old record
auto record2 = std::make_unique<Record<Info>>(getRecord(index));
if (base)
record2->mBase = record;
else
record2->setModified(record);
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();
auto topic = info.mTopicId;
// if the record has a prev, find its index value
if (!info.mPrev.empty())
{
index = getInfoIndex(info.mPrev.getRefIdString(), topic.getRefIdString());
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.getRefIdString());
}
// 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.getRefIdString()); // 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;
// 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)
{
Info info;
bool isDeleted = false;
info.load(reader, isDeleted);
std::string id = dialogue.mId.getRefIdString() + "#" + info.mId.getRefIdString();
if (isDeleted)
{
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
}
else if (base)
{
removeRows(index, 1);
}
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.mId = ESM::RefId::stringRefId(id);
load(info, base);
}
}
CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange(const std::string& topic) const
{
std::string lowerTopic = Misc::StringUtils::lowerCase(topic);
// find the topic
std::unordered_map<std::string, std::vector<std::pair<std::string, int>>>::const_iterator iter
= mInfoIndex.find(lowerTopic);
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));
}