openmw/components/esm3/readerscache.hpp
AnyOldName3 15162a734d Avoid IO in resolveParentFileIndices
In the olden days, we passed it a vector of open ESMReader instances, as they knew the filenames and sizes, so were a convenient source of this knowledge.

When the ReadersCache was introduced as a pool of readers to limit the maximum number of simultaneously open file handles (to avoid going over the OS' limit) it was a poor substitute.
* We iterate over all the earlier readers in order in a double loop, which is the worst case scenario for an LRU pool as once we're past the size limit, we're guaranteed maximum thrashing - the least recently used item is the most likely to be used next, so the worst to evict.
* We didn't want to read any ESM files, just know whether they'd been read and what their sizes were, so didn't want to open a file handle, which the ReadersCache forced us to do.

Obviously, opening lots of file handles isn't fast, and as this was an operation done for each content file which iterated over the file's masters and within that loop iterated over every loaded file, that's O(n^3) complexity in the worst case, and for things like delta plugin merged plugins, they hit the worst case in long load orders.

This resolves the freeze reported as https://gitlab.com/OpenMW/openmw/-/issues/8425, but there may be other freezes on launch.
2025-04-10 16:16:19 +01:00

78 lines
1.8 KiB
C++

#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::filesystem::path> mName;
std::optional<std::size_t> mFileSize;
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);
const std::filesystem::path& getName(std::size_t index) const;
std::size_t getFileSize(std::size_t index);
void clear();
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