mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
The libretro core is the frontend, and the headless tools are the test and debugging hosts. The Windows, UWP, Android, iOS, macOS and SDL apps, the UI framework and imgui go, along with what only they used: the Win32 debugger hooks (g_frameMutex, the shutdown lock), JNI thread attachment, the Android text drawer, RAIntegration, Discord, recent files, game installs, tilt input, the audio resamplers, the control mapper, memstick moving and the remote ISO and upload server modes. The web server keeps the debugger. The MSVC, Gradle and ndk-build projects go too.
296 lines
8.4 KiB
C++
296 lines
8.4 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <thread>
|
|
|
|
#include "Common/Thread/ThreadUtil.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Core/FileLoaders/CachingFileLoader.h"
|
|
|
|
// Takes ownership of backend.
|
|
CachingFileLoader::CachingFileLoader(FileLoader *backend)
|
|
: ProxiedFileLoader(backend) {
|
|
}
|
|
|
|
void CachingFileLoader::Prepare() {
|
|
std::call_once(preparedFlag_, [this](){
|
|
filesize_ = ProxiedFileLoader::FileSize();
|
|
if (filesize_ > 0) {
|
|
InitCache();
|
|
}
|
|
});
|
|
}
|
|
|
|
CachingFileLoader::~CachingFileLoader() {
|
|
if (filesize_ > 0) {
|
|
ShutdownCache();
|
|
}
|
|
}
|
|
|
|
bool CachingFileLoader::Exists() {
|
|
if (exists_ == -1) {
|
|
exists_ = ProxiedFileLoader::Exists() ? 1 : 0;
|
|
}
|
|
return exists_ == 1;
|
|
}
|
|
|
|
bool CachingFileLoader::ExistsFast() {
|
|
if (exists_ == -1) {
|
|
return ProxiedFileLoader::ExistsFast();
|
|
}
|
|
return exists_ == 1;
|
|
}
|
|
|
|
bool CachingFileLoader::IsDirectory() {
|
|
if (isDirectory_ == -1) {
|
|
isDirectory_ = ProxiedFileLoader::IsDirectory() ? 1 : 0;
|
|
}
|
|
return isDirectory_ == 1;
|
|
}
|
|
|
|
s64 CachingFileLoader::FileSize() {
|
|
Prepare();
|
|
return filesize_;
|
|
}
|
|
|
|
size_t CachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {
|
|
Prepare();
|
|
if (absolutePos >= filesize_) {
|
|
bytes = 0;
|
|
} else if (absolutePos + (s64)bytes >= filesize_) {
|
|
bytes = (size_t)(filesize_ - absolutePos);
|
|
}
|
|
|
|
size_t readSize = 0;
|
|
if ((flags & Flags::HINT_UNCACHED) != 0) {
|
|
readSize = backend_->ReadAt(absolutePos, bytes, data, flags);
|
|
} else {
|
|
readSize = ReadFromCache(absolutePos, bytes, data);
|
|
// While in case the cache size is too small for the entire read.
|
|
while (readSize < bytes) {
|
|
SaveIntoCache(absolutePos + readSize, bytes - readSize, flags);
|
|
size_t bytesFromCache = ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);
|
|
readSize += bytesFromCache;
|
|
if (bytesFromCache == 0) {
|
|
// We can't read any more.
|
|
break;
|
|
}
|
|
}
|
|
|
|
StartReadAhead(absolutePos + readSize);
|
|
}
|
|
|
|
return readSize;
|
|
}
|
|
|
|
void CachingFileLoader::InitCache() {
|
|
oldestGeneration_ = 0;
|
|
generation_ = 0;
|
|
}
|
|
|
|
void CachingFileLoader::ShutdownCache() {
|
|
// We can't delete while the read-ahead thread is running; the join waits for it.
|
|
if (aheadThread_.joinable())
|
|
aheadThread_.join();
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(blocksMutex_);
|
|
for (const auto &block : blocks_) {
|
|
delete [] block.second.ptr;
|
|
}
|
|
blocks_.clear();
|
|
}
|
|
|
|
size_t CachingFileLoader::ReadFromCache(s64 pos, size_t bytes, void *data) {
|
|
s64 cacheStartPos = pos >> BLOCK_SHIFT;
|
|
s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;
|
|
// TODO: Smarter.
|
|
size_t readSize = 0;
|
|
size_t offset = (size_t)(pos - (cacheStartPos << BLOCK_SHIFT));
|
|
u8 *p = (u8 *)data;
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(blocksMutex_);
|
|
for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {
|
|
auto block = blocks_.find(i);
|
|
if (block == blocks_.end()) {
|
|
return readSize;
|
|
}
|
|
block->second.generation = generation_;
|
|
|
|
size_t toRead = std::min(bytes - readSize, (size_t)BLOCK_SIZE - offset);
|
|
memcpy(p + readSize, block->second.ptr + offset, toRead);
|
|
readSize += toRead;
|
|
|
|
// Don't need an offset after the first read.
|
|
offset = 0;
|
|
}
|
|
return readSize;
|
|
}
|
|
|
|
void CachingFileLoader::SaveIntoCache(s64 pos, size_t bytes, Flags flags, bool readingAhead) {
|
|
s64 cacheStartPos = pos >> BLOCK_SHIFT;
|
|
s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;
|
|
// Read-ahead can ask for blocks past the end of the file.
|
|
cacheEndPos = std::min(cacheEndPos, (filesize_ - 1) >> BLOCK_SHIFT);
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(blocksMutex_);
|
|
size_t blocksToRead = 0;
|
|
for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {
|
|
auto block = blocks_.find(i);
|
|
if (block != blocks_.end()) {
|
|
break;
|
|
}
|
|
++blocksToRead;
|
|
if (blocksToRead >= MAX_BLOCKS_PER_READ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!MakeCacheSpaceFor(blocksToRead, readingAhead) || blocksToRead == 0) {
|
|
return;
|
|
}
|
|
|
|
if (blocksToRead == 1) {
|
|
blocksMutex_.unlock();
|
|
|
|
u8 *buf = new u8[BLOCK_SIZE];
|
|
size_t readBytes = backend_->ReadAt(cacheStartPos << BLOCK_SHIFT, BLOCK_SIZE, buf, flags);
|
|
|
|
blocksMutex_.lock();
|
|
// While blocksMutex_ was unlocked, another thread may have read.
|
|
// If so, free the one we just read.
|
|
if (blocks_.find(cacheStartPos) == blocks_.end()) {
|
|
// Only cache a block we actually fully read - a short/failed read (e.g. a
|
|
// dropped connection on a Remote ISO) must not be cached as if valid, or
|
|
// every later read of this block would silently return the uninitialized
|
|
// tail of `buf` as if it were real file data. The last block of the file
|
|
// is shorter, and complete if the read reached the end.
|
|
if (readBytes == BLOCK_SIZE || (readBytes > 0 && (cacheStartPos << BLOCK_SHIFT) + (s64)readBytes == filesize_)) {
|
|
blocks_[cacheStartPos] = BlockInfo{buf};
|
|
} else {
|
|
delete [] buf;
|
|
}
|
|
} else {
|
|
delete [] buf;
|
|
}
|
|
} else {
|
|
blocksMutex_.unlock();
|
|
|
|
u8 *wholeRead = new u8[blocksToRead << BLOCK_SHIFT];
|
|
size_t readBytes = backend_->ReadAt(cacheStartPos << BLOCK_SHIFT, blocksToRead << BLOCK_SHIFT, wholeRead, flags);
|
|
size_t wholeBlocksRead = readBytes >> BLOCK_SHIFT;
|
|
if ((readBytes & (BLOCK_SIZE - 1)) != 0 && (cacheStartPos << BLOCK_SHIFT) + (s64)readBytes == filesize_) {
|
|
// The short last block of the file.
|
|
wholeBlocksRead++;
|
|
}
|
|
|
|
blocksMutex_.lock();
|
|
for (size_t i = 0; i < wholeBlocksRead; ++i) {
|
|
if (blocks_.find(cacheStartPos + i) != blocks_.end()) {
|
|
// Written while we were busy, just skip it. Keep the existing block.
|
|
continue;
|
|
}
|
|
u8 *buf = new u8[BLOCK_SIZE];
|
|
memcpy(buf, wholeRead + (i << BLOCK_SHIFT), BLOCK_SIZE);
|
|
blocks_[cacheStartPos + i] = BlockInfo{buf};
|
|
}
|
|
delete[] wholeRead;
|
|
}
|
|
|
|
++generation_;
|
|
}
|
|
|
|
bool CachingFileLoader::MakeCacheSpaceFor(size_t blocks, bool readingAhead) {
|
|
size_t goal = MAX_BLOCKS_CACHED - blocks;
|
|
|
|
if (readingAhead && blocks_.size() > goal) {
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(blocksMutex_);
|
|
while (blocks_.size() > goal) {
|
|
u64 minGeneration = generation_;
|
|
|
|
// We increment the iterator inside because we delete things inside.
|
|
for (auto it = blocks_.begin(); it != blocks_.end(); ) {
|
|
// Check for the minimum seen generation.
|
|
// TODO: Do this smarter?
|
|
if (it->second.generation != 0 && it->second.generation < minGeneration) {
|
|
minGeneration = it->second.generation;
|
|
}
|
|
|
|
// 0 means it was never used yet or was the first read (e.g. block descriptor.)
|
|
if (it->second.generation == oldestGeneration_ || it->second.generation == 0) {
|
|
s64 pos = it->first;
|
|
delete [] it->second.ptr;
|
|
blocks_.erase(it);
|
|
|
|
// Our iterator is invalid now. Keep going?
|
|
if (blocks_.size() > goal) {
|
|
// This finds the one at that position.
|
|
it = blocks_.lower_bound(pos);
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
// If we didn't find any, update to the lowest we did find.
|
|
oldestGeneration_ = minGeneration;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CachingFileLoader::StartReadAhead(s64 pos) {
|
|
std::lock_guard<std::recursive_mutex> guard(blocksMutex_);
|
|
if (aheadThreadRunning_) {
|
|
// Already going.
|
|
return;
|
|
}
|
|
if (blocks_.size() + BLOCK_READAHEAD > MAX_BLOCKS_CACHED) {
|
|
// Not enough space to readahead.
|
|
return;
|
|
}
|
|
|
|
aheadThreadRunning_ = true;
|
|
if (aheadThread_.joinable())
|
|
aheadThread_.join();
|
|
aheadThread_ = std::thread([this, pos] {
|
|
SetCurrentThreadName("FileLoaderReadAhead");
|
|
|
|
|
|
std::unique_lock<std::recursive_mutex> guard(blocksMutex_);
|
|
s64 cacheStartPos = pos >> BLOCK_SHIFT;
|
|
s64 cacheEndPos = cacheStartPos + BLOCK_READAHEAD - 1;
|
|
|
|
for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {
|
|
auto block = blocks_.find(i);
|
|
if (block == blocks_.end()) {
|
|
guard.unlock();
|
|
SaveIntoCache(i << BLOCK_SHIFT, BLOCK_SIZE * BLOCK_READAHEAD, Flags::NONE, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
aheadThreadRunning_ = false;
|
|
});
|
|
}
|