#ifndef CSM_WOLRD_COLLECTION_H #define CSM_WOLRD_COLLECTION_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionbase.hpp" #include "columnbase.hpp" #include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "record.hpp" #include "ref.hpp" namespace CSMWorld { inline std::pair parseInfoRefId(const ESM::RefId& infoId) { const auto separator = infoId.getRefIdString().find('#'); if (separator == std::string::npos) throw std::runtime_error("Invalid info id: " + infoId.getRefIdString()); const std::string_view view(infoId.getRefIdString()); return { view.substr(0, separator), view.substr(separator + 1) }; } template void setRecordId(const decltype(T::mId)& id, T& record) { record.mId = id; } inline void setRecordId(const ESM::RefId& id, Info& record) { record.mId = id; const auto [topicId, originalId] = parseInfoRefId(id); record.mTopicId = ESM::RefId::stringRefId(topicId); record.mOriginalId = ESM::RefId::stringRefId(originalId); } template auto getRecordId(const T& record) { return record.mId; } inline void setRecordId(const ESM::RefId& id, Land& record) { int x = 0; int y = 0; Land::parseUniqueRecordId(id.getRefIdString(), x, y); record.mX = x; record.mY = y; } inline ESM::RefId getRecordId(const Land& record) { return ESM::RefId::stringRefId(Land::createUniqueRecordId(record.mX, record.mY)); } inline void setRecordId(const ESM::RefId& id, LandTexture& record) { int plugin = 0; int index = 0; LandTexture::parseUniqueRecordId(id.getRefIdString(), plugin, index); record.mPluginIndex = plugin; record.mIndex = index; } inline ESM::RefId getRecordId(const LandTexture& record) { return ESM::RefId::stringRefId(LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex)); } /// \brief Single-type record collection template class Collection : public CollectionBase { public: typedef ESXRecordT ESXRecord; private: std::vector>> mRecords; std::map mIndex; std::vector*> mColumns; protected: const std::vector>>& getRecords() const; void reorderRowsImp(const std::vector& indexOrder); bool reorderRowsImp(int baseIndex, const std::vector& newOrder); ///< 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? int cloneRecordImp(const ESM::RefId& origin, const ESM::RefId& dest, UniversalId::Type type); ///< Returns the index of the clone. int touchRecordImp(const ESM::RefId& id); ///< Returns the index of the record on success, -1 on failure. public: Collection() = default; Collection(const Collection&) = delete; Collection& operator=(const Collection&) = delete; ~Collection() override; void add(const ESXRecordT& record); ///< Add a new record (modified) int getSize() const override; ESM::RefId getId(int index) const override; int getIndex(const ESM::RefId& id) const override; int getColumns() const override; QVariant getData(int index, int column) const override; void setData(int index, int column, const QVariant& data) override; const ColumnBase& getColumn(int column) const override; void merge(); ///< Merge modified into base. void purge(); ///< Remove records that are flagged as erased. void removeRows(int index, int count) override; void appendBlankRecord(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) override; ///< \param type Will be ignored, unless the collection supports multiple record types void cloneRecord( const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) override; bool touchRecord(const ESM::RefId& id) override; ///< Change the state of a record from base to modified, if it is not already. /// \return True if the record was changed. int searchId(const ESM::RefId& id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) void replace(int index, std::unique_ptr record) override; ///< If the record type does not match, an exception is thrown. /// /// \attention \a record must not change the ID. void appendRecord(std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; ///< If the record type does not match, an exception is thrown. ///< \param type Will be ignored, unless the collection supports multiple record types const Record& getRecord(const ESM::RefId& id) const override; const Record& getRecord(int index) const override; int getAppendIndex(const ESM::RefId& id, UniversalId::Type type = UniversalId::Type_None) const override; ///< \param type Will be ignored, unless the collection supports multiple record types std::vector getIds(bool listDeleted = true) const override; ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list virtual void insertRecord( std::unique_ptr record, int index, UniversalId::Type type = UniversalId::Type_None); ///< Insert record before index. /// /// If the record type does not match, an exception is thrown. /// /// If the index is invalid either generally (by being out of range) or for the particular /// record, an exception is thrown. bool reorderRows(int baseIndex, const std::vector& 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 addColumn(Column* column); void setRecord(int index, std::unique_ptr> record); ///< \attention This function must not change the ID. NestableColumn* getNestableColumn(int column) const; }; template const std::vector>>& Collection::getRecords() const { return mRecords; } template void Collection::reorderRowsImp(const std::vector& indexOrder) { assert(indexOrder.size() == mRecords.size()); assert(std::unordered_set(indexOrder.begin(), indexOrder.end()).size() == indexOrder.size()); std::vector>> orderedRecords; for (const int index : indexOrder) { mIndex.at(mRecords[index]->get().mId) = static_cast(orderedRecords.size()); orderedRecords.push_back(std::move(mRecords[index])); } mRecords = std::move(orderedRecords); } template bool Collection::reorderRowsImp(int baseIndex, const std::vector& newOrder) { if (!newOrder.empty()) { int size = static_cast(newOrder.size()); // check that all indices are present std::vector test(newOrder); std::sort(test.begin(), test.end()); if (*test.begin() != 0 || *--test.end() != size - 1) return false; // reorder records std::vector>> buffer(size); 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); // adjust index for (auto& [id, index] : mIndex) if (index >= baseIndex && index < baseIndex + size) index = newOrder.at(index - baseIndex) + baseIndex; } return true; } template int Collection::cloneRecordImp( const ESM::RefId& origin, const ESM::RefId& destination, UniversalId::Type type) { auto copy = std::make_unique>(); copy->mModified = getRecord(origin).get(); copy->mState = RecordBase::State_ModifiedOnly; setRecordId(destination, copy->get()); if constexpr (std::is_same_v) { if (type == UniversalId::Type_Reference) { CSMWorld::CellRef* ptr = (CSMWorld::CellRef*)©->mModified; ptr->mRefNum.mIndex = 0; } } if constexpr (std::is_same_v) { copy->mModified.mStringId = copy->mModified.mId.getRefIdString(); } const int index = getAppendIndex(destination, type); insertRecord(std::move(copy), getAppendIndex(destination, type)); return index; } template int Collection::touchRecordImp(const ESM::RefId& id) { const int index = getIndex(id); Record& record = *mRecords.at(index); if (record.isDeleted()) throw std::runtime_error("attempt to touch deleted record from collection of " + std::string(ESXRecordT::getRecordType()) + ": " + id.toDebugString()); if (!record.isModified()) { record.setModified(record.get()); return index; } return -1; } template void Collection::cloneRecord( const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { cloneRecordImp(origin, destination, type); } template <> inline void Collection::cloneRecord( const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { const int index = cloneRecordImp(origin, destination, type); mRecords.at(index)->get().setPlugin(0); } template bool Collection::touchRecord(const ESM::RefId& id) { return touchRecordImp(id) != -1; } template <> inline bool Collection::touchRecord(const ESM::RefId& id) { const int index = touchRecordImp(id); if (index >= 0) { mRecords.at(index)->get().setPlugin(0); return true; } return false; } template Collection::~Collection() { for (typename std::vector*>::iterator iter(mColumns.begin()); iter != mColumns.end(); ++iter) delete *iter; } template void Collection::add(const ESXRecordT& record) { const ESM::RefId id = getRecordId(record); auto iter = mIndex.find(id); if (iter == mIndex.end()) { auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; record2->mModified = record; insertRecord(std::move(record2), getAppendIndex(id)); } else { mRecords[iter->second]->setModified(record); } } template int Collection::getSize() const { return mRecords.size(); } template ESM::RefId Collection::getId(int index) const { return getRecordId(mRecords.at(index)->get()); } template int Collection::getIndex(const ESM::RefId& id) const { int index = searchId(id); if (index == -1) throw std::runtime_error("ID is not found in collection of " + std::string(ESXRecordT::getRecordType()) + " records: " + id.getRefIdString()); return index; } template int Collection::getColumns() const { return mColumns.size(); } template QVariant Collection::getData(int index, int column) const { return mColumns.at(column)->get(*mRecords.at(index)); } template void Collection::setData(int index, int column, const QVariant& data) { return mColumns.at(column)->set(*mRecords.at(index), data); } template const ColumnBase& Collection::getColumn(int column) const { return *mColumns.at(column); } template NestableColumn* Collection::getNestableColumn(int column) const { if (column < 0 || column >= static_cast(mColumns.size())) throw std::runtime_error( "column index out of range [0, " + std::to_string(mColumns.size()) + "): " + std::to_string(column)); return mColumns.at(column); } template void Collection::addColumn(Column* column) { mColumns.push_back(column); } template void Collection::merge() { for (typename std::vector>>::iterator iter(mRecords.begin()); iter != mRecords.end(); ++iter) (*iter)->merge(); purge(); } template void Collection::purge() { int i = 0; while (i < static_cast(mRecords.size())) { if (mRecords[i]->isErased()) removeRows(i, 1); else ++i; } } template void Collection::removeRows(int index, int count) { mRecords.erase(mRecords.begin() + index, mRecords.begin() + index + count); auto iter = mIndex.begin(); while (iter != mIndex.end()) { if (iter->second >= index) { if (iter->second >= index + count) { iter->second -= count; ++iter; } else { iter = mIndex.erase(iter); } } else ++iter; } } template void Collection::appendBlankRecord(const ESM::RefId& id, UniversalId::Type type) { ESXRecordT record; setRecordId(id, record); record.blank(); if constexpr (std::is_same_v) { record.mStringId = record.mId.getRefIdString(); } auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; record2->mModified = record; insertRecord(std::move(record2), getAppendIndex(id, type), type); } template int Collection::searchId(const ESM::RefId& id) const { const auto iter = mIndex.find(id); if (iter == mIndex.end()) return -1; return iter->second; } template void Collection::replace(int index, std::unique_ptr record) { std::unique_ptr> tmp(static_cast*>(record.release())); mRecords.at(index) = std::move(tmp); } template void Collection::appendRecord(std::unique_ptr record, UniversalId::Type type) { int index = getAppendIndex(getRecordId(static_cast*>(record.get())->get()), type); insertRecord(std::move(record), index, type); } template int Collection::getAppendIndex(const ESM::RefId& id, UniversalId::Type type) const { return static_cast(mRecords.size()); } template std::vector Collection::getIds(bool listDeleted) const { std::vector ids; for (auto iter = mIndex.begin(); iter != mIndex.end(); ++iter) { if (listDeleted || !mRecords[iter->second]->isDeleted()) ids.push_back(getRecordId(mRecords[iter->second]->get())); } return ids; } template const Record& Collection::getRecord(const ESM::RefId& id) const { int index = getIndex(id); return *mRecords.at(index); } template const Record& Collection::getRecord(int index) const { return *mRecords.at(index); } template void Collection::insertRecord(std::unique_ptr record, int index, UniversalId::Type type) { int size = static_cast(mRecords.size()); if (index < 0 || index > size) throw std::runtime_error("index out of range"); std::unique_ptr> record2(static_cast*>(record.release())); ESM::RefId id = getRecordId(record2->get()); if (index == size) mRecords.push_back(std::move(record2)); else mRecords.insert(mRecords.begin() + index, std::move(record2)); if (index < size - 1) { for (auto& [key, value] : mIndex) { if (value >= index) ++value; } } mIndex.insert(std::make_pair(id, index)); } template void Collection::setRecord(int index, std::unique_ptr> record) { if (getRecordId(mRecords.at(index)->get()) != getRecordId(record->get())) throw std::runtime_error("attempt to change the ID of a record"); mRecords.at(index) = std::move(record); } template bool Collection::reorderRows(int baseIndex, const std::vector& newOrder) { return false; } } #endif