CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/FileLoaders/CachingFileLoader.cpp
Views: 1401
// Copyright (c) 2012- 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 <cstring>18#include <thread>19#include <algorithm>2021#include "Common/Thread/ThreadUtil.h"22#include "Common/TimeUtil.h"23#include "Core/FileLoaders/CachingFileLoader.h"2425// Takes ownership of backend.26CachingFileLoader::CachingFileLoader(FileLoader *backend)27: ProxiedFileLoader(backend) {28}2930void CachingFileLoader::Prepare() {31std::call_once(preparedFlag_, [this](){32filesize_ = ProxiedFileLoader::FileSize();33if (filesize_ > 0) {34InitCache();35}36});37}3839CachingFileLoader::~CachingFileLoader() {40if (filesize_ > 0) {41ShutdownCache();42}43}4445bool CachingFileLoader::Exists() {46if (exists_ == -1) {47exists_ = ProxiedFileLoader::Exists() ? 1 : 0;48}49return exists_ == 1;50}5152bool CachingFileLoader::ExistsFast() {53if (exists_ == -1) {54return ProxiedFileLoader::ExistsFast();55}56return exists_ == 1;57}5859bool CachingFileLoader::IsDirectory() {60if (isDirectory_ == -1) {61isDirectory_ = ProxiedFileLoader::IsDirectory() ? 1 : 0;62}63return isDirectory_ == 1;64}6566s64 CachingFileLoader::FileSize() {67Prepare();68return filesize_;69}7071size_t CachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {72Prepare();73if (absolutePos >= filesize_) {74bytes = 0;75} else if (absolutePos + (s64)bytes >= filesize_) {76bytes = (size_t)(filesize_ - absolutePos);77}7879size_t readSize = 0;80if ((flags & Flags::HINT_UNCACHED) != 0) {81readSize = backend_->ReadAt(absolutePos, bytes, data, flags);82} else {83readSize = ReadFromCache(absolutePos, bytes, data);84// While in case the cache size is too small for the entire read.85while (readSize < bytes) {86SaveIntoCache(absolutePos + readSize, bytes - readSize, flags);87size_t bytesFromCache = ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize);88readSize += bytesFromCache;89if (bytesFromCache == 0) {90// We can't read any more.91break;92}93}9495StartReadAhead(absolutePos + readSize);96}9798return readSize;99}100101void CachingFileLoader::InitCache() {102cacheSize_ = 0;103oldestGeneration_ = 0;104generation_ = 0;105}106107void CachingFileLoader::ShutdownCache() {108// TODO: Maybe add some hint that deletion is coming soon?109// We can't delete while the thread is running, so have to wait.110// This should only happen from the menu.111while (aheadThreadRunning_) {112sleep_ms(1);113}114if (aheadThread_.joinable())115aheadThread_.join();116117std::lock_guard<std::recursive_mutex> guard(blocksMutex_);118for (auto block : blocks_) {119delete [] block.second.ptr;120}121blocks_.clear();122cacheSize_ = 0;123}124125size_t CachingFileLoader::ReadFromCache(s64 pos, size_t bytes, void *data) {126s64 cacheStartPos = pos >> BLOCK_SHIFT;127s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;128// TODO: Smarter.129size_t readSize = 0;130size_t offset = (size_t)(pos - (cacheStartPos << BLOCK_SHIFT));131u8 *p = (u8 *)data;132133std::lock_guard<std::recursive_mutex> guard(blocksMutex_);134for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {135auto block = blocks_.find(i);136if (block == blocks_.end()) {137return readSize;138}139block->second.generation = generation_;140141size_t toRead = std::min(bytes - readSize, (size_t)BLOCK_SIZE - offset);142memcpy(p + readSize, block->second.ptr + offset, toRead);143readSize += toRead;144145// Don't need an offset after the first read.146offset = 0;147}148return readSize;149}150151void CachingFileLoader::SaveIntoCache(s64 pos, size_t bytes, Flags flags, bool readingAhead) {152s64 cacheStartPos = pos >> BLOCK_SHIFT;153s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT;154155std::lock_guard<std::recursive_mutex> guard(blocksMutex_);156size_t blocksToRead = 0;157for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {158auto block = blocks_.find(i);159if (block != blocks_.end()) {160break;161}162++blocksToRead;163if (blocksToRead >= MAX_BLOCKS_PER_READ) {164break;165}166}167168if (!MakeCacheSpaceFor(blocksToRead, readingAhead) || blocksToRead == 0) {169return;170}171172if (blocksToRead == 1) {173blocksMutex_.unlock();174175u8 *buf = new u8[BLOCK_SIZE];176backend_->ReadAt(cacheStartPos << BLOCK_SHIFT, BLOCK_SIZE, buf, flags);177178blocksMutex_.lock();179// While blocksMutex_ was unlocked, another thread may have read.180// If so, free the one we just read.181if (blocks_.find(cacheStartPos) == blocks_.end()) {182blocks_[cacheStartPos] = BlockInfo(buf);183} else {184delete [] buf;185}186} else {187blocksMutex_.unlock();188189u8 *wholeRead = new u8[blocksToRead << BLOCK_SHIFT];190backend_->ReadAt(cacheStartPos << BLOCK_SHIFT, blocksToRead << BLOCK_SHIFT, wholeRead, flags);191192blocksMutex_.lock();193for (size_t i = 0; i < blocksToRead; ++i) {194if (blocks_.find(cacheStartPos + i) != blocks_.end()) {195// Written while we were busy, just skip it. Keep the existing block.196continue;197}198u8 *buf = new u8[BLOCK_SIZE];199memcpy(buf, wholeRead + (i << BLOCK_SHIFT), BLOCK_SIZE);200blocks_[cacheStartPos + i] = BlockInfo(buf);201}202delete[] wholeRead;203}204205cacheSize_ += blocksToRead;206++generation_;207}208209bool CachingFileLoader::MakeCacheSpaceFor(size_t blocks, bool readingAhead) {210size_t goal = MAX_BLOCKS_CACHED - blocks;211212if (readingAhead && cacheSize_ > goal) {213return false;214}215216std::lock_guard<std::recursive_mutex> guard(blocksMutex_);217while (cacheSize_ > goal) {218u64 minGeneration = generation_;219220// We increment the iterator inside because we delete things inside.221for (auto it = blocks_.begin(); it != blocks_.end(); ) {222// Check for the minimum seen generation.223// TODO: Do this smarter?224if (it->second.generation != 0 && it->second.generation < minGeneration) {225minGeneration = it->second.generation;226}227228// 0 means it was never used yet or was the first read (e.g. block descriptor.)229if (it->second.generation == oldestGeneration_ || it->second.generation == 0) {230s64 pos = it->first;231delete it->second.ptr;232blocks_.erase(it);233--cacheSize_;234235// Our iterator is invalid now. Keep going?236if (cacheSize_ > goal) {237// This finds the one at that position.238it = blocks_.lower_bound(pos);239} else {240break;241}242} else {243++it;244}245}246247// If we didn't find any, update to the lowest we did find.248oldestGeneration_ = minGeneration;249}250251return true;252}253254void CachingFileLoader::StartReadAhead(s64 pos) {255std::lock_guard<std::recursive_mutex> guard(blocksMutex_);256if (aheadThreadRunning_) {257// Already going.258return;259}260if (cacheSize_ + BLOCK_READAHEAD > MAX_BLOCKS_CACHED) {261// Not enough space to readahead.262return;263}264265aheadThreadRunning_ = true;266if (aheadThread_.joinable())267aheadThread_.join();268aheadThread_ = std::thread([this, pos] {269SetCurrentThreadName("FileLoaderReadAhead");270271AndroidJNIThreadContext jniContext;272273std::unique_lock<std::recursive_mutex> guard(blocksMutex_);274s64 cacheStartPos = pos >> BLOCK_SHIFT;275s64 cacheEndPos = cacheStartPos + BLOCK_READAHEAD - 1;276277for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) {278auto block = blocks_.find(i);279if (block == blocks_.end()) {280guard.unlock();281SaveIntoCache(i << BLOCK_SHIFT, BLOCK_SIZE * BLOCK_READAHEAD, Flags::NONE, true);282break;283}284}285286aheadThreadRunning_ = false;287});288}289290291