mirror of https://github.com/OpenMW/openmw.git
Merge branch 'esm_readers_cache' into 'master'
Limit the number of simultaneously open not actively used content files (#6756) Closes #6756 See merge request OpenMW/openmw!1966fix/shrink_builds
commit
58fd560ce9
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
Loading…
Reference in New Issue