DirectoryBlob: host files kept open across reads

A read from an extracted game's file no longer opens, maps, unmaps and
closes the file every time. The reader keeps 16 slots of open (and,
where the platform maps, mapped) files, picked by a hash of the path; a
read owns its slot for its length through an atomic flag, so reads stay
lock-free, and copies straight out of the mapping. A read finding its
slot taken opens the file for itself, as before. No allocation: the
slots, paths included, live in the reader.

An extracted disc streaming two 600 KiB pictures alternately: 2 opens
over 120 frames instead of 218, pictures identical.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 04:24:31 +01:00
1 parent 9ffbc33572
commit 09cac91415
2 files changed
+104 -4

No files matched your search

+77 -2
View File
@@ -13,6 +13,8 @@
#include <variant>
#include <vector>
#include <streams/file_stream.h>
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
@@ -93,8 +95,8 @@ bool DiscContent::Read(u64* offset, u64* length, u8** buffer, DirectoryBlobReade
if (std::holds_alternative<ContentFile>(m_content_source))
{
const auto& content = std::get<ContentFile>(m_content_source);
File::DirectIOFile file(content.m_filename, File::AccessMode::Read);
if (!file.OffsetRead(content.m_offset + offset_in_content, *buffer, bytes_to_read))
if (!blob->ReadContentFile(content.m_filename, content.m_offset + offset_in_content, *buffer,
bytes_to_read))
{
return false;
}
@@ -386,6 +388,7 @@ DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root,
const std::string& true_root)
: m_encryption_cache(this)
{
InitFileSlots();
DirectoryBlobPartition game_partition(game_partition_root, {});
m_is_wii = game_partition.IsWii();
@@ -436,6 +439,7 @@ DirectoryBlobReader::DirectoryBlobReader(
fst_callback)
: m_encryption_cache(this), m_wrapped_volume(std::move(volume))
{
InitFileSlots();
DirectoryBlobPartition game_partition(m_wrapped_volume.get(),
m_wrapped_volume->GetGamePartition(), std::nullopt,
sys_callback, fst_callback, this);
@@ -499,6 +503,77 @@ DirectoryBlobReader::DirectoryBlobReader(const DirectoryBlobReader& rhs)
CreateDisc(rhs.m_wrapped_volume->GetBlobReader().CopyReader()) :
nullptr)
{
InitFileSlots();
}
void DirectoryBlobReader::InitFileSlots()
{
u32 i;
for (i = 0; i < NUM_FILE_SLOTS; i++)
{
m_file_slots[i].busy.store(0, std::memory_order_relaxed);
m_file_slots[i].file = nullptr;
m_file_slots[i].map = nullptr;
m_file_slots[i].map_size = 0;
m_file_slots[i].path[0] = '\0';
}
}
DirectoryBlobReader::~DirectoryBlobReader()
{
u32 i;
for (i = 0; i < NUM_FILE_SLOTS; i++)
{
if (m_file_slots[i].file)
filestream_close(m_file_slots[i].file);
}
}
bool DirectoryBlobReader::ReadContentFile(const std::string& path, u64 offset, u8* buffer, u64 size)
{
const char* name = path.c_str();
const size_t name_len = path.size();
FileSlot* slot;
bool ok;
u32 hash = 2166136261u;
size_t i;
for (i = 0; i < name_len; i++)
hash = (hash ^ u8(name[i])) * 16777619u;
slot = &m_file_slots[hash % NUM_FILE_SLOTS];
if (name_len >= sizeof(slot->path) || slot->busy.exchange(1, std::memory_order_acquire))
{
File::DirectIOFile file(name, File::AccessMode::Read);
return file.OffsetRead(offset, buffer, size);
}
if (!slot->file || std::strcmp(slot->path, name) != 0)
{
int64_t map_size = 0;
if (slot->file)
filestream_close(slot->file);
slot->file = filestream_open(name, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS);
slot->map = slot->file ? filestream_get_mapped_ptr(slot->file, &map_size) : nullptr;
slot->map_size = slot->map ? u64(map_size) : 0;
std::memcpy(slot->path, name, name_len + 1);
}
if (slot->map)
{
ok = offset <= slot->map_size && size <= slot->map_size - offset;
if (ok)
std::memcpy(buffer, slot->map + offset, size_t(size));
}
else
{
ok = slot->file && filestream_seek(slot->file, s64(offset), RETRO_VFS_SEEK_POSITION_START) >= 0 &&
filestream_read(slot->file, buffer, s64(size)) == s64(size);
}
slot->busy.store(0, std::memory_order_release);
return ok;
}
bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer)
+27 -2
View File
@@ -4,6 +4,7 @@
#pragma once
#include <array>
#include <atomic>
#include <cstddef>
#include <functional>
#include <map>
@@ -14,6 +15,8 @@
#include <variant>
#include <vector>
#include <retro_miscellaneous.h>
#include "Common/CommonTypes.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Volume.h"
@@ -24,6 +27,8 @@ namespace File
struct FSTEntry;
} // namespace File
struct RFILE;
namespace DiscIO
{
enum class PartitionType : u32;
@@ -252,8 +257,9 @@ public:
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
fst_callback);
DirectoryBlobReader(DirectoryBlobReader&&) = default;
DirectoryBlobReader& operator=(DirectoryBlobReader&&) = default;
DirectoryBlobReader(DirectoryBlobReader&&) = delete;
DirectoryBlobReader& operator=(DirectoryBlobReader&&) = delete;
~DirectoryBlobReader() override;
bool Read(u64 offset, u64 length, u8* buffer) override;
bool SupportsReadWiiDecrypted(u64 offset, u64 size, u64 partition_data_offset) const override;
@@ -307,6 +313,25 @@ private:
const VolumeDisc* GetWrappedVolume() const { return m_wrapped_volume.get(); }
// Reads from a host file, through a handle kept open across reads.
bool ReadContentFile(const std::string& path, u64 offset, u8* buffer, u64 size);
// Recently read host files, kept open (and mapped where the platform maps)
// so a read is a copy out of the page cache rather than an open, a map, an
// unmap and a close. A reader owns a slot for the length of one read; a
// read finding its slot taken opens the file for itself, as before.
static constexpr u32 NUM_FILE_SLOTS = 16;
struct FileSlot
{
std::atomic<u32> busy;
RFILE* file;
const u8* map;
u64 map_size;
char path[PATH_MAX_LENGTH];
};
FileSlot m_file_slots[NUM_FILE_SLOTS];
void InitFileSlots();
// For GameCube:
DirectoryBlobPartition m_gamecube_pseudopartition;