mirror of https://github.com/PCSX2/pcsx2.git
ChdReader: Use core_file to implement precaching
This commit is contained in:
parent
58d13dac34
commit
a7b07eb53f
|
@ -21,6 +21,7 @@ static std::vector<std::pair<std::string, chd_header>> s_chd_hash_cache; // <fil
|
||||||
static std::recursive_mutex s_chd_hash_cache_mutex;
|
static std::recursive_mutex s_chd_hash_cache_mutex;
|
||||||
|
|
||||||
// Provides an implementation of core_file which allows us to control if the underlying FILE handle is freed.
|
// Provides an implementation of core_file which allows us to control if the underlying FILE handle is freed.
|
||||||
|
// Additionally, this class allows greater control and feedback while precaching CHD files.
|
||||||
// The lifetime of ChdCoreFileWrapper will be equal to that of the relevant chd_file,
|
// The lifetime of ChdCoreFileWrapper will be equal to that of the relevant chd_file,
|
||||||
// ChdCoreFileWrapper will also get destroyed if chd_open_core_file fails.
|
// ChdCoreFileWrapper will also get destroyed if chd_open_core_file fails.
|
||||||
class ChdCoreFileWrapper
|
class ChdCoreFileWrapper
|
||||||
|
@ -31,10 +32,15 @@ private:
|
||||||
core_file m_core;
|
core_file m_core;
|
||||||
std::FILE* m_file;
|
std::FILE* m_file;
|
||||||
bool m_free_file = false;
|
bool m_free_file = false;
|
||||||
|
ChdCoreFileWrapper* m_parent = nullptr;
|
||||||
|
std::unique_ptr<u8[]> m_file_cache;
|
||||||
|
s64 m_file_cache_size;
|
||||||
|
s64 m_file_cache_pos;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ChdCoreFileWrapper(std::FILE* file)
|
ChdCoreFileWrapper(std::FILE* file, ChdCoreFileWrapper* parent)
|
||||||
: m_file{file}
|
: m_file{file}
|
||||||
|
, m_parent{parent}
|
||||||
{
|
{
|
||||||
m_core.argp = this;
|
m_core.argp = this;
|
||||||
m_core.fsize = FSize;
|
m_core.fsize = FSize;
|
||||||
|
@ -45,7 +51,7 @@ public:
|
||||||
|
|
||||||
~ChdCoreFileWrapper()
|
~ChdCoreFileWrapper()
|
||||||
{
|
{
|
||||||
if (m_free_file)
|
if (m_free_file && m_file)
|
||||||
std::fclose(m_file);
|
std::fclose(m_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,15 +70,100 @@ public:
|
||||||
m_free_file = isOwner;
|
m_free_file = isOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s64 GetPrecacheSize()
|
||||||
|
{
|
||||||
|
const s64 size = static_cast<size_t>(FileSystem::FSize64(m_file));
|
||||||
|
if (m_parent != nullptr)
|
||||||
|
return m_parent->GetPrecacheSize() + size;
|
||||||
|
else
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Precache(ProgressCallback* progress, Error* error)
|
||||||
|
{
|
||||||
|
progress->SetProgressRange(100);
|
||||||
|
|
||||||
|
const s64 size = GetPrecacheSize();
|
||||||
|
return PrecacheInternal(progress, error, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool PrecacheInternal(ProgressCallback* progress, Error* error, s64 startSize, s64 finalSize)
|
||||||
|
{
|
||||||
|
m_file_cache_size = FileSystem::FSize64(m_file);
|
||||||
|
if (m_file_cache_size <= 0)
|
||||||
|
{
|
||||||
|
Error::SetStringView(error, "Failed to determine file size.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the current file position.
|
||||||
|
m_file_cache_pos = FileSystem::FTell64(m_file);
|
||||||
|
if (m_file_cache_pos <= 0)
|
||||||
|
{
|
||||||
|
Error::SetStringView(error, "Failed to determine file position.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_file_cache = std::make_unique_for_overwrite<u8[]>(m_file_cache_size);
|
||||||
|
if (FileSystem::FSeek64(m_file, 0, SEEK_SET) != 0 ||
|
||||||
|
FileSystem::ReadFileWithPartialProgress(
|
||||||
|
m_file, m_file_cache.get(), m_file_cache_size, progress,
|
||||||
|
(startSize * 100) / finalSize,
|
||||||
|
((startSize + m_file_cache_size) * 100) / finalSize,
|
||||||
|
error) != static_cast<size_t>(m_file_cache_size))
|
||||||
|
{
|
||||||
|
m_file_cache.reset();
|
||||||
|
// Precache failed, continue using file
|
||||||
|
// Restore file position incase it's used for subsequent reads
|
||||||
|
FileSystem::FSeek64(m_file, m_file_cache_pos, SEEK_SET);
|
||||||
|
Error::SetStringView(error, "Failed to read part of the file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
startSize += m_file_cache_size;
|
||||||
|
|
||||||
|
if (m_parent)
|
||||||
|
{
|
||||||
|
if (!m_parent->PrecacheInternal(progress, error, startSize, finalSize))
|
||||||
|
{
|
||||||
|
// Precache failed, continue using file
|
||||||
|
// Restore file position incase it's used for subsequent reads
|
||||||
|
FileSystem::FSeek64(m_file, m_file_cache_pos, SEEK_SET);
|
||||||
|
m_file_cache.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_free_file)
|
||||||
|
std::fclose(m_file);
|
||||||
|
m_file = nullptr;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static u64 FSize(core_file* file)
|
static u64 FSize(core_file* file)
|
||||||
{
|
{
|
||||||
return static_cast<u64>(FileSystem::FSize64(FromCoreFile(file)->m_file));
|
ChdCoreFileWrapper* fileWrapper = FromCoreFile(file);
|
||||||
|
if (fileWrapper->m_file_cache)
|
||||||
|
return fileWrapper->m_file_cache_size;
|
||||||
|
else
|
||||||
|
return static_cast<u64>(FileSystem::FSize64(fileWrapper->m_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t FRead(void* buffer, size_t elmSize, size_t elmCount, core_file* file)
|
static size_t FRead(void* buffer, size_t elmSize, size_t elmCount, core_file* file)
|
||||||
{
|
{
|
||||||
return std::fread(buffer, elmSize, elmCount, FromCoreFile(file)->m_file);
|
ChdCoreFileWrapper* fileWrapper = FromCoreFile(file);
|
||||||
|
if (fileWrapper->m_file_cache)
|
||||||
|
{
|
||||||
|
// While currently libchdr only uses an elmCount of 1, we can't guarantee that will always be the case.
|
||||||
|
elmCount = std::min<size_t>(elmCount, std::max<s64>(fileWrapper->m_file_cache_size - fileWrapper->m_file_cache_pos, 0) / elmSize);
|
||||||
|
const size_t size = elmSize * elmCount;
|
||||||
|
std::memcpy(buffer, &fileWrapper->m_file_cache[fileWrapper->m_file_cache_pos], size);
|
||||||
|
return elmCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return std::fread(buffer, elmSize, elmCount, fileWrapper->m_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int FClose(core_file* file)
|
static int FClose(core_file* file)
|
||||||
|
@ -84,7 +175,28 @@ private:
|
||||||
|
|
||||||
static int FSeek(core_file* file, int64_t offset, int whence)
|
static int FSeek(core_file* file, int64_t offset, int whence)
|
||||||
{
|
{
|
||||||
return FileSystem::FSeek64(FromCoreFile(file)->m_file, offset, whence);
|
ChdCoreFileWrapper* fileWrapper = FromCoreFile(file);
|
||||||
|
if (fileWrapper->m_file_cache)
|
||||||
|
{
|
||||||
|
switch (whence)
|
||||||
|
{
|
||||||
|
case SEEK_SET:
|
||||||
|
fileWrapper->m_file_cache_pos = offset;
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
fileWrapper->m_file_cache_pos += offset;
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
fileWrapper->m_file_cache_pos = fileWrapper->m_file_cache_size + offset;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return FileSystem::FSeek64(fileWrapper->m_file, offset, whence);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,7 +234,7 @@ static bool IsHeaderParentCHD(const chd_header& header, const chd_header& parent
|
||||||
static chd_file* OpenCHD(const std::string& filename, FileSystem::ManagedCFilePtr fp, Error* error, u32 recursion_level)
|
static chd_file* OpenCHD(const std::string& filename, FileSystem::ManagedCFilePtr fp, Error* error, u32 recursion_level)
|
||||||
{
|
{
|
||||||
chd_file* chd;
|
chd_file* chd;
|
||||||
ChdCoreFileWrapper* core_wrapper = new ChdCoreFileWrapper(fp.get());
|
ChdCoreFileWrapper* core_wrapper = new ChdCoreFileWrapper(fp.get(), nullptr);
|
||||||
// libchdr will take ownership of core_wrapper, and will close/free it on failure.
|
// libchdr will take ownership of core_wrapper, and will close/free it on failure.
|
||||||
chd_error err = chd_open_core_file(core_wrapper->GetCoreFile(), CHD_OPEN_READ, nullptr, &chd);
|
chd_error err = chd_open_core_file(core_wrapper->GetCoreFile(), CHD_OPEN_READ, nullptr, &chd);
|
||||||
if (err == CHDERR_NONE)
|
if (err == CHDERR_NONE)
|
||||||
|
@ -236,7 +348,7 @@ static chd_file* OpenCHD(const std::string& filename, FileSystem::ManagedCFilePt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our last core file wrapper got freed, so make a new one.
|
// Our last core file wrapper got freed, so make a new one.
|
||||||
core_wrapper = new ChdCoreFileWrapper(fp.get());
|
core_wrapper = new ChdCoreFileWrapper(fp.get(), ChdCoreFileWrapper::FromCoreFile(chd_core_file(parent_chd)));
|
||||||
// Now try re-opening with the parent.
|
// Now try re-opening with the parent.
|
||||||
err = chd_open_core_file(core_wrapper->GetCoreFile(), CHD_OPEN_READ, parent_chd, &chd);
|
err = chd_open_core_file(core_wrapper->GetCoreFile(), CHD_OPEN_READ, parent_chd, &chd);
|
||||||
if (err != CHDERR_NONE)
|
if (err != CHDERR_NONE)
|
||||||
|
@ -290,28 +402,11 @@ bool ChdFileReader::Open2(std::string filename, Error* error)
|
||||||
|
|
||||||
bool ChdFileReader::Precache2(ProgressCallback* progress, Error* error)
|
bool ChdFileReader::Precache2(ProgressCallback* progress, Error* error)
|
||||||
{
|
{
|
||||||
if (!CheckAvailableMemoryForPrecaching(chd_get_compressed_size(ChdFile), error))
|
ChdCoreFileWrapper* fileWrapper = ChdCoreFileWrapper::FromCoreFile(chd_core_file(ChdFile));
|
||||||
|
if (!CheckAvailableMemoryForPrecaching(fileWrapper->GetPrecacheSize(), error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
progress->SetProgressRange(100);
|
return fileWrapper->Precache(progress, error);
|
||||||
|
|
||||||
const auto callback = [](size_t pos, size_t total, void* param) -> bool {
|
|
||||||
ProgressCallback* progress = static_cast<ProgressCallback*>(param);
|
|
||||||
const u32 percent = static_cast<u32>((pos * 100) / total);
|
|
||||||
progress->SetProgressValue(std::min<u32>(percent, 100));
|
|
||||||
return !progress->IsCancelled();
|
|
||||||
};
|
|
||||||
|
|
||||||
const chd_error cerror = chd_precache_progress(ChdFile, callback, progress);
|
|
||||||
if (cerror != CHDERR_NONE)
|
|
||||||
{
|
|
||||||
if (cerror != CHDERR_CANCELLED)
|
|
||||||
Error::SetStringView(error, "Failed to read part of the file.");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadedFileReader::Chunk ChdFileReader::ChunkForOffset(u64 offset)
|
ThreadedFileReader::Chunk ChdFileReader::ChunkForOffset(u64 offset)
|
||||||
|
|
Loading…
Reference in New Issue