1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-04-18 16:36:45 +00:00

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.
This commit is contained in:
elsid 2022-06-01 22:53:18 +02:00
parent e78d36ff50
commit 3affe9913f
No known key found for this signature in database
GPG key ID: B845CB9FEE18AB40
30 changed files with 420 additions and 169 deletions

View file

@ -15,6 +15,7 @@
#include <components/version/version.hpp> #include <components/version/version.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp> #include <components/vfs/registerarchives.hpp>
#include <components/esm3/readerscache.hpp>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
@ -156,7 +157,7 @@ namespace
Settings::Manager settings; Settings::Manager settings;
settings.load(config); settings.load(config);
std::vector<ESM::ESMReader> readers(contentFiles.size()); ESM::ReadersCache readers;
EsmLoader::Query query; EsmLoader::Query query;
query.mLoadActivators = true; query.mLoadActivators = true;
query.mLoadCells = true; query.mLoadCells = true;

View file

@ -20,6 +20,7 @@
#include <components/version/version.hpp> #include <components/version/version.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp> #include <components/vfs/registerarchives.hpp>
#include <components/esm3/readerscache.hpp>
#include <osg/Vec3f> #include <osg/Vec3f>
@ -174,7 +175,7 @@ namespace NavMeshTool
DetourNavigator::NavMeshDb db(dbPath, maxDbFileSize); DetourNavigator::NavMeshDb db(dbPath, maxDbFileSize);
std::vector<ESM::ESMReader> readers(contentFiles.size()); ESM::ReadersCache readers;
EsmLoader::Query query; EsmLoader::Query query;
query.mLoadActivators = true; query.mLoadActivators = true;
query.mLoadCells = true; query.mLoadCells = true;

View file

@ -20,6 +20,7 @@
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
#include <components/navmeshtool/protocol.hpp> #include <components/navmeshtool/protocol.hpp>
#include <components/esm3/readerscache.hpp>
#include <LinearMath/btVector3.h> #include <LinearMath/btVector3.h>
@ -68,17 +69,17 @@ namespace NavMeshTool
} }
std::vector<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, std::vector<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData,
std::vector<ESM::ESMReader>& readers) ESM::ReadersCache& readers)
{ {
std::vector<EsmLoader::Record<CellRef>> cellRefs; std::vector<EsmLoader::Record<CellRef>> cellRefs;
for (std::size_t i = 0; i < cell.mContextList.size(); i++) for (std::size_t i = 0; i < cell.mContextList.size(); i++)
{ {
ESM::ESMReader& reader = readers[static_cast<std::size_t>(cell.mContextList[i].index)]; ESM::ReadersCache::BusyItem reader = readers.get(static_cast<std::size_t>(cell.mContextList[i].index));
cell.restore(reader, static_cast<int>(i)); cell.restore(*reader, static_cast<int>(i));
ESM::CellRef cellRef; ESM::CellRef cellRef;
bool deleted = false; bool deleted = false;
while (ESM::Cell::getNextRef(reader, cellRef, deleted)) while (ESM::Cell::getNextRef(*reader, cellRef, deleted))
{ {
Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID);
const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); const ESM::RecNameInts type = getType(esmData, cellRef.mRefID);
@ -101,7 +102,7 @@ namespace NavMeshTool
template <class F> template <class F>
void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs,
Resource::BulletShapeManager& bulletShapeManager, std::vector<ESM::ESMReader>& readers, Resource::BulletShapeManager& bulletShapeManager, ESM::ReadersCache& readers,
F&& f) F&& f)
{ {
std::vector<CellRef> cellRefs = loadCellRefs(cell, esmData, readers); std::vector<CellRef> cellRefs = loadCellRefs(cell, esmData, readers);
@ -233,7 +234,7 @@ namespace NavMeshTool
mAabb.m_max = btVector3(0, 0, 0); mAabb.m_max = btVector3(0, 0, 0);
} }
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector<ESM::ESMReader>& readers, WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells, bool writeBinaryLog) bool processInteriorCells, bool writeBinaryLog)
{ {

View file

@ -18,6 +18,7 @@
namespace ESM namespace ESM
{ {
class ESMReader; class ESMReader;
class ReadersCache;
} }
namespace VFS namespace VFS
@ -89,7 +90,7 @@ namespace NavMeshTool
std::vector<std::vector<float>> mHeightfields; std::vector<std::vector<float>> mHeightfields;
}; };
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector<ESM::ESMReader>& readers, WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells, bool writeBinaryLog); bool processInteriorCells, bool writeBinaryLog);
} }

View file

@ -158,8 +158,6 @@ namespace MWBase
virtual const MWWorld::ESMStore& getStore() const = 0; virtual const MWWorld::ESMStore& getStore() const = 0;
virtual std::vector<ESM::ESMReader>& getEsmReader() = 0;
virtual MWWorld::LocalScripts& getLocalScripts() = 0; virtual MWWorld::LocalScripts& getLocalScripts() = 0;
virtual bool hasCellChanged() const = 0; virtual bool hasCellChanged() const = 0;

View file

@ -12,6 +12,7 @@
#include <components/sceneutil/nodecallback.hpp> #include <components/sceneutil/nodecallback.hpp>
#include <components/terrain/quadtreenode.hpp> #include <components/terrain/quadtreenode.hpp>
#include <components/shader/shadermanager.hpp> #include <components/shader/shadermanager.hpp>
#include <components/esm3/readerscache.hpp>
#include "../mwworld/groundcoverstore.hpp" #include "../mwworld/groundcoverstore.hpp"
@ -176,7 +177,7 @@ namespace MWRender
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
DensityCalculator calculator(mDensity); DensityCalculator calculator(mDensity);
std::vector<ESM::ESMReader> esm; ESM::ReadersCache readers;
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
{ {
@ -190,14 +191,13 @@ namespace MWRender
std::map<ESM::RefNum, ESM::CellRef> refs; std::map<ESM::RefNum, ESM::CellRef> refs;
for (size_t i=0; i<cell.mContextList.size(); ++i) for (size_t i=0; i<cell.mContextList.size(); ++i)
{ {
unsigned int index = cell.mContextList[i].index; const std::size_t index = static_cast<std::size_t>(cell.mContextList[i].index);
if (esm.size() <= index) const ESM::ReadersCache::BusyItem reader = readers.get(index);
esm.resize(index+1); cell.restore(*reader, i);
cell.restore(esm[index], i);
ESM::CellRef ref; ESM::CellRef ref;
ref.mRefNum.unset(); ref.mRefNum.unset();
bool deleted = false; bool deleted = false;
while(cell.getNextRef(esm[index], ref, deleted)) while (cell.getNextRef(*reader, ref, deleted))
{ {
if (!deleted && refs.find(ref.mRefNum) == refs.end() && !calculator.isInstanceEnabled()) deleted = true; if (!deleted && refs.find(ref.mRefNum) == refs.end() && !calculator.isInstanceEnabled()) deleted = true;
if (!deleted && !isInChunkBorders(ref, minBound, maxBound)) deleted = true; if (!deleted && !isInChunkBorders(ref, minBound, maxBound)) deleted = true;

View file

@ -17,6 +17,7 @@
#include <components/sceneutil/clone.hpp> #include <components/sceneutil/clone.hpp>
#include <components/sceneutil/util.hpp> #include <components/sceneutil/util.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/esm3/readerscache.hpp>
#include <osgParticle/ParticleProcessor> #include <osgParticle/ParticleProcessor>
#include <osgParticle/ParticleSystemUpdater> #include <osgParticle/ParticleSystemUpdater>
@ -412,7 +413,7 @@ namespace MWRender
osg::Vec3f relativeViewPoint = viewPoint - worldCenter; osg::Vec3f relativeViewPoint = viewPoint - worldCenter;
std::map<ESM::RefNum, ESM::CellRef> refs; std::map<ESM::RefNum, ESM::CellRef> refs;
std::vector<ESM::ESMReader> esm; ESM::ReadersCache readers;
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
@ -425,17 +426,16 @@ namespace MWRender
{ {
try try
{ {
unsigned int index = cell->mContextList[i].index; const std::size_t index = static_cast<std::size_t>(cell->mContextList[i].index);
if (esm.size()<=index) const ESM::ReadersCache::BusyItem reader = readers.get(index);
esm.resize(index+1); cell->restore(*reader, i);
cell->restore(esm[index], i);
ESM::CellRef ref; ESM::CellRef ref;
ref.mRefNum.unset(); ref.mRefNum.unset();
ESM::MovedCellRef cMRef; ESM::MovedCellRef cMRef;
cMRef.mRefNum.mIndex = 0; cMRef.mRefNum.mIndex = 0;
bool deleted = false; bool deleted = false;
bool moved = false; bool moved = false;
while (ESM::Cell::getNextRef(esm[index], ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) while (ESM::Cell::getNextRef(*reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved))
{ {
if (moved) if (moved)
continue; continue;

View file

@ -68,9 +68,7 @@ MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell)
std::map<std::string, CellStore>::iterator result = mInteriors.find (lowerName); std::map<std::string, CellStore>::iterator result = mInteriors.find (lowerName);
if (result==mInteriors.end()) if (result==mInteriors.end())
{ result = mInteriors.emplace(std::move(lowerName), CellStore(cell, mStore, mReaders)).first;
result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).first;
}
return &result->second; return &result->second;
} }
@ -80,11 +78,8 @@ MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell)
mExteriors.find (std::make_pair (cell->getGridX(), cell->getGridY())); mExteriors.find (std::make_pair (cell->getGridX(), cell->getGridY()));
if (result==mExteriors.end()) if (result==mExteriors.end())
{ result = mExteriors.emplace(std::make_pair(cell->getGridX(), cell->getGridY()),
result = mExteriors.insert (std::make_pair ( CellStore(cell, mStore, mReaders)).first;
std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell, mStore, mReader))).first;
}
return &result->second; return &result->second;
} }
@ -130,9 +125,10 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const
writer.endRecord (ESM::REC_CSTA); writer.endRecord (ESM::REC_CSTA);
} }
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader) MWWorld::Cells::Cells (const MWWorld::ESMStore& store, ESM::ReadersCache& readers)
: mStore (store), mReader (reader), : mStore(store)
mIdCacheIndex (0) , mReaders(readers)
, mIdCacheIndex(0)
{ {
int cacheSize = std::clamp(Settings::Manager::getInt("pointers cache size", "Cells"), 40, 1000); int cacheSize = std::clamp(Settings::Manager::getInt("pointers cache size", "Cells"), 40, 1000);
mIdCache = IdCache(cacheSize, std::pair<std::string, CellStore *> ("", (CellStore*)nullptr)); mIdCache = IdCache(cacheSize, std::pair<std::string, CellStore *> ("", (CellStore*)nullptr));
@ -165,8 +161,7 @@ MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y)
cell = MWBase::Environment::get().getWorld()->createRecord (record); cell = MWBase::Environment::get().getWorld()->createRecord (record);
} }
result = mExteriors.insert (std::make_pair ( result = mExteriors.emplace(std::make_pair(x, y), CellStore(cell, mStore, mReaders)).first;
std::make_pair (x, y), CellStore (cell, mStore, mReader))).first;
} }
if (result->second.getState()!=CellStore::State_Loaded) if (result->second.getState()!=CellStore::State_Loaded)
@ -186,7 +181,7 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name)
{ {
const ESM::Cell *cell = mStore.get<ESM::Cell>().find(lowerName); const ESM::Cell *cell = mStore.get<ESM::Cell>().find(lowerName);
result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).first; result = mInteriors.emplace(std::move(lowerName), CellStore(cell, mStore, mReaders)).first;
} }
if (result->second.getState()!=CellStore::State_Loaded) if (result->second.getState()!=CellStore::State_Loaded)

View file

@ -11,6 +11,7 @@ namespace ESM
{ {
class ESMReader; class ESMReader;
class ESMWriter; class ESMWriter;
class ReadersCache;
struct CellId; struct CellId;
struct Cell; struct Cell;
struct RefNum; struct RefNum;
@ -30,7 +31,7 @@ namespace MWWorld
{ {
typedef std::vector<std::pair<std::string, CellStore *> > IdCache; typedef std::vector<std::pair<std::string, CellStore *> > IdCache;
const MWWorld::ESMStore& mStore; const MWWorld::ESMStore& mStore;
std::vector<ESM::ESMReader>& mReader; ESM::ReadersCache& mReaders;
mutable std::map<std::string, CellStore> mInteriors; mutable std::map<std::string, CellStore> mInteriors;
mutable std::map<std::pair<int, int>, CellStore> mExteriors; mutable std::map<std::pair<int, int>, CellStore> mExteriors;
IdCache mIdCache; IdCache mIdCache;
@ -51,7 +52,7 @@ namespace MWWorld
void clear(); void clear();
Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader); explicit Cells(const MWWorld::ESMStore& store, ESM::ReadersCache& reader);
CellStore *getExterior (int x, int y); CellStore *getExterior (int x, int y);

View file

@ -17,6 +17,7 @@
#include <components/esm3/fogstate.hpp> #include <components/esm3/fogstate.hpp>
#include <components/esm3/creaturelevliststate.hpp> #include <components/esm3/creaturelevliststate.hpp>
#include <components/esm3/doorstate.hpp> #include <components/esm3/doorstate.hpp>
#include <components/esm3/readerscache.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp" #include "../mwbase/luamanager.hpp"
@ -387,8 +388,14 @@ namespace MWWorld
return false; return false;
} }
CellStore::CellStore (const ESM::Cell *cell, const MWWorld::ESMStore& esmStore, std::vector<ESM::ESMReader>& readerList) CellStore::CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers)
: mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0), mRechargingItemsUpToDate(false) : mStore(esmStore)
, mReaders(readers)
, mCell(cell)
, mState(State_Unloaded)
, mHasState(false)
, mLastRespawn(0, 0)
, mRechargingItemsUpToDate(false)
{ {
mWaterLevel = cell->mWater; mWaterLevel = cell->mWater;
} }
@ -545,8 +552,6 @@ namespace MWWorld
void CellStore::listRefs() void CellStore::listRefs()
{ {
std::vector<ESM::ESMReader>& esm = mReader;
assert (mCell); assert (mCell);
if (mCell->mContextList.empty()) if (mCell->mContextList.empty())
@ -558,8 +563,9 @@ namespace MWWorld
try try
{ {
// Reopen the ESM reader and seek to the right position. // Reopen the ESM reader and seek to the right position.
int index = mCell->mContextList[i].index; const std::size_t index = static_cast<std::size_t>(mCell->mContextList[i].index);
mCell->restore (esm[index], i); const ESM::ReadersCache::BusyItem reader = mReaders.get(index);
mCell->restore(*reader, i);
ESM::CellRef ref; ESM::CellRef ref;
@ -568,7 +574,7 @@ namespace MWWorld
cMRef.mRefNum.mIndex = 0; cMRef.mRefNum.mIndex = 0;
bool deleted = false; bool deleted = false;
bool moved = false; bool moved = false;
while (ESM::Cell::getNextRef(esm[index], ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) while (ESM::Cell::getNextRef(*reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved))
{ {
if (deleted || moved) if (deleted || moved)
continue; continue;
@ -602,8 +608,6 @@ namespace MWWorld
void CellStore::loadRefs() void CellStore::loadRefs()
{ {
std::vector<ESM::ESMReader>& esm = mReader;
assert (mCell); assert (mCell);
if (mCell->mContextList.empty()) if (mCell->mContextList.empty())
@ -617,8 +621,9 @@ namespace MWWorld
try try
{ {
// Reopen the ESM reader and seek to the right position. // Reopen the ESM reader and seek to the right position.
int index = mCell->mContextList[i].index; const std::size_t index = static_cast<std::size_t>(mCell->mContextList[i].index);
mCell->restore (esm[index], i); const ESM::ReadersCache::BusyItem reader = mReaders.get(index);
mCell->restore(*reader, i);
ESM::CellRef ref; ESM::CellRef ref;
ref.mRefNum.unset(); ref.mRefNum.unset();
@ -628,7 +633,7 @@ namespace MWWorld
cMRef.mRefNum.mIndex = 0; cMRef.mRefNum.mIndex = 0;
bool deleted = false; bool deleted = false;
bool moved = false; bool moved = false;
while (ESM::Cell::getNextRef(esm[index], ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) while (ESM::Cell::getNextRef(*reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved))
{ {
if (moved) if (moved)
continue; continue;

View file

@ -38,6 +38,7 @@
namespace ESM namespace ESM
{ {
class ReadersCache;
struct Cell; struct Cell;
struct CellState; struct CellState;
struct CellId; struct CellId;
@ -61,7 +62,7 @@ namespace MWWorld
private: private:
const MWWorld::ESMStore& mStore; const MWWorld::ESMStore& mStore;
std::vector<ESM::ESMReader>& mReader; ESM::ReadersCache& mReaders;
// Even though fog actually belongs to the player and not cells, // 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. // it makes sense to store it here since we need it once for each cell.
@ -211,9 +212,7 @@ namespace MWWorld
} }
/// @param readerList The readers to use for loading of the cell on-demand. /// @param readerList The readers to use for loading of the cell on-demand.
CellStore (const ESM::Cell *cell_, CellStore(const ESM::Cell* cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers);
const MWWorld::ESMStore& store,
std::vector<ESM::ESMReader>& readerList);
const ESM::Cell *getCell() const; const ESM::Cell *getCell() const;

View file

@ -2,13 +2,13 @@
#include "esmstore.hpp" #include "esmstore.hpp"
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
#include <components/esm3/readerscache.hpp>
namespace MWWorld namespace MWWorld
{ {
EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& readers, EsmLoader::EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder)
ToUTF8::Utf8Encoder* encoder) : mReaders(readers)
: mEsm(readers)
, mStore(store) , mStore(store)
, mEncoder(encoder) , mEncoder(encoder)
, mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the previous file's dialogue , mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the previous file's dialogue
@ -17,13 +17,25 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& read
void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener)
{ {
ESM::ESMReader lEsm; const ESM::ReadersCache::BusyItem reader = mReaders.get(static_cast<std::size_t>(index));
lEsm.setEncoder(mEncoder);
lEsm.setIndex(index); reader->setEncoder(mEncoder);
lEsm.open(filepath.string()); reader->setIndex(index);
lEsm.resolveParentFileIndices(mEsm); reader->open(filepath.string());
mEsm[index] = std::move(lEsm); reader->resolveParentFileIndices(mReaders);
mStore.load(mEsm[index], listener, mDialogue);
assert(reader->getGameFiles().size() == reader->getParentFileIndices().size());
for (std::size_t i = 0, n = reader->getParentFileIndices().size(); i < n; ++i)
if (i == static_cast<std::size_t>(reader->getIndex()))
throw std::runtime_error("File " + reader->getName() + " asks for parent file "
+ reader->getGameFiles()[i].name
+ ", but it is not available or has been loaded in the wrong order. "
"Please run the launcher to fix this issue.");
mStore.load(*reader, listener, mDialogue);
if (!mMasterFileFormat.has_value() && Misc::StringUtils::ciEndsWith(reader->getName(), ".esm") && !Misc::StringUtils::ciEndsWith(reader->getName(), ".omwgame"))
mMasterFileFormat = reader->getFormat();
} }
} /* namespace MWWorld */ } /* namespace MWWorld */

View file

@ -1,7 +1,7 @@
#ifndef ESMLOADER_HPP #ifndef ESMLOADER_HPP
#define ESMLOADER_HPP #define ESMLOADER_HPP
#include <vector> #include <optional>
#include "contentloader.hpp" #include "contentloader.hpp"
@ -12,7 +12,7 @@ namespace ToUTF8
namespace ESM namespace ESM
{ {
class ESMReader; class ReadersCache;
struct Dialogue; struct Dialogue;
} }
@ -23,16 +23,18 @@ class ESMStore;
struct EsmLoader : public ContentLoader struct EsmLoader : public ContentLoader
{ {
EsmLoader(MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& readers, explicit EsmLoader(MWWorld::ESMStore& store, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder);
ToUTF8::Utf8Encoder* encoder);
std::optional<int> getMasterFileFormat() const { return mMasterFileFormat; }
void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) override; void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) override;
private: private:
std::vector<ESM::ESMReader>& mEsm; ESM::ReadersCache& mReaders;
MWWorld::ESMStore& mStore; MWWorld::ESMStore& mStore;
ToUTF8::Utf8Encoder* mEncoder; ToUTF8::Utf8Encoder* mEncoder;
ESM::Dialogue* mDialogue; ESM::Dialogue* mDialogue;
std::optional<int> mMasterFileFormat;
}; };
} /* namespace MWWorld */ } /* namespace MWWorld */

View file

@ -9,6 +9,7 @@
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/lua/configuration.hpp> #include <components/lua/configuration.hpp>
#include <components/misc/algorithm.hpp> #include <components/misc/algorithm.hpp>
#include <components/esm3/readerscache.hpp>
#include "../mwmechanics/spelllist.hpp" #include "../mwmechanics/spelllist.hpp"
@ -24,19 +25,18 @@ namespace
constexpr std::size_t deletedRefID = std::numeric_limits<std::size_t>::max(); constexpr std::size_t deletedRefID = std::numeric_limits<std::size_t>::max();
void readRefs(const ESM::Cell& cell, std::vector<Ref>& refs, std::vector<std::string>& refIDs, std::vector<ESM::ESMReader>& readers) void readRefs(const ESM::Cell& cell, std::vector<Ref>& refs, std::vector<std::string>& refIDs, ESM::ReadersCache& readers)
{ {
// TODO: we have many similar copies of this code. // TODO: we have many similar copies of this code.
for (size_t i = 0; i < cell.mContextList.size(); i++) for (size_t i = 0; i < cell.mContextList.size(); i++)
{ {
size_t index = cell.mContextList[i].index; const std::size_t index = static_cast<std::size_t>(cell.mContextList[i].index);
if (readers.size() <= index) const ESM::ReadersCache::BusyItem reader = readers.get(index);
readers.resize(index + 1); cell.restore(*reader, i);
cell.restore(readers[index], i);
ESM::CellRef ref; ESM::CellRef ref;
ref.mRefNum.unset(); ref.mRefNum.unset();
bool deleted = false; bool deleted = false;
while(cell.getNextRef(readers[index], ref, deleted)) while (cell.getNextRef(*reader, ref, deleted))
{ {
if(deleted) if(deleted)
refs.emplace_back(ref.mRefNum, deletedRefID); refs.emplace_back(ref.mRefNum, deletedRefID);
@ -241,7 +241,7 @@ ESM::LuaScriptsCfg ESMStore::getLuaScriptsCfg() const
return cfg; return cfg;
} }
void ESMStore::setUp(bool validateRecords) void ESMStore::setUp()
{ {
mIds.clear(); mIds.clear();
@ -267,15 +267,15 @@ void ESMStore::setUp(bool validateRecords)
mMagicEffects.setUp(); mMagicEffects.setUp();
mAttributes.setUp(); mAttributes.setUp();
mDialogs.setUp(); mDialogs.setUp();
if (validateRecords)
{
validate();
countAllCellRefs();
}
} }
void ESMStore::countAllCellRefs() void ESMStore::validateRecords(ESM::ReadersCache& readers)
{
validate();
countAllCellRefs(readers);
}
void ESMStore::countAllCellRefs(ESM::ReadersCache& readers)
{ {
// TODO: We currently need to read entire files here again. // TODO: We currently need to read entire files here again.
// We should consider consolidating or deferring this reading. // We should consider consolidating or deferring this reading.
@ -283,7 +283,6 @@ void ESMStore::countAllCellRefs()
return; return;
std::vector<Ref> refs; std::vector<Ref> refs;
std::vector<std::string> refIDs; std::vector<std::string> refIDs;
std::vector<ESM::ESMReader> readers;
for(auto it = mCells.intBegin(); it != mCells.intEnd(); ++it) for(auto it = mCells.intBegin(); it != mCells.intEnd(); ++it)
readRefs(*it, refs, refIDs, readers); readRefs(*it, refs, refIDs, readers);
for(auto it = mCells.extBegin(); it != mCells.extEnd(); ++it) for(auto it = mCells.extBegin(); it != mCells.extEnd(); ++it)

View file

@ -20,6 +20,11 @@ namespace MWMechanics
class SpellList; class SpellList;
} }
namespace ESM
{
class ReadersCache;
}
namespace MWWorld namespace MWWorld
{ {
class ESMStore class ESMStore
@ -90,7 +95,7 @@ namespace MWWorld
/// Validate entries in store after setup /// Validate entries in store after setup
void validate(); void validate();
void countAllCellRefs(); void countAllCellRefs(ESM::ReadersCache& readers);
template<class T> template<class T>
void removeMissingObjects(Store<T>& store); void removeMissingObjects(Store<T>& store);
@ -266,7 +271,8 @@ namespace MWWorld
// This method must be called once, after loading all master/plugin files. This can only be done // This method must be called once, after loading all master/plugin files. This can only be done
// from the outside, so it must be public. // from the outside, so it must be public.
void setUp(bool validateRecords = false); void setUp();
void validateRecords(ESM::ReadersCache& readers);
int countSavedGameRecords() const; int countSavedGameRecords() const;

View file

@ -2,6 +2,7 @@
#include <components/esmloader/load.hpp> #include <components/esmloader/load.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/esm3/readerscache.hpp>
namespace MWWorld namespace MWWorld
{ {
@ -11,7 +12,7 @@ namespace MWWorld
query.mLoadStatics = true; query.mLoadStatics = true;
query.mLoadCells = true; query.mLoadCells = true;
std::vector<ESM::ESMReader> readers(groundcoverFiles.size()); ESM::ReadersCache readers;
const ::EsmLoader::EsmData content = ::EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder); const ::EsmLoader::EsmData content = ::EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder);
for (const ESM::Static& stat : statics) for (const ESM::Static& stat : statics)

View file

@ -145,8 +145,8 @@ namespace MWWorld
ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride,
const std::string& startCell, const std::string& startupScript, const std::string& startCell, const std::string& startupScript,
const std::string& resourcePath, const std::string& userDataPath) const std::string& resourcePath, const std::string& userDataPath)
: mResourceSystem(resourceSystem), mLocalScripts (mStore), : mResourceSystem(resourceSystem), mLocalScripts(mStore),
mCells (mStore, mEsm), mSky (true), mCells(mStore, mReaders), mSky(true),
mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles), mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles),
mUserDataPath(userDataPath), mUserDataPath(userDataPath),
mDefaultHalfExtents(Settings::Manager::getVector3("default actor pathfind half extents", "Game")), mDefaultHalfExtents(Settings::Manager::getVector3("default actor pathfind half extents", "Game")),
@ -156,30 +156,20 @@ namespace MWWorld
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
{ {
mEsm.resize(contentFiles.size());
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
listener->loadingOn(); listener->loadingOn();
loadContentFiles(fileCollections, contentFiles, mStore, mEsm, encoder, listener); loadContentFiles(fileCollections, contentFiles, encoder, listener);
loadGroundcoverFiles(fileCollections, groundcoverFiles, encoder); loadGroundcoverFiles(fileCollections, groundcoverFiles, encoder);
listener->loadingOff(); listener->loadingOff();
// Find main game file
for (const ESM::ESMReader& reader : mEsm)
{
if (!Misc::StringUtils::ciEndsWith(reader.getName(), ".esm") && !Misc::StringUtils::ciEndsWith(reader.getName(), ".omwgame"))
continue;
if (reader.getFormat() == 0)
ensureNeededRecords(); // and insert records that may not be present in all versions of MW.
break;
}
mCurrentDate = std::make_unique<DateTimeManager>(); mCurrentDate = std::make_unique<DateTimeManager>();
fillGlobalVariables(); fillGlobalVariables();
mStore.setUp(true); mStore.setUp();
mStore.validateRecords(mReaders);
mStore.movePlayerRecord(); mStore.movePlayerRecord();
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat(); mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
@ -417,23 +407,6 @@ namespace MWWorld
} }
} }
void World::validateMasterFiles(const std::vector<ESM::ESMReader>& readers)
{
for (const auto& esm : readers)
{
assert(esm.getGameFiles().size() == esm.getParentFileIndices().size());
for (unsigned int i=0; i<esm.getParentFileIndices().size(); ++i)
{
if (!esm.isValidParentFileIndex(i))
{
std::string fstring = "File " + esm.getName() + " asks for parent file " + esm.getGameFiles()[i].name
+ ", but it is not available or has been loaded in the wrong order. Please run the launcher to fix this issue.";
throw std::runtime_error(fstring);
}
}
}
}
void World::ensureNeededRecords() void World::ensureNeededRecords()
{ {
std::map<std::string, ESM::Variant> gmst; std::map<std::string, ESM::Variant> gmst;
@ -641,11 +614,6 @@ namespace MWWorld
return mStore; return mStore;
} }
std::vector<ESM::ESMReader>& World::getEsmReader()
{
return mEsm;
}
LocalScripts& World::getLocalScripts() LocalScripts& World::getLocalScripts()
{ {
return mLocalScripts; return mLocalScripts;
@ -2939,11 +2907,11 @@ namespace MWWorld
return mScriptsEnabled; return mScriptsEnabled;
} }
void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector<std::string>& content, ESMStore& store, std::vector<ESM::ESMReader>& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector<std::string>& content,
ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener)
{ {
GameContentLoader gameContentLoader; GameContentLoader gameContentLoader;
EsmLoader esmLoader(store, readers, encoder); EsmLoader esmLoader(mStore, mReaders, encoder);
validateMasterFiles(readers);
gameContentLoader.addLoader(".esm", esmLoader); gameContentLoader.addLoader(".esm", esmLoader);
gameContentLoader.addLoader(".esp", esmLoader); gameContentLoader.addLoader(".esp", esmLoader);
@ -2951,7 +2919,7 @@ namespace MWWorld
gameContentLoader.addLoader(".omwaddon", esmLoader); gameContentLoader.addLoader(".omwaddon", esmLoader);
gameContentLoader.addLoader(".project", esmLoader); gameContentLoader.addLoader(".project", esmLoader);
OMWScriptsLoader omwScriptsLoader(store); OMWScriptsLoader omwScriptsLoader(mStore);
gameContentLoader.addLoader(".omwscripts", omwScriptsLoader); gameContentLoader.addLoader(".omwscripts", omwScriptsLoader);
int idx = 0; int idx = 0;
@ -2970,6 +2938,9 @@ namespace MWWorld
} }
idx++; idx++;
} }
if (const auto v = esmLoader.getMasterFileFormat(); v.has_value() && *v == 0)
ensureNeededRecords(); // Insert records that may not be present in all versions of master files.
} }
void World::loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder) void World::loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder)

View file

@ -5,6 +5,7 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/esm3/readerscache.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -81,7 +82,7 @@ namespace MWWorld
private: private:
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
std::vector<ESM::ESMReader> mEsm; ESM::ReadersCache mReaders;
MWWorld::ESMStore mStore; MWWorld::ESMStore mStore;
GroundcoverStore mGroundcoverStore; GroundcoverStore mGroundcoverStore;
LocalScripts mLocalScripts; LocalScripts mLocalScripts;
@ -162,13 +163,13 @@ namespace MWWorld
void updateNavigatorObject(const MWPhysics::Object& object); void updateNavigatorObject(const MWPhysics::Object& object);
void ensureNeededRecords(); void ensureNeededRecords();
void validateMasterFiles(const std::vector<ESM::ESMReader>& readers);
void fillGlobalVariables(); void fillGlobalVariables();
void updateSkyDate(); void updateSkyDate();
void loadContentFiles(const Files::Collections& fileCollections, const std::vector<std::string>& content, ESMStore& store, std::vector<ESM::ESMReader>& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener); void loadContentFiles(const Files::Collections& fileCollections, const std::vector<std::string>& content,
ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener);
void loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder); void loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector<std::string>& groundcoverFiles, ToUTF8::Utf8Encoder* encoder);
@ -241,8 +242,6 @@ namespace MWWorld
const MWWorld::ESMStore& getStore() const override; const MWWorld::ESMStore& getStore() const override;
std::vector<ESM::ESMReader>& getEsmReader() override;
LocalScripts& getLocalScripts() override; LocalScripts& getLocalScripts() override;
bool hasCellChanged() const override; bool hasCellChanged() const override;

View file

@ -81,6 +81,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
fx/lexer.cpp fx/lexer.cpp
fx/technique.cpp fx/technique.cpp
esm3/readerscache.cpp
) )
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})

View file

@ -0,0 +1,91 @@
#include <components/esm3/readerscache.hpp>
#include <components/files/collections.hpp>
#include <components/files/multidircollection.hpp>
#include <gtest/gtest.h>
#ifndef OPENMW_DATA_DIR
#error "OPENMW_DATA_DIR is not defined"
#endif
namespace
{
using namespace testing;
using namespace ESM;
TEST(ESM3ReadersCache, onAttemptToRequestTheSameReaderTwiceShouldThrowException)
{
ReadersCache readers(1);
const ReadersCache::BusyItem reader = readers.get(0);
EXPECT_THROW(readers.get(0), std::logic_error);
}
TEST(ESM3ReadersCache, shouldAllowToHaveBusyItemsMoreThanCapacity)
{
ReadersCache readers(1);
const ReadersCache::BusyItem reader0 = readers.get(0);
const ReadersCache::BusyItem reader1 = readers.get(1);
}
TEST(ESM3ReadersCache, shouldKeepClosedReleasedClosedItem)
{
ReadersCache readers(1);
readers.get(0);
const ReadersCache::BusyItem reader = readers.get(0);
EXPECT_FALSE(reader->isOpen());
}
struct ESM3ReadersCacheWithContentFile : Test
{
static constexpr std::size_t sInitialOffset = 324;
static constexpr std::size_t sSkip = 100;
const Files::PathContainer mDataDirs {{std::string(OPENMW_DATA_DIR)}};
const Files::Collections mFileCollections {mDataDirs, true};
const std::string mContentFile = "template.omwgame";
const std::string mContentFilePath = mFileCollections.getCollection(".omwgame").getPath(mContentFile).string();
};
TEST_F(ESM3ReadersCacheWithContentFile, shouldKeepOpenReleasedOpenReader)
{
ReadersCache readers(1);
{
const ReadersCache::BusyItem reader = readers.get(0);
reader->open(mContentFilePath);
ASSERT_TRUE(reader->isOpen());
ASSERT_EQ(reader->getFileOffset(), sInitialOffset);
ASSERT_GT(reader->getFileSize(), sInitialOffset + sSkip);
reader->skip(sSkip);
ASSERT_EQ(reader->getFileOffset(), sInitialOffset + sSkip);
}
{
const ReadersCache::BusyItem reader = readers.get(0);
EXPECT_TRUE(reader->isOpen());
EXPECT_EQ(reader->getName(), mContentFilePath);
EXPECT_EQ(reader->getFileOffset(), sInitialOffset + sSkip);
}
}
TEST_F(ESM3ReadersCacheWithContentFile, shouldCloseFreeReaderWhenReachingCapacityLimit)
{
ReadersCache readers(1);
{
const ReadersCache::BusyItem reader = readers.get(0);
reader->open(mContentFilePath);
ASSERT_TRUE(reader->isOpen());
ASSERT_EQ(reader->getFileOffset(), sInitialOffset);
ASSERT_GT(reader->getFileSize(), sInitialOffset + sSkip);
reader->skip(sSkip);
ASSERT_EQ(reader->getFileOffset(), sInitialOffset + sSkip);
}
{
const ReadersCache::BusyItem reader = readers.get(1);
reader->open(mContentFilePath);
ASSERT_TRUE(reader->isOpen());
}
{
const ReadersCache::BusyItem reader = readers.get(0);
EXPECT_TRUE(reader->isOpen());
EXPECT_EQ(reader->getFileOffset(), sInitialOffset);
}
}
}

View file

@ -10,6 +10,7 @@
#include <components/files/collections.hpp> #include <components/files/collections.hpp>
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/to_utf8/to_utf8.hpp> #include <components/to_utf8/to_utf8.hpp>
#include <components/esm3/readerscache.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -39,7 +40,7 @@ namespace
query.mLoadGameSettings = true; query.mLoadGameSettings = true;
query.mLoadLands = true; query.mLoadLands = true;
query.mLoadStatics = true; query.mLoadStatics = true;
std::vector<ESM::ESMReader> readers(mContentFiles.size()); ESM::ReadersCache readers;
ToUTF8::Utf8Encoder* const encoder = nullptr; ToUTF8::Utf8Encoder* const encoder = nullptr;
const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder);
EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mActivators.size(), 0);
@ -61,7 +62,7 @@ namespace
query.mLoadGameSettings = true; query.mLoadGameSettings = true;
query.mLoadLands = true; query.mLoadLands = true;
query.mLoadStatics = true; query.mLoadStatics = true;
std::vector<ESM::ESMReader> readers(mContentFiles.size()); ESM::ReadersCache readers;
ToUTF8::Utf8Encoder* const encoder = nullptr; ToUTF8::Utf8Encoder* const encoder = nullptr;
const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder);
EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mActivators.size(), 0);
@ -83,7 +84,7 @@ namespace
query.mLoadGameSettings = false; query.mLoadGameSettings = false;
query.mLoadLands = true; query.mLoadLands = true;
query.mLoadStatics = true; query.mLoadStatics = true;
std::vector<ESM::ESMReader> readers(mContentFiles.size()); ESM::ReadersCache readers;
ToUTF8::Utf8Encoder* const encoder = nullptr; ToUTF8::Utf8Encoder* const encoder = nullptr;
const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder);
EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mActivators.size(), 0);
@ -98,7 +99,7 @@ namespace
TEST_F(EsmLoaderTest, shouldIgnoreAllWithDefaultQuery) TEST_F(EsmLoaderTest, shouldIgnoreAllWithDefaultQuery)
{ {
const Query query; const Query query;
std::vector<ESM::ESMReader> readers(mContentFiles.size()); ESM::ReadersCache readers;
ToUTF8::Utf8Encoder* const encoder = nullptr; ToUTF8::Utf8Encoder* const encoder = nullptr;
const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder);
EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mActivators.size(), 0);
@ -121,7 +122,7 @@ namespace
query.mLoadLands = true; query.mLoadLands = true;
query.mLoadStatics = true; query.mLoadStatics = true;
const std::vector<std::string> contentFiles {{"script.omwscripts"}}; const std::vector<std::string> contentFiles {{"script.omwscripts"}};
std::vector<ESM::ESMReader> readers(contentFiles.size()); ESM::ReadersCache readers;
ToUTF8::Utf8Encoder* const encoder = nullptr; ToUTF8::Utf8Encoder* const encoder = nullptr;
const EsmData esmData = loadEsmData(query, contentFiles, mFileCollections, readers, encoder); const EsmData esmData = loadEsmData(query, contentFiles, mFileCollections, readers, encoder);
EXPECT_EQ(esmData.mActivators.size(), 0); EXPECT_EQ(esmData.mActivators.size(), 0);

View file

@ -95,7 +95,7 @@ add_component_dir (esm3
savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap
inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats
weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile
aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache
) )
add_component_dir (esm3terrain add_component_dir (esm3terrain

View file

@ -1,5 +1,7 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "readerscache.hpp"
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/files/openfile.hpp> #include <components/files/openfile.hpp>
@ -61,21 +63,22 @@ void ESMReader::clearCtx()
mCtx.subName.clear(); mCtx.subName.clear();
} }
void ESMReader::resolveParentFileIndices(const std::vector<ESMReader>& allPlugins) void ESMReader::resolveParentFileIndices(ReadersCache& readers)
{ {
mCtx.parentFileIndices.clear(); mCtx.parentFileIndices.clear();
const std::vector<Header::MasterData> &masters = getGameFiles(); for (const Header::MasterData &mast : getGameFiles())
for (size_t j = 0; j < masters.size(); j++) { {
const Header::MasterData &mast = masters[j]; const std::string& fname = mast.name;
std::string fname = mast.name;
int index = getIndex(); int index = getIndex();
for (int i = 0; i < getIndex(); i++) { for (int i = 0; i < getIndex(); i++)
const ESMReader& reader = allPlugins.at(i); {
if (reader.getFileSize() == 0) const ESM::ReadersCache::BusyItem reader = readers.get(static_cast<std::size_t>(i));
if (reader->getFileSize() == 0)
continue; // Content file in non-ESM format continue; // Content file in non-ESM format
const std::string& candidate = reader.getName(); const std::string& candidate = reader->getName();
std::string fnamecandidate = std::filesystem::path(candidate).filename().string(); std::string fnamecandidate = std::filesystem::path(candidate).filename().string();
if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { if (Misc::StringUtils::ciEqual(fname, fnamecandidate))
{
index = i; index = i;
break; break;
} }

View file

@ -16,6 +16,8 @@
namespace ESM namespace ESM
{ {
class ReadersCache;
class ESMReader class ESMReader
{ {
public: public:
@ -39,6 +41,7 @@ public:
const NAME &retSubName() const { return mCtx.subName; } const NAME &retSubName() const { return mCtx.subName; }
uint32_t getSubSize() const { return mCtx.leftSub; } uint32_t getSubSize() const { return mCtx.leftSub; }
const std::string& getName() const { return mCtx.filename; }; const std::string& getName() const { return mCtx.filename; };
bool isOpen() const { return mEsm != nullptr; }
/************************************************************************* /*************************************************************************
* *
@ -85,9 +88,8 @@ public:
// all files/readers used by the engine. This is required for correct adjustRefNum() results // all files/readers used by the engine. This is required for correct adjustRefNum() results
// as required for handling moved, deleted and edited CellRefs. // as required for handling moved, deleted and edited CellRefs.
/// @note Does not validate. /// @note Does not validate.
void resolveParentFileIndices(const std::vector<ESMReader>& files); void resolveParentFileIndices(ReadersCache& readers);
const std::vector<int>& getParentFileIndices() const { return mCtx.parentFileIndices; } const std::vector<int>& getParentFileIndices() const { return mCtx.parentFileIndices; }
bool isValidParentFileIndex(int i) const { return i != getIndex(); }
/************************************************************************* /*************************************************************************
* *

View file

@ -0,0 +1,87 @@
#include "readerscache.hpp"
#include <stdexcept>
namespace ESM
{
ReadersCache::BusyItem::BusyItem(ReadersCache& owner, std::list<Item>::iterator item) noexcept
: mOwner(owner)
, mItem(item)
{}
ReadersCache::BusyItem::~BusyItem() noexcept
{
mOwner.releaseItem(mItem);
}
ReadersCache::ReadersCache(std::size_t capacity)
: mCapacity(capacity)
{}
ReadersCache::BusyItem ReadersCache::get(std::size_t index)
{
const auto indexIt = mIndex.find(index);
std::list<Item>::iterator it;
if (indexIt == mIndex.end())
{
closeExtraReaders();
it = mBusyItems.emplace(mBusyItems.end());
mIndex.emplace(index, it);
}
else
{
switch (indexIt->second->mState)
{
case State::Busy:
throw std::logic_error("ESMReader at index " + std::to_string(index) + " is busy");
case State::Free:
it = indexIt->second;
mBusyItems.splice(mBusyItems.end(), mFreeItems, it);
break;
case State::Closed:
closeExtraReaders();
it = indexIt->second;
if (it->mName.has_value())
{
it->mReader.open(*it->mName);
it->mName.reset();
}
mBusyItems.splice(mBusyItems.end(), mClosedItems, it);
break;
}
it->mState = State::Busy;
}
return BusyItem(*this, it);
}
void ReadersCache::closeExtraReaders()
{
while (!mFreeItems.empty() && mBusyItems.size() + mFreeItems.size() + 1 > mCapacity)
{
const auto it = mFreeItems.begin();
if (it->mReader.isOpen())
{
it->mName = it->mReader.getName();
it->mReader.close();
}
mClosedItems.splice(mClosedItems.end(), mFreeItems, it);
it->mState = State::Closed;
}
}
void ReadersCache::releaseItem(std::list<Item>::iterator it) noexcept
{
assert(it->mState == State::Busy);
if (it->mReader.isOpen())
{
mFreeItems.splice(mFreeItems.end(), mBusyItems, it);
it->mState = State::Free;
}
else
{
mClosedItems.splice(mClosedItems.end(), mBusyItems, it);
it->mState = State::Closed;
}
}
}

View file

@ -0,0 +1,71 @@
#ifndef OPENMW_COMPONENTS_ESM3_READERSCACHE_H
#define OPENMW_COMPONENTS_ESM3_READERSCACHE_H
#include "esmreader.hpp"
#include <cstddef>
#include <list>
#include <map>
#include <optional>
#include <string>
namespace ESM
{
class ReadersCache
{
private:
enum class State
{
Busy,
Free,
Closed,
};
struct Item
{
State mState = State::Busy;
ESMReader mReader;
std::optional<std::string> mName;
Item() = default;
};
public:
class BusyItem
{
public:
explicit BusyItem(ReadersCache& owner, std::list<Item>::iterator item) noexcept;
BusyItem(const BusyItem& other) = delete;
~BusyItem() noexcept;
BusyItem& operator=(const BusyItem& other) = delete;
ESMReader& operator*() const noexcept { return mItem->mReader; }
ESMReader* operator->() const noexcept { return &mItem->mReader; }
private:
ReadersCache& mOwner;
std::list<Item>::iterator mItem;
};
explicit ReadersCache(std::size_t capacity = 100);
BusyItem get(std::size_t index);
private:
const std::size_t mCapacity;
std::map<std::size_t, std::list<Item>::iterator> mIndex;
std::list<Item> mBusyItems;
std::list<Item> mFreeItems;
std::list<Item> mClosedItems;
inline void closeExtraReaders();
inline void releaseItem(std::list<Item>::iterator it) noexcept;
};
}
#endif

View file

@ -18,6 +18,7 @@
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/esm3/readerscache.hpp>
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
@ -207,7 +208,7 @@ namespace EsmLoader
} }
ShallowContent shallowLoad(const Query& query, const std::vector<std::string>& contentFiles, ShallowContent shallowLoad(const Query& query, const std::vector<std::string>& contentFiles,
const Files::Collections& fileCollections, std::vector<ESM::ESMReader>& readers, const Files::Collections& fileCollections, ESM::ReadersCache& readers,
ToUTF8::Utf8Encoder* encoder) ToUTF8::Utf8Encoder* encoder)
{ {
ShallowContent result; ShallowContent result;
@ -233,14 +234,14 @@ namespace EsmLoader
const Files::MultiDirCollection& collection = fileCollections.getCollection(extension); const Files::MultiDirCollection& collection = fileCollections.getCollection(extension);
ESM::ESMReader& reader = readers[i]; const ESM::ReadersCache::BusyItem reader = readers.get(i);
reader.setEncoder(encoder); reader->setEncoder(encoder);
reader.setIndex(static_cast<int>(i)); reader->setIndex(static_cast<int>(i));
reader.open(collection.getPath(file).string()); reader->open(collection.getPath(file).string());
if (query.mLoadCells) if (query.mLoadCells)
reader.resolveParentFileIndices(readers); reader->resolveParentFileIndices(readers);
loadEsm(query, readers[i], result); loadEsm(query, *reader, result);
} }
return result; return result;
@ -289,7 +290,7 @@ namespace EsmLoader
} }
EsmData loadEsmData(const Query& query, const std::vector<std::string>& contentFiles, EsmData loadEsmData(const Query& query, const std::vector<std::string>& contentFiles,
const Files::Collections& fileCollections, std::vector<ESM::ESMReader>& readers, ToUTF8::Utf8Encoder* encoder) const Files::Collections& fileCollections, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder)
{ {
Log(Debug::Info) << "Loading ESM data..."; Log(Debug::Info) << "Loading ESM data...";

View file

@ -32,7 +32,7 @@ namespace EsmLoader
}; };
EsmData loadEsmData(const Query& query, const std::vector<std::string>& contentFiles, EsmData loadEsmData(const Query& query, const std::vector<std::string>& contentFiles,
const Files::Collections& fileCollections, std::vector<ESM::ESMReader>& readers, const Files::Collections& fileCollections, ESM::ReadersCache& readers,
ToUTF8::Utf8Encoder* encoder); ToUTF8::Utf8Encoder* encoder);
} }

View file

@ -13,6 +13,7 @@
#include <components/resource/bulletshapemanager.hpp> #include <components/resource/bulletshapemanager.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/esm3/readerscache.hpp>
#include <osg/ref_ptr> #include <osg/ref_ptr>
@ -50,17 +51,17 @@ namespace Resource
} }
std::vector<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, std::vector<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData,
std::vector<ESM::ESMReader>& readers) ESM::ReadersCache& readers)
{ {
std::vector<EsmLoader::Record<CellRef>> cellRefs; std::vector<EsmLoader::Record<CellRef>> cellRefs;
for (std::size_t i = 0; i < cell.mContextList.size(); i++) for (std::size_t i = 0; i < cell.mContextList.size(); i++)
{ {
ESM::ESMReader& reader = readers[static_cast<std::size_t>(cell.mContextList[i].index)]; const ESM::ReadersCache::BusyItem reader = readers.get(static_cast<std::size_t>(cell.mContextList[i].index));
cell.restore(reader, static_cast<int>(i)); cell.restore(*reader, static_cast<int>(i));
ESM::CellRef cellRef; ESM::CellRef cellRef;
bool deleted = false; bool deleted = false;
while (ESM::Cell::getNextRef(reader, cellRef, deleted)) while (ESM::Cell::getNextRef(*reader, cellRef, deleted))
{ {
Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID);
const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); const ESM::RecNameInts type = getType(esmData, cellRef.mRefID);
@ -83,7 +84,7 @@ namespace Resource
template <class F> template <class F>
void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs,
Resource::BulletShapeManager& bulletShapeManager, std::vector<ESM::ESMReader>& readers, Resource::BulletShapeManager& bulletShapeManager, ESM::ReadersCache& readers,
F&& f) F&& f)
{ {
std::vector<CellRef> cellRefs = loadCellRefs(cell, esmData, readers); std::vector<CellRef> cellRefs = loadCellRefs(cell, esmData, readers);
@ -130,7 +131,7 @@ namespace Resource
} }
} }
void forEachBulletObject(std::vector<ESM::ESMReader>& readers, const VFS::Manager& vfs, void forEachBulletObject(ESM::ReadersCache& readers, const VFS::Manager& vfs,
Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
std::function<void (const ESM::Cell& cell, const BulletObject& object)> callback) std::function<void (const ESM::Cell& cell, const BulletObject& object)> callback)
{ {

View file

@ -12,7 +12,7 @@
namespace ESM namespace ESM
{ {
class ESMReader; class ReadersCache;
struct Cell; struct Cell;
} }
@ -40,7 +40,7 @@ namespace Resource
float mScale; float mScale;
}; };
void forEachBulletObject(std::vector<ESM::ESMReader>& readers, const VFS::Manager& vfs, void forEachBulletObject(ESM::ReadersCache& readers, const VFS::Manager& vfs,
Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
std::function<void (const ESM::Cell&, const BulletObject& object)> callback); std::function<void (const ESM::Cell&, const BulletObject& object)> callback);
} }