Implement RomFS cache and async reads. (#7089)
* Implement RomFS cache and async reads. * Suggestions and fix compilation. * Apply suggestions
This commit is contained in:
parent
79ea06b226
commit
4284893044
10 changed files with 404 additions and 22 deletions
|
@ -124,6 +124,7 @@ add_library(citra_common STATIC
|
|||
serialization/boost_flat_set.h
|
||||
serialization/boost_small_vector.hpp
|
||||
serialization/boost_vector.hpp
|
||||
static_lru_cache.h
|
||||
string_literal.h
|
||||
string_util.cpp
|
||||
string_util.h
|
||||
|
|
|
@ -1155,6 +1155,43 @@ std::size_t IOFile::ReadImpl(void* data, std::size_t length, std::size_t data_si
|
|||
return std::fread(data, data_size, length, m_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::size_t pread(int fd, void* buf, size_t count, uint64_t offset) {
|
||||
long unsigned int read_bytes = 0;
|
||||
OVERLAPPED overlapped = {0};
|
||||
HANDLE file = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
||||
|
||||
overlapped.OffsetHigh = static_cast<uint32_t>(offset >> 32);
|
||||
overlapped.Offset = static_cast<uint32_t>(offset & 0xFFFF'FFFFLL);
|
||||
SetLastError(0);
|
||||
bool ret = ReadFile(file, buf, static_cast<uint32_t>(count), &read_bytes, &overlapped);
|
||||
|
||||
if (!ret && GetLastError() != ERROR_HANDLE_EOF) {
|
||||
errno = GetLastError();
|
||||
return std::numeric_limits<std::size_t>::max();
|
||||
}
|
||||
return read_bytes;
|
||||
}
|
||||
#else
|
||||
#define pread ::pread
|
||||
#endif
|
||||
|
||||
std::size_t IOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
|
||||
std::size_t offset) {
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
return std::numeric_limits<std::size_t>::max();
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEBUG_ASSERT(data != nullptr);
|
||||
|
||||
return pread(fileno(m_file), data, data_size * length, offset);
|
||||
}
|
||||
|
||||
std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
|
|
|
@ -294,6 +294,18 @@ public:
|
|||
return items_read;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t ReadAtArray(T* data, std::size_t length, std::size_t offset) {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
|
||||
std::size_t items_read = ReadAtImpl(data, length, sizeof(T), offset);
|
||||
if (items_read != length)
|
||||
m_good = false;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t WriteArray(const T* data, std::size_t length) {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
|
@ -312,6 +324,12 @@ public:
|
|||
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t ReadAtBytes(T* data, std::size_t length, std::size_t offset) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
return ReadAtArray(reinterpret_cast<char*>(data), length, offset);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t WriteBytes(const T* data, std::size_t length) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
|
@ -363,6 +381,8 @@ public:
|
|||
|
||||
private:
|
||||
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
|
||||
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
|
||||
std::size_t offset);
|
||||
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
|
||||
|
||||
bool Open();
|
||||
|
|
113
src/common/static_lru_cache.h
Normal file
113
src/common/static_lru_cache.h
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Modified version of: https://www.boost.org/doc/libs/1_79_0/boost/compute/detail/lru_cache.hpp
|
||||
// Most important change is the use of an array instead of a map, so that elements are
|
||||
// statically allocated. The insert and get methods have been merged into the request method.
|
||||
// Original license:
|
||||
//
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (c) 2013 Kyle Lutz <kyle.r.lutz@gmail.com>
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0
|
||||
// See accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
// See http://boostorg.github.com/compute for more information.
|
||||
//---------------------------------------------------------------------------//
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
// a cache which evicts the least recently used item when it is full
|
||||
// the cache elements are statically allocated.
|
||||
template <class Key, class Value, size_t Size>
|
||||
class StaticLRUCache {
|
||||
public:
|
||||
using key_type = Key;
|
||||
using value_type = Value;
|
||||
using list_type = std::list<std::pair<Key, size_t>>;
|
||||
using array_type = std::array<Value, Size>;
|
||||
|
||||
StaticLRUCache() = default;
|
||||
|
||||
~StaticLRUCache() = default;
|
||||
|
||||
size_t size() const {
|
||||
return m_list.size();
|
||||
}
|
||||
|
||||
constexpr size_t capacity() const {
|
||||
return m_array.size();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return m_list.empty();
|
||||
}
|
||||
|
||||
bool contains(const key_type& key) const {
|
||||
return find(key) != m_list.end();
|
||||
}
|
||||
|
||||
// Requests an element from the cache. If it is not found,
|
||||
// the element is inserted using its key.
|
||||
// Returns whether the element was present in the cache
|
||||
// and a reference to the element itself.
|
||||
std::pair<bool, value_type&> request(const key_type& key) {
|
||||
// lookup value in the cache
|
||||
auto i = find(key);
|
||||
if (i == m_list.cend()) {
|
||||
size_t next_index = size();
|
||||
// insert item into the cache, but first check if it is full
|
||||
if (next_index >= capacity()) {
|
||||
// cache is full, evict the least recently used item
|
||||
next_index = evict();
|
||||
}
|
||||
|
||||
// insert the new item
|
||||
m_list.push_front(std::make_pair(key, next_index));
|
||||
return std::pair<bool, value_type&>(false, m_array[next_index]);
|
||||
}
|
||||
// return the value, but first update its place in the most
|
||||
// recently used list
|
||||
if (i != m_list.cbegin()) {
|
||||
// move item to the front of the most recently used list
|
||||
auto backup = *i;
|
||||
m_list.erase(i);
|
||||
m_list.push_front(backup);
|
||||
|
||||
// return the value
|
||||
return std::pair<bool, value_type&>(true, m_array[backup.second]);
|
||||
} else {
|
||||
// the item is already at the front of the most recently
|
||||
// used list so just return it
|
||||
return std::pair<bool, value_type&>(true, m_array[i->second]);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
m_list.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
typename list_type::const_iterator find(const key_type& key) const {
|
||||
return std::find_if(m_list.cbegin(), m_list.cend(),
|
||||
[&key](const auto& el) { return el.first == key; });
|
||||
}
|
||||
|
||||
size_t evict() {
|
||||
// evict item from the end of most recently used list
|
||||
typename list_type::iterator i = --m_list.end();
|
||||
size_t evicted_index = i->second;
|
||||
m_list.erase(i);
|
||||
return evicted_index;
|
||||
}
|
||||
|
||||
private:
|
||||
array_type m_array;
|
||||
list_type m_list;
|
||||
};
|
||||
|
||||
} // namespace Common
|
Loading…
Add table
Add a link
Reference in a new issue