Path: blob/master/Core/FileLoaders/RamCachingFileLoader.cpp
3186 views
// Copyright (c) 2015- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>18#include <thread>19#include <cstring>2021#include "Common/Thread/ThreadUtil.h"22#include "Common/TimeUtil.h"23#include "Common/Log.h"24#include "Core/FileLoaders/RamCachingFileLoader.h"2526// Takes ownership of backend.27RamCachingFileLoader::RamCachingFileLoader(FileLoader *backend)28: ProxiedFileLoader(backend) {29filesize_ = backend->FileSize();30if (filesize_ > 0) {31InitCache();32}33}3435RamCachingFileLoader::~RamCachingFileLoader() {36if (filesize_ > 0) {37ShutdownCache();38}39}4041bool RamCachingFileLoader::Exists() {42if (exists_ == -1) {43exists_ = ProxiedFileLoader::Exists() ? 1 : 0;44}45return exists_ == 1;46}4748bool RamCachingFileLoader::ExistsFast() {49if (exists_ == -1) {50return ProxiedFileLoader::ExistsFast();51}52return exists_ == 1;53}5455bool RamCachingFileLoader::IsDirectory() {56if (isDirectory_ == -1) {57isDirectory_ = ProxiedFileLoader::IsDirectory() ? 1 : 0;58}59return isDirectory_ == 1;60}6162s64 RamCachingFileLoader::FileSize() {63return filesize_;64}6566size_t RamCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {67size_t readSize = 0;68if (cache_ == nullptr || (flags & Flags::HINT_UNCACHED) != 0) {69readSize = backend_->ReadAt(absolutePos, bytes, data, flags);70} else {71readSize = ReadFromCache(absolutePos, bytes, data);72// While in case the cache size is too small for the entire read.73while (readSize < bytes) {74SaveIntoCache(absolutePos + readSize, bytes - readSize, flags);75size_t bytesFromCache = ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);76readSize += bytesFromCache;77if (bytesFromCache == 0) {78// We can't read any more.79break;80}81}8283StartReadAhead(absolutePos + readSize);84}85return readSize;86}8788void RamCachingFileLoader::InitCache() {89std::lock_guard<std::mutex> guard(blocksMutex_);90u32 blockCount = (u32)((filesize_ + BLOCK_SIZE - 1) >> BLOCK_SHIFT);91// Overallocate for the last block.92cache_ = (u8 *)malloc((size_t)blockCount << BLOCK_SHIFT);93if (cache_ == nullptr) {94ERROR_LOG(Log::IO, "Failed to allocate cache for Cache full ISO in RAM! Will fall back to regular reads.");95return;96}97aheadRemaining_ = blockCount;98blocks_.resize(blockCount);99}100101void RamCachingFileLoader::ShutdownCache() {102Cancel();103104// We can't delete while the thread is running, so have to wait.105// This should only happen from the menu.106if (aheadThread_.joinable())107aheadThread_.join();108109_dbg_assert_(!aheadThreadRunning_);110111std::lock_guard<std::mutex> guard(blocksMutex_);112blocks_.clear();113if (cache_ != nullptr) {114free(cache_);115cache_ = nullptr;116}117}118119void RamCachingFileLoader::Cancel() {120if (aheadThreadRunning_) {121std::lock_guard<std::mutex> guard(blocksMutex_);122aheadCancel_ = true;123}124125ProxiedFileLoader::Cancel();126}127128size_t RamCachingFileLoader::ReadFromCache(s64 pos, size_t bytes, void *data) {129s64 cacheStartPos = pos >> BLOCK_SHIFT;130s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;131if ((size_t)cacheEndPos >= blocks_.size()) {132cacheEndPos = blocks_.size() - 1;133}134135size_t readSize = 0;136size_t offset = (size_t)(pos - (cacheStartPos << BLOCK_SHIFT));137u8 *p = (u8 *)data;138139// Clamp bytes to what's actually available.140if (pos + (s64)bytes > filesize_) {141// Should've been caught above, but just in case.142if (pos >= filesize_) {143return 0;144}145bytes = (size_t)(filesize_ - pos);146}147148std::lock_guard<std::mutex> guard(blocksMutex_);149for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {150if (blocks_[(size_t)i] == 0) {151return readSize;152}153154size_t toRead = std::min(bytes - readSize, (size_t)BLOCK_SIZE - offset);155s64 cachePos = (i << BLOCK_SHIFT) + offset;156memcpy(p + readSize, &cache_[cachePos], toRead);157readSize += toRead;158159// Don't need an offset after the first read.160offset = 0;161}162return readSize;163}164165void RamCachingFileLoader::SaveIntoCache(s64 pos, size_t bytes, Flags flags) {166s64 cacheStartPos = pos >> BLOCK_SHIFT;167s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;168if ((size_t)cacheEndPos >= blocks_.size()) {169cacheEndPos = blocks_.size() - 1;170}171172size_t blocksToRead = 0;173{174std::lock_guard<std::mutex> guard(blocksMutex_);175for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {176if (blocks_[(size_t)i] == 0) {177++blocksToRead;178if (blocksToRead >= MAX_BLOCKS_PER_READ) {179break;180}181182// TODO: Shouldn't we break as soon as we see a 1?183}184}185}186187s64 cacheFilePos = cacheStartPos << BLOCK_SHIFT;188size_t bytesRead = backend_->ReadAt(cacheFilePos, blocksToRead << BLOCK_SHIFT, &cache_[cacheFilePos], flags);189190// In case there was an error, let's not mark blocks that failed to read as read.191u32 blocksActuallyRead = (u32)((bytesRead + BLOCK_SIZE - 1) >> BLOCK_SHIFT);192{193std::lock_guard<std::mutex> guard(blocksMutex_);194195// In case they were simultaneously read.196u32 blocksRead = 0;197for (size_t i = 0; i < blocksActuallyRead; ++i) {198if (blocks_[(size_t)cacheStartPos + i] == 0) {199blocks_[(size_t)cacheStartPos + i] = 1;200++blocksRead;201}202}203204if (aheadRemaining_ != 0) {205aheadRemaining_ -= blocksRead;206}207}208}209210void RamCachingFileLoader::StartReadAhead(s64 pos) {211if (cache_ == nullptr) {212return;213}214215std::lock_guard<std::mutex> guard(blocksMutex_);216aheadPos_ = pos;217if (aheadThreadRunning_) {218// Already going.219return;220}221222aheadThreadRunning_ = true;223aheadCancel_ = false;224if (aheadThread_.joinable())225aheadThread_.join();226aheadThread_ = std::thread([this] {227SetCurrentThreadName("FileLoaderReadAhead");228229AndroidJNIThreadContext jniContext;230231while (aheadRemaining_ != 0 && !aheadCancel_) {232// Where should we look?233const u32 cacheStartPos = NextAheadBlock();234if (cacheStartPos == 0xFFFFFFFF) {235// Must be full.236break;237}238u32 cacheEndPos = cacheStartPos + BLOCK_READAHEAD - 1;239if (cacheEndPos >= blocks_.size()) {240cacheEndPos = (u32)blocks_.size() - 1;241}242243for (u32 i = cacheStartPos; i <= cacheEndPos; ++i) {244if (blocks_[i] == 0) {245SaveIntoCache((u64)i << BLOCK_SHIFT, BLOCK_SIZE * BLOCK_READAHEAD, Flags::NONE);246break;247}248}249}250251aheadThreadRunning_ = false;252});253}254255u32 RamCachingFileLoader::NextAheadBlock() {256std::lock_guard<std::mutex> guard(blocksMutex_);257258// If we had an aheadPos_ set, start reading from there and go forward.259u32 startFrom = (u32)(aheadPos_ >> BLOCK_SHIFT);260// But next time, start from the beginning again.261aheadPos_ = 0;262263for (u32 i = startFrom; i < blocks_.size(); ++i) {264if (blocks_[i] == 0) {265return i;266}267}268269return 0xFFFFFFFF;270}271272273