diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 4626d16424..b3a1216f75 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -11,72 +11,290 @@ namespace CSMWorld { - ActorAdapter::ActorAdapter(CSMWorld::Data& data) + const std::string& ActorAdapter::RaceData::getId() const + { + return mId; + } + + bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const + { + switch (type) + { + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + return false; + default: + return true; + } + } + + const std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const + { + return mFemaleParts[ESM::getMeshPart(index)]; + } + + const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const + { + return mMaleParts[ESM::getMeshPart(index)]; + } + + bool ActorAdapter::RaceData::hasDependency(const std::string& id) const + { + return mDependencies.find(id) != mDependencies.end(); + } + + void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + { + mFemaleParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + { + mMaleParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::RaceData::addOtherDependency(const std::string& id) + { + if (!id.empty()) mDependencies.emplace(id); + } + + void ActorAdapter::RaceData::reset(const std::string& id) + { + mId = id; + for (auto& str : mFemaleParts) + str.clear(); + for (auto& str : mMaleParts) + str.clear(); + mDependencies.clear(); + + // Mark self as a dependency + addOtherDependency(id); + } + + + const std::string& ActorAdapter::ActorData::getId() const + { + return mId; + } + + bool ActorAdapter::ActorData::isFemale() const + { + return mFemale; + } + + const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + { + if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index)) + { + return mFemale ? mRaceData->getFemalePart(index) : mRaceData->getMalePart(index); + } + return mParts[index]; + } + + bool ActorAdapter::ActorData::hasDependency(const std::string& id) const + { + return mDependencies.find(id) != mDependencies.end(); + } + + void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId) + { + mParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::ActorData::addOtherDependency(const std::string& id) + { + if (!id.empty()) mDependencies.emplace(id); + } + + void ActorAdapter::ActorData::reset(const std::string& id, bool isFemale, RaceDataPtr raceData) + { + mId = id; + mFemale = isFemale; + mRaceData = raceData; + for (auto& str : mParts) + str.clear(); + mDependencies.clear(); + + // Mark self and race as a dependency + addOtherDependency(id); + if (raceData) addOtherDependency(raceData->getId()); + } + + + ActorAdapter::ActorAdapter(Data& data) : mReferenceables(data.getReferenceables()) , mRaces(data.getRaces()) , mBodyParts(data.getBodyParts()) { - connect(data.getTableModel(UniversalId::Type_Referenceable), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + // Setup qt slots and signals + QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable); + connect(refModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(handleReferenceablesInserted(const QModelIndex&, int, int))); + connect(refModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&))); - connect(data.getTableModel(UniversalId::Type_Race), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + connect(refModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + this, SLOT(handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int))); + + QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race); + connect(raceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); + connect(raceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&))); - connect(data.getTableModel(UniversalId::Type_BodyPart), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + connect(raceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int))); + + QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart); + connect(partModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(handleBodyPartsInserted(const QModelIndex&, int, int))); + connect(partModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&))); + connect(partModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + this, SLOT(handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int))); } - const ActorAdapter::ActorPartMap* ActorAdapter::getActorParts(const std::string& refId, bool create) + ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id) { - auto it = mCachedActors.find(refId); - if (it != mCachedActors.end()) + // Return cached actor data if it exists + ActorDataPtr data = mCachedActors.get(id); + if (data) { - return &it->second.parts; + return data; } - else if (create) + + // Create the actor data + data.reset(new ActorData()); + setupActor(id, data); + mCachedActors.insert(id, data); + return data; + } + + void ActorAdapter::handleReferenceablesInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top level are pertinent. Others are caught by dataChanged handler. + if (!parent.isValid()) { - updateActor(refId); - return getActorParts(refId, false); - } - else - { - return nullptr; + for (int row = start; row <= end; ++row) + { + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } } + + // Update affected + updateDirty(); } void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - // Setup - const int TypeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - int rowStart = getHighestIndex(topLeft).row(); - int rowEnd = getHighestIndex(botRight).row(); + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); // Handle each record - for (int row = rowStart; row <= rowEnd; ++row) + for (int row = start; row <= end; ++row) { - int type = mReferenceables.getData(row, TypeColumn).toInt(); - if (type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Npc) - { - // Update the cached npc or creature - std::string refId = mReferenceables.getId(row); - if (mCachedActors.find(refId) != mCachedActors.end()) - updateActor(refId); - } - else if (type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Clothing) + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleReferenceablesAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only rows at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) { std::string refId = mReferenceables.getId(row); - updateActorsWithDependency(refId); + markDirtyDependency(refId); } } } + void ActorAdapter::handleReferenceablesRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleReferenceablesAboutToBeRemoved + updateDirty(); + } + + void ActorAdapter::handleRacesInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string raceId = mReferenceables.getId(row); + markDirtyDependency(raceId); + } + } + + // Update affected + updateDirty(); + } + void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { - int rowStart = getHighestIndex(topLeft).row(); - int rowEnd = getHighestIndex(botRight).row(); - for (int row = rowStart; row <= rowEnd; ++row) + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); + for (int row = start; row <= end; ++row) { std::string raceId = mRaces.getId(row); - updateActorsWithDependency(raceId); + markDirtyDependency(raceId); } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleRacesAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only changes at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string raceId = mRaces.getId(row); + markDirtyDependency(raceId); + } + } + } + + void ActorAdapter::handleRacesRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleRacesAboutToBeRemoved + updateDirty(); + } + + void ActorAdapter::handleBodyPartsInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + // Race specified by part may need update + auto& record = mBodyParts.getRecord(row); + if (!record.isDeleted()) + { + markDirtyDependency(record.get().mRace); + } + + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + } + + // Update affected + updateDirty(); } void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) @@ -85,18 +303,39 @@ namespace CSMWorld int rowEnd = getHighestIndex(botRight).row(); for (int row = rowStart; row <= rowEnd; ++row) { - // Manually update race specified by part + // Race specified by part may need update auto& record = mBodyParts.getRecord(row); if (!record.isDeleted()) { - updateRace(record.get().mRace); + markDirtyDependency(record.get().mRace); } // Update entries with a tracked dependency std::string partId = mBodyParts.getId(row); - updateRacesWithDependency(partId); - updateActorsWithDependency(partId); + markDirtyDependency(partId); } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleBodyPartsAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only changes at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + } + } + + void ActorAdapter::handleBodyPartsRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleBodyPartsAboutToBeRemoved + updateDirty(); } QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const @@ -111,251 +350,223 @@ namespace CSMWorld return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos; } - ActorAdapter::RaceData& ActorAdapter::getRaceData(const std::string& raceId) + ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id) { - auto it = mCachedRaces.find(raceId); - if (it != mCachedRaces.end()) + // Return cached race data if it exists + RaceDataPtr data = mCachedRaces.get(id); + if (data) return data; + + // Create the race data + data.reset(new RaceData()); + setupRace(id, data); + mCachedRaces.insert(id, data); + return data; + } + + void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data) + { + int index = mReferenceables.searchId(id); + if (index == -1) { - return it->second; + // Record does not exist + data->reset(id); + emit actorChanged(id); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Record is deleted and therefore not accessible + data->reset(id); + emit actorChanged(id); + return; + } + + const int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); + int type = mReferenceables.getData(index, TypeColumn).toInt(); + if (type == UniversalId::Type_Creature) + { + // Valid creature record + setupCreature(id, data); + emit actorChanged(id); + } + else if (type == UniversalId::Type_Npc) + { + // Valid npc record + setupNpc(id, data); + emit actorChanged(id); } else { - // Create and find result - updateRace(raceId); - return mCachedRaces.find(raceId)->second; + // Wrong record type + data->reset(id); + emit actorChanged(id); } } - void ActorAdapter::updateRace(const std::string& raceId) + void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) { - // Retrieve or create cache entry - auto raceDataIt = mCachedRaces.find(raceId); - if (raceDataIt == mCachedRaces.end()) + // Common setup + data->reset(id); + + int index = mRaces.searchId(id); + if (index == -1) { - auto result = mCachedRaces.emplace(raceId, RaceData()); - raceDataIt = result.first; - } - - auto& raceData = raceDataIt->second; - raceData.femaleParts.clear(); - raceData.maleParts.clear(); - raceData.dependencies.clear(); - - // Construct entry - for (int i = 0; i < mBodyParts.getSize(); ++i) - { - auto& record = mBodyParts.getRecord(i); - if (!record.isDeleted() && record.get().mRace == raceId) - { - auto& part = record.get(); - - // Part could affect race data - raceData.dependencies.emplace(part.mId, true); - - // Add base types - if (part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) - { - auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; - // Note: Prefer the first part encountered for duplicates. emplace() does not overwrite - if (part.mData.mFlags & ESM::BodyPart::BPF_Female) - raceData.femaleParts.emplace(type, part.mId); - else - raceData.maleParts.emplace(type, part.mId); - } - } - } - - updateActorsWithDependency(raceId); - } - - void ActorAdapter::updateActor(const std::string& refId) - { - int index = mReferenceables.searchId(refId); - if (index != -1) - { - int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - int recordType = mReferenceables.getData(index, typeColumn).toInt(); - if (recordType == CSMWorld::UniversalId::Type_Creature) - updateCreature(refId); - else if (recordType == CSMWorld::UniversalId::Type_Npc) - updateNpc(refId); - } - } - - void ActorAdapter::updateNpc(const std::string& refId) - { - auto& record = mReferenceables.getRecord(refId); - - // Retrieve record if possible - if (record.isDeleted()) - { - mCachedActors.erase(refId); - emit actorChanged(refId); + // Record does not exist return; } - auto& npc = dynamic_cast&>(record).get(); - // Create holder for cached data - auto actorIt = mCachedActors.find(refId); - if (actorIt == mCachedActors.end()) + auto& raceRecord = mRaces.getRecord(index); + if (raceRecord.isDeleted()) { - auto result = mCachedActors.emplace(refId, ActorData()); - actorIt = result.first; + // Record is deleted, so not accessible + return; } - auto& actorData = actorIt->second; - // Reset old data - actorData.parts.clear(); - actorData.dependencies.clear(); + // TODO move stuff in actor related to race here - // Look at the npc's inventory first + // Setup body parts + for (int i = 0; i < mBodyParts.getSize(); ++i) + { + std::string partId = mBodyParts.getId(i); + auto& partRecord = mBodyParts.getRecord(i); + + if (partRecord.isDeleted()) + { + // Record is deleted, so not accessible. + continue; + } + + auto& part = partRecord.get(); + if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) + { + auto type = (ESM::BodyPart::MeshPart) part.mData.mPart; + bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female; + if (female) data->setFemalePart(type, part.mId); + else data->setMalePart(type, part.mId); + } + } + } + + void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data) + { + // Common setup, record is known to exist and is not deleted + int index = mReferenceables.searchId(id); + auto& npc = dynamic_cast&>(mReferenceables.getRecord(index)).get(); + + RaceDataPtr raceData = getRaceData(npc.mRace); + data->reset(id, !npc.isMale(), raceData); + + // Add inventory items for (auto& item : npc.mInventory.mList) { - if (item.mCount > 0) + if (item.mCount <= 0) continue; + std::string itemId = item.mItem.toString(); + addNpcItem(itemId, data); + } + + // Add head and hair + data->setPart(ESM::PRT_Head, npc.mHead); + data->setPart(ESM::PRT_Hair, npc.mHair); + } + + void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) + { + int index = mReferenceables.searchId(itemId); + if (index == -1) + { + // Item does not exist yet + data->addOtherDependency(itemId); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Item cannot be accessed yet + data->addOtherDependency(itemId); + return; + } + + // Convenience function to add a parts list to actor data + auto addParts = [&](const ESM::PartReferenceList& list) { + for (auto& part : list.mParts) { - std::string itemId = item.mItem.toString(); - // Handle armor and clothing - int index = mReferenceables.searchId(itemId); - if (index != -1 && !mReferenceables.getRecord(index).isDeleted()) - { - auto& itemRecord = mReferenceables.getRecord(index); + std::string partId; + auto partType = (ESM::PartReferenceType) part.mPart; - int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); - int recordType = mReferenceables.getData(index, typeColumn).toInt(); - if (recordType == CSMWorld::UniversalId::Type_Armor) - { - // Changes here could affect the actor - actorData.dependencies.emplace(itemId, true); + if (data->isFemale()) + partId = part.mFemale; + if (partId.empty()) + partId = part.mMale; - // Add any parts if there is room - auto& armor = dynamic_cast&>(itemRecord).get(); - for (auto& part : armor.mParts.mParts) - { - std::string bodyPartId; - if (!npc.isMale()) - bodyPartId = part.mFemale; - if (bodyPartId.empty()) - bodyPartId = part.mMale; + if (!partId.empty()) data->setPart(partType, partId); + } + }; - if (!bodyPartId.empty()) - { - actorData.parts.emplace(static_cast(part.mPart), bodyPartId); - actorData.dependencies.emplace(bodyPartId, true); - } - } - } - else if (recordType == CSMWorld::UniversalId::Type_Clothing) - { - // Changes here could affect the actor - actorData.dependencies.emplace(itemId, true); + int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); + int type = mReferenceables.getData(index, TypeColumn).toInt(); + if (type == UniversalId::Type_Armor) + { + auto& armor = dynamic_cast&>(record).get(); + addParts(armor.mParts); - // Add any parts if there is room - auto& clothing = dynamic_cast&>(itemRecord).get(); - for (auto& part : clothing.mParts.mParts) - { - std::string bodyPartId; - if (!npc.isMale()) - bodyPartId = part.mFemale; - if (bodyPartId.empty()) - bodyPartId = part.mMale; + // Changing parts could affect what is picked for rendering + data->addOtherDependency(itemId); + } + else if (type == UniversalId::Type_Clothing) + { + auto& clothing = dynamic_cast&>(record).get(); + addParts(clothing.mParts); - if (!bodyPartId.empty()) - { - actorData.parts.emplace(static_cast(part.mPart), bodyPartId); - actorData.dependencies.emplace(bodyPartId, true); - } - } - } - } + // Changing parts could affect what is picked for rendering + data->addOtherDependency(itemId); + } + } + + void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) + { + data->reset(id); + + // TODO move stuff from Actor here + } + + void ActorAdapter::markDirtyDependency(const std::string& dep) + { + for (auto raceIt : mCachedRaces) + { + if (raceIt->hasDependency(dep)) + mDirtyRaces.emplace(raceIt->getId()); + } + for (auto actorIt : mCachedActors) + { + if (actorIt->hasDependency(dep)) + mDirtyActors.emplace(actorIt->getId()); + } + } + + void ActorAdapter::updateDirty() + { + // Handle races before actors, since actors are dependent on race + for (auto& race : mDirtyRaces) + { + RaceDataPtr data = mCachedRaces.get(race); + if (data) + { + setupRace(race, data); } } + mDirtyRaces.clear(); - // Lookup cached race parts - auto& raceData = getRaceData(npc.mRace); - - // Changes to race could affect the actor - actorData.dependencies.emplace(npc.mRace, true); - - // Fill in the rest with race specific body parts - for (int i = 0; i < ESM::PRT_Count; ++i) + for (auto& actor : mDirtyActors) { - auto type = static_cast(i); - if (actorData.parts.find(type) == actorData.parts.end()) + ActorDataPtr data = mCachedActors.get(actor); + if (data) { - switch (type) - { - case ESM::PRT_Head: - actorData.parts.emplace(type, npc.mHead); - actorData.dependencies.emplace(npc.mHead, true); - break; - case ESM::PRT_Hair: - actorData.parts.emplace(type, npc.mHair); - actorData.dependencies.emplace(npc.mHair, true); - break; - case ESM::PRT_Skirt: - case ESM::PRT_Shield: - case ESM::PRT_RPauldron: - case ESM::PRT_LPauldron: - case ESM::PRT_Weapon: - // No body part associated - break; - default: - { - std::string bodyPartId; - // Check female map if applicable - if (!npc.isMale()) - { - auto partIt = raceData.femaleParts.find(ESM::getMeshPart(type)); - if (partIt != raceData.femaleParts.end()) - bodyPartId = partIt->second; - } - - // Check male map next - if (bodyPartId.empty() || npc.isMale()) - { - auto partIt = raceData.maleParts.find(ESM::getMeshPart(type)); - if (partIt != raceData.maleParts.end()) - bodyPartId = partIt->second; - } - - // Add to map - if (!bodyPartId.empty()) - { - actorData.parts.emplace(type, bodyPartId); - actorData.dependencies.emplace(bodyPartId, true); - } - } - } + setupActor(actor, data); } } - - // Signal change to actor - emit actorChanged(refId); - } - - void ActorAdapter::updateCreature(const std::string& refId) - { - // Signal change to actor - emit actorChanged(refId); - } - - void ActorAdapter::updateActorsWithDependency(const std::string& id) - { - for (auto it : mCachedActors) - { - auto& deps = it.second.dependencies; - if (deps.find(id) != deps.end()) - updateActor(it.first); - } - } - - void ActorAdapter::updateRacesWithDependency(const std::string& id) - { - for (auto it : mCachedRaces) - { - auto& deps = it.second.dependencies; - if (deps.find(id) != deps.end()) - updateRace(it.first); - } + mDirtyActors.clear(); } } diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index f1148199c0..0e8b0c9fed 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -1,13 +1,13 @@ #ifndef CSM_WOLRD_ACTORADAPTER_H #define CSM_WOLRD_ACTORADAPTER_H -#include -#include -#include +#include +#include #include #include +#include #include #include @@ -17,88 +17,147 @@ namespace ESM { struct Race; - enum PartReferenceType; } namespace CSMWorld { class Data; - /// Quick and dirty hashing functor. - struct StringBoolPairHash - { - size_t operator()(const std::pair& value) const noexcept - { - auto stringHash = std::hash(); - return stringHash(value.first) + value.second; - } - }; - + /// Adapts multiple collections to provide the data needed to render + /// an npc or creature. class ActorAdapter : public QObject { - Q_OBJECT + Q_OBJECT + public: + + /// A list indexed by ESM::PartReferenceType + using ActorPartList = std::array; + /// A list indexed by ESM::BodyPart::MeshPart + using RacePartList = std::array; + /// Tracks unique strings + using StringSet = std::unordered_set; + + + /// Contains base race data shared between actors + class RaceData + { public: + /// Retrieves the id of the race represented + const std::string& getId() const; + /// Checks if a part could exist for the given type + bool handlesPart(ESM::PartReferenceType type) const; + /// Retrieves the associated body part + const std::string& getFemalePart(ESM::PartReferenceType index) const; + /// Retrieves the associated body part + const std::string& getMalePart(ESM::PartReferenceType index) const; + /// Checks if the race has a data dependency + bool hasDependency(const std::string& id) const; - // Maps body part type to 'body part' id - using ActorPartMap = std::unordered_map; - - ActorAdapter(CSMWorld::Data& data); - - const ActorPartMap* getActorParts(const std::string& refId, bool create=true); - - signals: - - void actorChanged(const std::string& refId); - - public slots: - - void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); - void handleRaceChanged(const QModelIndex&, const QModelIndex&); - void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); + /// Sets the associated part if it's empty and marks a dependency + void setFemalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + /// Sets the associated part if it's empty and marks a dependency + void setMalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + /// Marks an additional dependency + void addOtherDependency(const std::string& id); + /// Clears parts and dependencies + void reset(const std::string& raceId); private: - // Maps mesh part type to 'body part' id - using RacePartMap = std::unordered_map; - // Stores ids that are referenced by the actor. Data part is meaningless. - using DependencyMap = std::unordered_map; + bool handles(ESM::PartReferenceType type) const; + std::string mId; + RacePartList mFemaleParts; + RacePartList mMaleParts; + StringSet mDependencies; + }; + using RaceDataPtr = std::shared_ptr; - struct ActorData - { - ActorPartMap parts; - DependencyMap dependencies; - }; + /// Contains all the data needed to render an actor. Tracks dependencies + /// so that pertinent data changes can be checked. + class ActorData + { + public: + /// Retrieves the id of the actor represented + const std::string& getId() const; + /// Checks if the actor is female + bool isFemale() const; + /// Retrieves the associated actor part + const std::string& getPart(ESM::PartReferenceType index) const; + /// Checks if the actor has a data dependency + bool hasDependency(const std::string& id) const; - struct RaceData - { - RacePartMap femaleParts; - RacePartMap maleParts; - DependencyMap dependencies; - }; + /// Sets the actor part used and marks a dependency + void setPart(ESM::PartReferenceType partIndex, const std::string& partId); + /// Marks an additional dependency for the actor + void addOtherDependency(const std::string& id); + /// Clears race, parts, and dependencies + void reset(const std::string& actorId, bool female=true, RaceDataPtr raceData=nullptr); - ActorAdapter(const ActorAdapter&) = delete; - ActorAdapter& operator=(const ActorAdapter&) = delete; + private: + std::string mId; + bool mFemale; + RaceDataPtr mRaceData; + ActorPartList mParts; + StringSet mDependencies; + }; + using ActorDataPtr = std::shared_ptr; - QModelIndex getHighestIndex(QModelIndex) const; - bool is1stPersonPart(const std::string& id) const; - RaceData& getRaceData(const std::string& raceId); + ActorAdapter(Data& data); - void updateRace(const std::string& raceId); - void updateActor(const std::string& refId); - void updateNpc(const std::string& refId); - void updateCreature(const std::string& refId); + /// Obtains the shared data for a given actor + ActorDataPtr getActorData(const std::string& refId); - void updateActorsWithDependency(const std::string& id); - void updateRacesWithDependency(const std::string& id); + signals: - RefIdCollection& mReferenceables; - IdCollection& mRaces; - IdCollection& mBodyParts; + void actorChanged(const std::string& refId); - // Key: referenceable id - std::unordered_map mCachedActors; - // Key: race id - std::unordered_map mCachedRaces; + public slots: + + void handleReferenceablesInserted(const QModelIndex&, int, int); + void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); + void handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int); + void handleReferenceablesRemoved(const QModelIndex&, int, int); + + void handleRacesInserted(const QModelIndex&, int, int); + void handleRaceChanged(const QModelIndex&, const QModelIndex&); + void handleRacesAboutToBeRemoved(const QModelIndex&, int, int); + void handleRacesRemoved(const QModelIndex&, int, int); + + void handleBodyPartsInserted(const QModelIndex&, int, int); + void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); + void handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int); + void handleBodyPartsRemoved(const QModelIndex&, int, int); + + private: + + ActorAdapter(const ActorAdapter&) = delete; + ActorAdapter& operator=(const ActorAdapter&) = delete; + + QModelIndex getHighestIndex(QModelIndex) const; + bool is1stPersonPart(const std::string& id) const; + + RaceDataPtr getRaceData(const std::string& raceId); + + void setupActor(const std::string& id, ActorDataPtr data); + void setupRace(const std::string& id, RaceDataPtr data); + + void setupNpc(const std::string& id, ActorDataPtr data); + void addNpcItem(const std::string& itemId, ActorDataPtr data); + + void setupCreature(const std::string& id, ActorDataPtr data); + + void markDirtyDependency(const std::string& dependency); + void updateDirty(); + + RefIdCollection& mReferenceables; + IdCollection& mRaces; + IdCollection& mBodyParts; + + cache::WeakCache mCachedActors; // Key: referenceable id + cache::WeakCache mCachedRaces; // Key: race id + + StringSet mDirtyActors; // Actors that need updating + StringSet mDirtyRaces; // Races that need updating }; } diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index b9979ae2c6..0a2519d0dd 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -26,6 +26,7 @@ namespace CSVRender , mBaseNode(new osg::Group()) , mSkeleton(nullptr) { + mActorData = mData.getActorAdapter()->getActorData(mId); } osg::Group* Actor::getBaseNode() @@ -60,7 +61,6 @@ namespace CSVRender { if (mId == refId) { - Log(Debug::Info) << "Actor::actorChanged " << mId; update(); } } @@ -80,9 +80,6 @@ namespace CSVRender mSkeleton->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); - // Attach weapons - loadBodyParts(creature.mId); - // Post setup mSkeleton->markDirty(); mSkeleton->setActive(SceneUtil::Skeleton::Active); @@ -141,12 +138,11 @@ namespace CSVRender void Actor::loadBodyParts(const std::string& actorId) { - auto actorAdapter = mData.getActorAdapter(); - auto parts = actorAdapter->getActorParts(actorId); - if (parts) + for (int i = 0; i < ESM::PRT_Count; ++i) { - for (auto& pair : *parts) - attachBodyPart(pair.first, getBodyPartMesh(pair.second)); + auto type = (ESM::PartReferenceType) i; + std::string partId = mActorData->getPart(type); + attachBodyPart(type, getBodyPartMesh(partId)); } } diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 42a6019ed7..d7cbb30672 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -10,6 +10,8 @@ #include #include +#include "../../model/world/actoradapter.hpp" + namespace osg { class Group; @@ -63,6 +65,7 @@ namespace CSVRender bool mInitialized; int mType; CSMWorld::Data& mData; + CSMWorld::ActorAdapter::ActorDataPtr mActorData; osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7af76137c5..494e1c5ce8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -150,6 +150,10 @@ add_component_dir (fallback fallback validate ) +add_component_dir(cache + weakcache + ) + if(NOT WIN32 AND NOT ANDROID) add_component_dir (crashcatcher crashcatcher diff --git a/components/cache/weakcache.hpp b/components/cache/weakcache.hpp new file mode 100644 index 0000000000..9d940cd746 --- /dev/null +++ b/components/cache/weakcache.hpp @@ -0,0 +1,116 @@ +#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP +#define OPENMW_COMPONENTS_WEAKCACHE_HPP + +#include +#include + +namespace cache +{ + /// \class WeakCache + /// Provides a container to weakly store pointers to shared data. + template + class WeakCache + { + public: + using WeakPtr = std::weak_ptr; + using StrongPtr = std::shared_ptr; + using Map = std::unordered_map; + + class iterator + { + public: + iterator(typename Map::iterator current, typename Map::iterator end); + iterator& operator++(); + bool operator==(const iterator& other); + bool operator!=(const iterator& other); + StrongPtr operator*(); + private: + typename Map::iterator mCurrent, mEnd; + StrongPtr mPtr; + }; + + /// Stores a weak pointer to the item. + void insert(Key key, StrongPtr value); + + /// Retrieves the item associated with the key. + /// \return An item or null. + StrongPtr get(Key key); + + iterator begin(); + iterator end(); + + private: + Map mData; + }; + + + template + WeakCache::iterator::iterator(typename Map::iterator current, typename Map::iterator end) + : mCurrent(current) + , mEnd(end) + { + // Move to 1st available valid item + for ( ; mCurrent != mEnd; ++mCurrent) + { + mPtr = mCurrent->second.lock(); + if (mPtr) break; + } + } + + template + typename WeakCache::iterator& WeakCache::iterator::operator++() + { + auto next = mCurrent; + ++next; + return *this = iterator(next, mEnd); + } + + template + bool WeakCache::iterator::operator==(const iterator& other) + { + return mCurrent == other.mCurrent; + } + + template + bool WeakCache::iterator::operator!=(const iterator& other) + { + return !(*this == other); + } + + template + typename WeakCache::StrongPtr WeakCache::iterator::operator*() + { + return mPtr; + } + + + template + void WeakCache::insert(Key key, StrongPtr value) + { + mData[key] = WeakPtr(value); + } + + template + typename WeakCache::StrongPtr WeakCache::get(Key key) + { + auto searchIt = mData.find(key); + if (searchIt != mData.end()) + return searchIt->second.lock(); + else + return StrongPtr(); + } + + template + typename WeakCache::iterator WeakCache::begin() + { + return iterator(mData.begin(), mData.end()); + } + + template + typename WeakCache::iterator WeakCache::end() + { + return iterator(mData.end(), mData.end()); + } +} + +#endif