Death::IO::FileStreamPool class

Keeps the open stream of one file between uses, so reading a resource does not reopen it.

A .pak archive hands out a BoundedFileStream per resource, and each one used to open the archive by path again. On a desktop that is a cheap system call. On a console reading from optical media or a memory card it is a directory lookup and a seek - about 30 ms on a PlayStation 2 or Dreamcast disc - and the PCSX2 log showed the archive reopened roughly every 50 ms while the intro played.

The pool lends a FileStream out with Acquire() and takes it back with Release(). A stream that comes back keeps its descriptor AND its read buffer, so the open-read-close sequence loading is made of pays for the open once, and a resource that sits next to the previous one in the archive is served out of the buffer the previous read already filled - FileStream::Seek() keeps the buffer when the target lies inside it - with no disc access at all.

Only MaxIdleStreams (one) is kept when nothing is using it, because the game reads sequentially: an idle stream pins a file descriptor, which the console filesystems have few of (KallistiOS has a fixed fd_table, the PS2 cdfs driver a handful of handles), plus its buffer, 8 KB by default, and a second one would only save a reopen on the rare occasion that two readers return at the same time. Streams alive at once (a music stream and a level load on another thread) each get their own, so a reader never waits on another's position. When a stream comes back and the pool is full, the newcomer replaces the parked one, because its buffer holds what was read last and the next resource is most likely right after it. The requested buffer size never parks a stream: a reused stream adopts the size its new user asks for (FileStream::SetBufferSize()). Safe to call from any thread.

Public static variables

static std::int32_t MaxIdleStreams constexpr
How many idle streams are kept open; a stream returned beyond this replaces the oldest.

Constructors, destructors, conversion operators

FileStreamPool(Containers::StringView path) explicit
FileStreamPool(const FileStreamPool&) deleted

Public functions

auto operator=(const FileStreamPool&) -> FileStreamPool& deleted
auto GetPath() const -> Containers::StringView
Returns the path of the file the streams open.
auto Acquire(std::int32_t bufferSize) -> std::unique_ptr<FileStream>
Returns an open read-only stream of the file with the given buffer size, reusing an idle one if there is any and opening a new one otherwise.
void Release(std::unique_ptr<FileStream>&& stream)
Takes a stream back for the next Acquire(); an invalid one is closed, and a full pool drops its oldest parked stream for it.

Function documentation

std::unique_ptr<FileStream> Death::IO::FileStreamPool::Acquire(std::int32_t bufferSize)

Returns an open read-only stream of the file with the given buffer size, reusing an idle one if there is any and opening a new one otherwise.

The position of a reused stream is wherever its last user left it, so seek before reading.