1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-10 06:39:49 +00:00
OpenMW/apps/openmw/mwworld/cellstore.hpp
elsid 3affe9913f
Limit the number of simultaneously open not actively used content files
Use LRU cache for ESMReaders. When cache capacity is reached close least
recently used ESMReader. Remember the file name if a reader was open. Once the
reader requested again open the file if there is stored name for it. Put
released ESMReader to the back of the free items list. Close ESMReader's from
the front of the free items list.

Cached item can be used only by one client at the same time. If the same item is
requested twice exception is thrown. This should never happen in practice. If
this happens need to fix the client logic.

It's allowed to go over the capacity limit when requesting different readers.
Ideally this should never happen but there will be system error anyway
signalizing about too many open files. Need to fix client logic in this case.

All places that were using a vector of ESMReaders now using the cache. Cache is
local for each use case and there is no need for a thread safety.
2022-06-03 01:29:03 +02:00

566 lines
20 KiB
C++

#ifndef GAME_MWWORLD_CELLSTORE_H
#define GAME_MWWORLD_CELLSTORE_H
#include <algorithm>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <map>
#include <memory>
#include "livecellref.hpp"
#include "cellreflist.hpp"
#include <components/esm3/loadacti.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadappa.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadbook.hpp>
#include <components/esm3/loadclot.hpp>
#include <components/esm3/loadcont.hpp>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loaddoor.hpp>
#include <components/esm3/loadingr.hpp>
#include <components/esm3/loadlevlist.hpp>
#include <components/esm3/loadligh.hpp>
#include <components/esm3/loadlock.hpp>
#include <components/esm3/loadprob.hpp>
#include <components/esm3/loadrepa.hpp>
#include <components/esm3/loadstat.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/loadmisc.hpp>
#include <components/esm3/loadbody.hpp>
#include <components/esm3/fogstate.hpp>
#include "timestamp.hpp"
#include "ptr.hpp"
namespace ESM
{
class ReadersCache;
struct Cell;
struct CellState;
struct CellId;
struct RefNum;
}
namespace MWWorld
{
class ESMStore;
/// \brief Mutable state of a cell
class CellStore
{
public:
enum State
{
State_Unloaded, State_Preloaded, State_Loaded
};
private:
const MWWorld::ESMStore& mStore;
ESM::ReadersCache& mReaders;
// Even though fog actually belongs to the player and not cells,
// it makes sense to store it here since we need it once for each cell.
// Note this is nullptr until the cell is explored to save some memory
std::unique_ptr<ESM::FogState> mFogState;
const ESM::Cell *mCell;
State mState;
bool mHasState;
std::vector<std::string> mIds;
float mWaterLevel;
MWWorld::TimeStamp mLastRespawn;
// List of refs owned by this cell
CellRefList<ESM::Activator> mActivators;
CellRefList<ESM::Potion> mPotions;
CellRefList<ESM::Apparatus> mAppas;
CellRefList<ESM::Armor> mArmors;
CellRefList<ESM::Book> mBooks;
CellRefList<ESM::Clothing> mClothes;
CellRefList<ESM::Container> mContainers;
CellRefList<ESM::Creature> mCreatures;
CellRefList<ESM::Door> mDoors;
CellRefList<ESM::Ingredient> mIngreds;
CellRefList<ESM::CreatureLevList> mCreatureLists;
CellRefList<ESM::ItemLevList> mItemLists;
CellRefList<ESM::Light> mLights;
CellRefList<ESM::Lockpick> mLockpicks;
CellRefList<ESM::Miscellaneous> mMiscItems;
CellRefList<ESM::NPC> mNpcs;
CellRefList<ESM::Probe> mProbes;
CellRefList<ESM::Repair> mRepairs;
CellRefList<ESM::Static> mStatics;
CellRefList<ESM::Weapon> mWeapons;
CellRefList<ESM::BodyPart> mBodyParts;
typedef std::map<LiveCellRefBase*, MWWorld::CellStore*> MovedRefTracker;
// References owned by a different cell that have been moved here.
// <reference, cell the reference originally came from>
MovedRefTracker mMovedHere;
// References owned by this cell that have been moved to another cell.
// <reference, cell the reference was moved to>
MovedRefTracker mMovedToAnotherCell;
// Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from mMovedToAnotherCell
std::vector<LiveCellRefBase*> mMergedRefs;
// Get the Ptr for the given ref which originated from this cell (possibly moved to another cell at this point).
Ptr getCurrentPtr(MWWorld::LiveCellRefBase* ref);
/// Moves object from the given cell to this cell.
void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from);
/// Repopulate mMergedRefs.
void updateMergedRefs();
// (item, max charge)
typedef std::vector<std::pair<LiveCellRefBase*, float> > TRechargingItems;
TRechargingItems mRechargingItems;
bool mRechargingItemsUpToDate;
void updateRechargingItems();
void rechargeItems(float duration);
void checkItem(const Ptr& ptr);
// helper function for forEachInternal
template<class Visitor, class List>
bool forEachImp (Visitor& visitor, List& list)
{
for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end();
++iter)
{
if (!isAccessible(iter->mData, iter->mRef))
continue;
if (!visitor (MWWorld::Ptr(&*iter, this)))
return false;
}
return true;
}
// listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved objects are accounted for.
template<class Visitor>
bool forEachInternal (Visitor& visitor)
{
return
forEachImp (visitor, mActivators) &&
forEachImp (visitor, mPotions) &&
forEachImp (visitor, mAppas) &&
forEachImp (visitor, mArmors) &&
forEachImp (visitor, mBooks) &&
forEachImp (visitor, mClothes) &&
forEachImp (visitor, mContainers) &&
forEachImp (visitor, mDoors) &&
forEachImp (visitor, mIngreds) &&
forEachImp (visitor, mItemLists) &&
forEachImp (visitor, mLights) &&
forEachImp (visitor, mLockpicks) &&
forEachImp (visitor, mMiscItems) &&
forEachImp (visitor, mProbes) &&
forEachImp (visitor, mRepairs) &&
forEachImp (visitor, mStatics) &&
forEachImp (visitor, mWeapons) &&
forEachImp (visitor, mBodyParts) &&
forEachImp (visitor, mCreatures) &&
forEachImp (visitor, mNpcs) &&
forEachImp (visitor, mCreatureLists);
}
/// @note If you get a linker error here, this means the given type can not be stored in a cell. The supported types are
/// defined at the bottom of this file.
template <class T>
CellRefList<T>& get();
public:
/// Should this reference be accessible to the outside world (i.e. to scripts / game logic)?
/// Determined based on the deletion flags. By default, objects deleted by content files are never accessible;
/// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla
/// scripting compatibility, and the fact that objects may be "un-deleted" in the original game).
static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref)
{
return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0);
}
/// Moves object from this cell to the given cell.
/// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...)
/// @note throws exception if cellToMoveTo == this
/// @return updated MWWorld::Ptr with the new CellStore pointer set.
MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo);
void rest(double hours);
void recharge(float duration);
/// Make a copy of the given object and insert it into this cell.
/// @note If you get a linker error here, this means the given type can not be inserted into a cell.
/// The supported types are defined at the bottom of this file.
template <typename T>
LiveCellRefBase* insert(const LiveCellRef<T>* ref)
{
mHasState = true;
CellRefList<T>& list = get<T>();
LiveCellRefBase* ret = &list.insert(*ref);
updateMergedRefs();
return ret;
}
/// @param readerList The readers to use for loading of the cell on-demand.
CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers);
const ESM::Cell *getCell() const;
State getState() const;
const std::vector<std::string>& getPreloadedIds() const;
///< Get Ids of objects in this cell, only valid in State_Preloaded
bool hasState() const;
///< Does this cell have state that needs to be stored in a saved game file?
bool hasId (const std::string& id) const;
///< May return true for deleted IDs when in preload state. Will return false, if cell is
/// unloaded.
/// @note Will not account for moved references which may exist in Loaded state. Use search() instead if the cell is loaded.
Ptr search (const std::string& id);
///< Will return an empty Ptr if cell is not loaded. Does not check references in
/// containers.
/// @note Triggers CellStore hasState flag.
ConstPtr searchConst (const std::string& id) const;
///< Will return an empty Ptr if cell is not loaded. Does not check references in
/// containers.
/// @note Does not trigger CellStore hasState flag.
Ptr searchViaActorId (int id);
///< Will return an empty Ptr if cell is not loaded.
Ptr searchViaRefNum (const ESM::RefNum& refNum);
///< Will return an empty Ptr if cell is not loaded. Does not check references in
/// containers.
/// @note Triggers CellStore hasState flag.
float getWaterLevel() const;
bool movedHere(const MWWorld::Ptr& ptr) const;
void setWaterLevel (float level);
void setFog(std::unique_ptr<ESM::FogState>&& fog);
///< \note Takes ownership of the pointer
ESM::FogState* getFog () const;
std::size_t count() const;
///< Return total number of references, including deleted ones.
void load ();
///< Load references from content file.
void preload ();
///< Build ID list from content file.
/// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning
/// false will abort the iteration.
/// \note Prefer using forEachConst when possible.
/// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour.
/// \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template<class Visitor>
bool forEach (Visitor&& visitor)
{
if (mState != State_Loaded)
return false;
if (mMergedRefs.empty())
return true;
mHasState = true;
for (unsigned int i=0; i<mMergedRefs.size(); ++i)
{
if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef))
continue;
if (!visitor(MWWorld::Ptr(mMergedRefs[i], this)))
return false;
}
return true;
}
/// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning
/// false will abort the iteration.
/// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour.
/// \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template<class Visitor>
bool forEachConst (Visitor&& visitor) const
{
if (mState != State_Loaded)
return false;
for (unsigned int i=0; i<mMergedRefs.size(); ++i)
{
if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef))
continue;
if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this)))
return false;
}
return true;
}
/// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning
/// false will abort the iteration.
/// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in unintended behaviour.
/// \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template <class T, class Visitor>
bool forEachType(Visitor& visitor)
{
if (mState != State_Loaded)
return false;
if (mMergedRefs.empty())
return true;
mHasState = true;
CellRefList<T>& list = get<T>();
for (typename CellRefList<T>::List::iterator it (list.mList.begin()); it!=list.mList.end(); ++it)
{
LiveCellRefBase* base = &*it;
if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end())
continue;
if (!isAccessible(base->mData, base->mRef))
continue;
if (!visitor(MWWorld::Ptr(base, this)))
return false;
}
for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it)
{
LiveCellRefBase* base = it->first;
if (dynamic_cast<LiveCellRef<T>*>(base))
if (!visitor(MWWorld::Ptr(base, this)))
return false;
}
return true;
}
// NOTE: does not account for moved references
// Should be phased out when we have const version of forEach
inline const CellRefList<ESM::Door>& getReadOnlyDoors() const
{
return mDoors;
}
inline const CellRefList<ESM::Static>& getReadOnlyStatics() const
{
return mStatics;
}
bool isExterior() const;
Ptr searchInContainer (const std::string& id);
void loadState (const ESM::CellState& state);
void saveState (ESM::CellState& state) const;
void writeFog (ESM::ESMWriter& writer) const;
void readFog (ESM::ESMReader& reader);
void writeReferences (ESM::ESMWriter& writer) const;
struct GetCellStoreCallback
{
public:
///@note must return nullptr if the cell is not found
virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0;
virtual ~GetCellStoreCallback() = default;
};
/// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved references)
void readReferences (ESM::ESMReader& reader, const std::map<int, int>& contentFileMap, GetCellStoreCallback* callback);
void respawn ();
///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded.
Ptr getMovedActor(int actorId) const;
private:
/// Run through references and store IDs
void listRefs();
void loadRefs();
void loadRef (ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, std::string>& refNumToID);
///< Make case-adjustments to \a ref and insert it into the respective container.
///
/// Invalid \a ref objects are silently dropped.
};
template<>
inline CellRefList<ESM::Activator>& CellStore::get<ESM::Activator>()
{
mHasState = true;
return mActivators;
}
template<>
inline CellRefList<ESM::Potion>& CellStore::get<ESM::Potion>()
{
mHasState = true;
return mPotions;
}
template<>
inline CellRefList<ESM::Apparatus>& CellStore::get<ESM::Apparatus>()
{
mHasState = true;
return mAppas;
}
template<>
inline CellRefList<ESM::Armor>& CellStore::get<ESM::Armor>()
{
mHasState = true;
return mArmors;
}
template<>
inline CellRefList<ESM::Book>& CellStore::get<ESM::Book>()
{
mHasState = true;
return mBooks;
}
template<>
inline CellRefList<ESM::Clothing>& CellStore::get<ESM::Clothing>()
{
mHasState = true;
return mClothes;
}
template<>
inline CellRefList<ESM::Container>& CellStore::get<ESM::Container>()
{
mHasState = true;
return mContainers;
}
template<>
inline CellRefList<ESM::Creature>& CellStore::get<ESM::Creature>()
{
mHasState = true;
return mCreatures;
}
template<>
inline CellRefList<ESM::Door>& CellStore::get<ESM::Door>()
{
mHasState = true;
return mDoors;
}
template<>
inline CellRefList<ESM::Ingredient>& CellStore::get<ESM::Ingredient>()
{
mHasState = true;
return mIngreds;
}
template<>
inline CellRefList<ESM::CreatureLevList>& CellStore::get<ESM::CreatureLevList>()
{
mHasState = true;
return mCreatureLists;
}
template<>
inline CellRefList<ESM::ItemLevList>& CellStore::get<ESM::ItemLevList>()
{
mHasState = true;
return mItemLists;
}
template<>
inline CellRefList<ESM::Light>& CellStore::get<ESM::Light>()
{
mHasState = true;
return mLights;
}
template<>
inline CellRefList<ESM::Lockpick>& CellStore::get<ESM::Lockpick>()
{
mHasState = true;
return mLockpicks;
}
template<>
inline CellRefList<ESM::Miscellaneous>& CellStore::get<ESM::Miscellaneous>()
{
mHasState = true;
return mMiscItems;
}
template<>
inline CellRefList<ESM::NPC>& CellStore::get<ESM::NPC>()
{
mHasState = true;
return mNpcs;
}
template<>
inline CellRefList<ESM::Probe>& CellStore::get<ESM::Probe>()
{
mHasState = true;
return mProbes;
}
template<>
inline CellRefList<ESM::Repair>& CellStore::get<ESM::Repair>()
{
mHasState = true;
return mRepairs;
}
template<>
inline CellRefList<ESM::Static>& CellStore::get<ESM::Static>()
{
mHasState = true;
return mStatics;
}
template<>
inline CellRefList<ESM::Weapon>& CellStore::get<ESM::Weapon>()
{
mHasState = true;
return mWeapons;
}
template<>
inline CellRefList<ESM::BodyPart>& CellStore::get<ESM::BodyPart>()
{
mHasState = true;
return mBodyParts;
}
bool operator== (const CellStore& left, const CellStore& right);
bool operator!= (const CellStore& left, const CellStore& right);
}
#endif