#ifndef CSM_WOLRD_COLLECTION_H #define CSM_WOLRD_COLLECTION_H #include #include #include #include #include #include #include #include #include #include #include #include "collectionbase.hpp" #include "columnbase.hpp" #include "land.hpp" #include "landtexture.hpp" #include "record.hpp" #include "ref.hpp" namespace CSMWorld { /// \brief Access to ID field in records template struct IdAccessor { void setId(ESXRecordT& record, const ESM::RefId& id) const; const ESM::RefId getId(const ESXRecordT& record) const; }; template void IdAccessor::setId(ESXRecordT& record, const ESM::RefId& id) const { record.mId = id; } template const ESM::RefId IdAccessor::getId(const ESXRecordT& record) const { return record.mId; } template <> inline void IdAccessor::setId(Land& record, const ESM::RefId& id) const { int x = 0, y = 0; Land::parseUniqueRecordId(id.getRefIdString(), x, y); record.mX = x; record.mY = y; } template <> inline void IdAccessor::setId(LandTexture& record, const ESM::RefId& id) const { int plugin = 0; int index = 0; LandTexture::parseUniqueRecordId(id.getRefIdString(), plugin, index); record.mPluginIndex = plugin; record.mIndex = index; } template <> inline const ESM::RefId IdAccessor::getId(const Land& record) const { return ESM::RefId::stringRefId(Land::createUniqueRecordId(record.mX, record.mY)); } template <> inline const ESM::RefId IdAccessor::getId(const LandTexture& record) const { 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; // not implemented Collection(const Collection&); Collection& operator=(const Collection&); protected: const std::vector>>& getRecords() const; 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 std::string& origin, const std::string& dest, UniversalId::Type type); ///< Returns the index of the clone. int touchRecordImp(const std::string& id); ///< Returns the index of the record on success, -1 on failure. public: Collection(); virtual ~Collection(); 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; virtual void merge(); ///< Merge modified into base. virtual 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 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 std::string& origin, const std::string& destination, UniversalId::Type type) { auto copy = std::make_unique>(); copy->mModified = getRecord(ESM::RefId::stringRefId(origin)).get(); copy->mState = RecordBase::State_ModifiedOnly; IdAccessorT().setId(copy->get(), ESM::RefId::stringRefId(destination)); if (type == UniversalId::Type_Reference) { CSMWorld::CellRef* ptr = (CSMWorld::CellRef*)©->mModified; ptr->mRefNum.mIndex = 0; } ESM::RefId destinationRefId = ESM::RefId::stringRefId(destination); int index = getAppendIndex(destinationRefId, type); insertRecord(std::move(copy), getAppendIndex(destinationRefId, type)); return index; } template int Collection::touchRecordImp(const std::string& id) { int index = getIndex(ESM::RefId::stringRefId(id)); Record& record = *mRecords.at(index); if (record.isDeleted()) { throw std::runtime_error("attempt to touch deleted record"); } 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.getRefIdString(), destination.getRefIdString(), type); } template <> inline void Collection>::cloneRecord( const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { int index = cloneRecordImp(origin.getRefIdString(), destination.getRefIdString(), type); mRecords.at(index)->get().setPlugin(0); } template bool Collection::touchRecord(const ESM::RefId& id) { return touchRecordImp(id.getRefIdString()) != -1; } template <> inline bool Collection>::touchRecord(const ESM::RefId& id) { int index = touchRecordImp(id.getRefIdString()); if (index >= 0) { mRecords.at(index)->get().setPlugin(0); return true; } return false; } template Collection::Collection() { } 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 = IdAccessorT().getId(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 IdAccessorT().getId(mRecords.at(index)->get()); } template int Collection::getIndex(const ESM::RefId& id) const { int index = searchId(id); if (index == -1) throw std::runtime_error("invalid ID: " + 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"); 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; IdAccessorT().setId(record, id); record.blank(); 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(IdAccessorT().getId(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(IdAccessorT().getId(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 = IdAccessorT().getId(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 (IdAccessorT().getId(mRecords.at(index)->get()) != IdAccessorT().getId(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