core: Add async filesystem operations (#2098)

This commit is contained in:
PabloMK7 2026-05-08 11:35:47 +02:00 committed by GitHub
parent 921ea178b9
commit 260f08c497
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 206 additions and 60 deletions

View File

@ -17,6 +17,7 @@ foreach(KEY IN ITEMS
"use_virtual_sd"
"use_custom_storage"
"compress_cia_installs"
"async_fs_operations"
"region_value"
"init_clock"
"init_time"

View File

@ -18,6 +18,7 @@ object SettingKeys {
external fun enable_required_online_lle_modules(): String
external fun use_virtual_sd(): String
external fun compress_cia_installs(): String
external fun async_fs_operations(): String
external fun region_value(): String
external fun init_clock(): String
external fun init_time(): String

View File

@ -54,6 +54,7 @@ enum class BooleanSetting(
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
@ -92,6 +93,7 @@ enum class BooleanSetting(
SHADERS_ACCURATE_MUL,
USE_ARTIC_BASE_CONTROLLER,
COMPRESS_INSTALLED_CIA_CONTENT,
ASYNC_FS_OPERATIONS,
ANDROID_HIDE_IMAGES,
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
APPLY_REGION_FREE_PATCH

View File

@ -599,6 +599,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.COMPRESS_INSTALLED_CIA_CONTENT.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.ASYNC_FS_OPERATIONS,
R.string.async_fs_operations,
R.string.async_fs_operations_description,
BooleanSetting.ASYNC_FS_OPERATIONS.key,
BooleanSetting.ASYNC_FS_OPERATIONS.defaultValue
)
)
}
}

View File

@ -232,6 +232,7 @@ void Config::ReadValues() {
// Storage
ReadSetting("Storage", Settings::values.compress_cia_installs);
ReadSetting("Storage", Settings::values.async_fs_operations);
// Utility
ReadSetting("Utility", Settings::values.dump_textures);

View File

@ -278,6 +278,10 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# 0 (default): Do not compress, 1: Compress
)") DECLARE_KEY(compress_cia_installs) BOOST_HANA_STRING(R"(
# Whether to enable async filesystem operations
# 0: Disabled, 1 (default): Enabled
)") DECLARE_KEY(async_fs_operations) BOOST_HANA_STRING(R"(
# Position of the performance overlay
# 0: Top Left
# 1: Center Top

View File

@ -233,6 +233,8 @@
<string name="storage">Storage</string>
<string name="compress_cia_installs">Compress installed CIA content</string>
<string name="compress_cia_installs_description">Compresses the content of CIA files when installed to the emulated SD card. Only affects CIA content which is installed while the setting is enabled.</string>
<string name="async_fs_operations">Asynchronous filesystem operations</string>
<string name="async_fs_operations_description">Makes emulated filesystem accesses asynchronous. Greatly reduces filesystem related stutter, but may slightly increase load times.</string>
<!-- Camera settings strings -->
<string name="inner_camera">Inner Camera</string>

View File

@ -486,6 +486,7 @@ void QtConfig::ReadDataStorageValues() {
ReadBasicSetting(Settings::values.use_virtual_sd);
ReadBasicSetting(Settings::values.use_custom_storage);
ReadBasicSetting(Settings::values.compress_cia_installs);
ReadBasicSetting(Settings::values.async_fs_operations);
const std::string nand_dir =
ReadSetting(Settings::QKeys::nand_directory, QStringLiteral("")).toString().toStdString();
@ -1079,6 +1080,7 @@ void QtConfig::SaveDataStorageValues() {
WriteBasicSetting(Settings::values.use_virtual_sd);
WriteBasicSetting(Settings::values.use_custom_storage);
WriteBasicSetting(Settings::values.compress_cia_installs);
WriteBasicSetting(Settings::values.async_fs_operations);
WriteSetting(Settings::QKeys::nand_directory,
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
QStringLiteral(""));

View File

@ -78,6 +78,7 @@ void ConfigureStorage::SetConfiguration() {
ui->toggle_virtual_sd->setChecked(Settings::values.use_virtual_sd.GetValue());
ui->toggle_custom_storage->setChecked(Settings::values.use_custom_storage.GetValue());
ui->toggle_compress_cia->setChecked(Settings::values.compress_cia_installs.GetValue());
ui->async_fs_operations->setChecked(Settings::values.async_fs_operations.GetValue());
ui->storage_group->setEnabled(!is_powered_on);
}
@ -86,6 +87,7 @@ void ConfigureStorage::ApplyConfiguration() {
Settings::values.use_virtual_sd = ui->toggle_virtual_sd->isChecked();
Settings::values.use_custom_storage = ui->toggle_custom_storage->isChecked();
Settings::values.compress_cia_installs = ui->toggle_compress_cia->isChecked();
Settings::values.async_fs_operations = ui->async_fs_operations->isChecked();
if (!Settings::values.use_custom_storage) {
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,

View File

@ -180,7 +180,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<layout class="QVBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QCheckBox" name="toggle_compress_cia">
<property name="text">
@ -191,6 +191,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="async_fs_operations">
<property name="text">
<string>Asynchronous filesystem operations</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes emulated filesystem accesses asynchronous. Greatly reduces filesystem related stutter, but may slightly increase load times.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -484,6 +484,7 @@ struct Values {
Setting<bool> use_virtual_sd{true, Keys::use_virtual_sd};
Setting<bool> use_custom_storage{false, Keys::use_custom_storage};
Setting<bool> compress_cia_installs{false, Keys::compress_cia_installs};
Setting<bool> async_fs_operations{true, Keys::async_fs_operations};
// System
SwitchableSetting<s32> region_value{REGION_VALUE_AUTO_SELECT, Keys::region_value};

View File

@ -17,6 +17,7 @@
#include "common/serialization/boost_small_vector.hpp"
#include "common/settings.h"
#include "common/swap.h"
#include "common/thread_worker.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
@ -327,6 +328,55 @@ public:
}
}
/**
* Same as RunAsync, but runs the async operation on a specific thread worker provided by the
* caller.
* @param worker The thread worker where the operation will be run.
* @param async_section Callable that takes Kernel::HLERequestContext& as argument
* and returns the amount of nanoseconds to wait before calling result_function.
* This callable is ran asynchronously.
* @param result_function Callable that takes Kernel::HLERequestContext& as argument
* and doesn't return anything. This callable is ran from the emulator thread
* and can be used to set the IPC result.
* @param really_async If set to false, it will call both async_section and result_function
* from the emulator thread.
*/
template <typename AsyncFunctor, typename ResultFunctor>
void RunOnThreadWorker(Common::ThreadWorker& worker, AsyncFunctor async_section,
ResultFunctor result_function, bool really_async = true) {
if (!Settings::values.deterministic_async_operations && really_async) {
kernel.ReportAsyncState(true);
// We use packaged_task so we can retrieve a std::future to pass to AsyncWakeUpCallback
auto task = std::make_shared<std::packaged_task<void()>>([this, async_section] {
s64 sleep_for = async_section(*this);
this->thread->WakeAfterDelay(sleep_for, true);
});
auto future = task->get_future();
worker.QueueWork([task]() { (*task)(); });
this->SleepClientThread("RunOnThread", std::chrono::nanoseconds(-1),
std::make_shared<AsyncWakeUpCallback<ResultFunctor>>(
kernel, result_function, std::move(future)));
} else {
// Synchronous fallback (same logic as original)
s64 sleep_for = async_section(*this);
if (sleep_for > 0) {
kernel.ReportAsyncState(true);
auto parallel_wakeup = std::make_shared<AsyncWakeUpCallback<ResultFunctor>>(
kernel, result_function, std::move(std::future<void>()));
this->SleepClientThread("RunOnThread", std::chrono::nanoseconds(sleep_for),
parallel_wakeup);
} else {
result_function(*this);
}
}
}
/**
* Resolves a object id from the request command buffer into a pointer to an object. See the
* "HLE handle protocol" section in the class documentation for more details.

View File

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -42,27 +42,54 @@ Directory::~Directory() {}
void Directory::Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 count = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer();
std::vector<FileSys::Entry> entries(count);
LOG_TRACE(Service_FS, "Read {}: count={}", GetName(), count);
// Number of entries actually read
u32 read = backend->Read(static_cast<u32>(entries.size()), entries.data());
buffer.Write(entries.data(), 0, read * sizeof(FileSys::Entry));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(ResultSuccess);
rb.Push(read);
rb.PushMappedBuffer(buffer);
struct AsyncData {
// Input
u32 count;
// Output
Result ret{0};
u32 read;
Kernel::MappedBuffer* buffer;
};
auto async_data = std::make_shared<AsyncData>();
async_data->count = rp.Pop<u32>();
async_data->buffer = &rp.PopMappedBuffer();
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
std::vector<FileSys::Entry> entries(async_data->count);
LOG_TRACE(Service_FS, "Read {}: count={}", GetName(), count);
// Number of entries actually read
async_data->read = backend->Read(static_cast<u32>(entries.size()), entries.data());
async_data->buffer->Write(entries.data(), 0, async_data->read * sizeof(FileSys::Entry));
return 0;
},
[async_data](Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb(ctx, 2, 2);
rb.Push(ResultSuccess);
rb.Push(async_data->read);
rb.PushMappedBuffer(*async_data->buffer);
},
Settings::values.async_fs_operations.GetValue());
}
void Directory::Close(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
LOG_TRACE(Service_FS, "Close {}", GetName());
backend->Close();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
ctx.RunAsync(
[this](Kernel::HLERequestContext& ctx) {
backend->Close();
return 0;
},
[](Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb(ctx, 1, 0);
rb.Push(ResultSuccess);
},
Settings::values.async_fs_operations.GetValue());
}
} // namespace Service::FS

View File

@ -75,8 +75,13 @@ void File::Read(Kernel::HLERequestContext& ctx) {
offset, length, backend->GetSize());
}
const bool allows_cache_reads = backend->AllowsCachedReads();
// Conventional reading if the backend does not support cache.
if (!backend->AllowsCachedReads()) {
// Do not use asynchronous operations on file reads, as in most cases
// there are many of them with small sizes. This causes a lot of delay
// due to thread communication overhead.
if (!allows_cache_reads) {
auto& buffer = rp.PopMappedBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
std::unique_ptr<u8[]> data = std::make_unique_for_overwrite<u8[]>(length);
@ -115,7 +120,8 @@ void File::Read(Kernel::HLERequestContext& ctx) {
async_data->length = length;
async_data->offset = offset;
async_data->cache_ready = backend->CacheReady(offset, length);
if (!async_data->cache_ready) {
const bool really_async = !async_data->cache_ready;
if (really_async) {
async_data->pre_timer = std::chrono::steady_clock::now();
}
@ -161,7 +167,7 @@ void File::Read(Kernel::HLERequestContext& ctx) {
}
rb.PushMappedBuffer(*async_data->buffer);
},
!async_data->cache_ready);
really_async);
}
void File::Write(Kernel::HLERequestContext& ctx) {
@ -186,6 +192,7 @@ void File::Write(Kernel::HLERequestContext& ctx) {
}
bool flush = (flags & 0xFF) != 0, update_timestamp = (flags & 0xFF00) != 0;
// Do not use asynchronous fs operations here for the same reason as File::Read.
if (!backend->AllowsCachedReads()) {
std::vector<u8> data(length);
buffer.Read(data.data(), 0, data.size());
@ -274,7 +281,7 @@ void File::SetSize(Kernel::HLERequestContext& ctx) {
return;
}
if (!backend->AllowsCachedReads()) {
if (!backend->AllowsCachedReads() && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
file->size = size;
backend->SetSize(size);
@ -303,7 +310,7 @@ void File::Close(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected",
connected_sessions.size());
if (!backend->AllowsCachedReads()) {
if (!backend->AllowsCachedReads() && !Settings::values.async_fs_operations) {
backend->Close();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
@ -334,7 +341,7 @@ void File::Flush(Kernel::HLERequestContext& ctx) {
return;
}
if (!backend->AllowsCachedReads()) {
if (!backend->AllowsCachedReads() && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
backend->Flush();
rb.Push(ResultSuccess);

View File

@ -67,7 +67,7 @@ void FS_USER::OpenFile(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "path={}, mode={} attrs={}", file_path.DebugStr(), mode.hex, attributes);
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
const auto [file_res, open_timeout_ns] =
archives.OpenFileFromArchive(archive_handle, file_path, mode, attributes);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
@ -100,7 +100,8 @@ void FS_USER::OpenFile(Kernel::HLERequestContext& ctx) {
async_data->attributes = attributes;
async_data->pre_timer = std::chrono::steady_clock::now();
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->file =
archives.OpenFileFromArchive(async_data->archive_handle, async_data->file_path,
@ -151,7 +152,7 @@ void FS_USER::OpenFileDirectly(Kernel::HLERequestContext& ctx) {
u64 program_id = GetSessionData(ctx.Session())->program_id;
if (!archives.ArchiveIsSlow(archive_id)) {
if (!archives.ArchiveIsSlow(archive_id) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
ResultVal<ArchiveHandle> archive_handle =
@ -203,7 +204,8 @@ void FS_USER::OpenFileDirectly(Kernel::HLERequestContext& ctx) {
async_data->attributes = attributes;
async_data->pre_timer = std::chrono::steady_clock::now();
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->archive_handle = archives.OpenArchive(
async_data->archive_id, async_data->archive_path, async_data->program_id);
@ -259,7 +261,7 @@ void FS_USER::DeleteFile(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "type={} size={} data={}", filename_type, filename_size,
file_path.DebugStr());
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.DeleteFileFromArchive(archive_handle, file_path));
return;
@ -275,7 +277,8 @@ void FS_USER::DeleteFile(Kernel::HLERequestContext& ctx) {
async_data->archive_handle = archive_handle;
async_data->file_path = file_path;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res =
archives.DeleteFileFromArchive(async_data->archive_handle, async_data->file_path);
@ -312,7 +315,7 @@ void FS_USER::RenameFile(Kernel::HLERequestContext& ctx) {
dest_filename_size, dest_file_path.DebugStr());
if (!archives.ArchiveIsSlow(src_archive_handle) &&
!archives.ArchiveIsSlow(dest_archive_handle)) {
!archives.ArchiveIsSlow(dest_archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.RenameFileBetweenArchives(src_archive_handle, src_file_path,
dest_archive_handle, dest_file_path));
@ -333,7 +336,8 @@ void FS_USER::RenameFile(Kernel::HLERequestContext& ctx) {
async_data->dest_archive_handle = dest_archive_handle;
async_data->dest_file_path = dest_file_path;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.RenameFileBetweenArchives(
async_data->src_archive_handle, async_data->src_file_path,
@ -362,7 +366,7 @@ void FS_USER::DeleteDirectory(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size,
dir_path.DebugStr());
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.DeleteDirectoryFromArchive(archive_handle, dir_path));
return;
@ -378,7 +382,8 @@ void FS_USER::DeleteDirectory(Kernel::HLERequestContext& ctx) {
async_data->archive_handle = archive_handle;
async_data->dir_path = dir_path;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.DeleteDirectoryFromArchive(async_data->archive_handle,
async_data->dir_path);
@ -406,7 +411,7 @@ void FS_USER::DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size,
dir_path.DebugStr());
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.DeleteDirectoryRecursivelyFromArchive(archive_handle, dir_path));
return;
@ -422,7 +427,8 @@ void FS_USER::DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) {
async_data->archive_handle = archive_handle;
async_data->dir_path = dir_path;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.DeleteDirectoryRecursivelyFromArchive(
async_data->archive_handle, async_data->dir_path);
@ -452,7 +458,7 @@ void FS_USER::CreateFile(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "type={} attributes={} size={:x} data={}", filename_type, attributes,
file_size, file_path.DebugStr());
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.CreateFileInArchive(archive_handle, file_path, file_size, attributes));
return;
@ -472,7 +478,8 @@ void FS_USER::CreateFile(Kernel::HLERequestContext& ctx) {
async_data->file_size = file_size;
async_data->attributes = attributes;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res =
archives.CreateFileInArchive(async_data->archive_handle, async_data->file_path,
@ -500,7 +507,7 @@ void FS_USER::CreateDirectory(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size,
dir_path.DebugStr());
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.CreateDirectoryFromArchive(archive_handle, dir_path, attributes));
return;
@ -518,7 +525,8 @@ void FS_USER::CreateDirectory(Kernel::HLERequestContext& ctx) {
async_data->dir_path = dir_path;
async_data->attributes = attributes;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.CreateDirectoryFromArchive(
async_data->archive_handle, async_data->dir_path, async_data->attributes);
@ -554,7 +562,7 @@ void FS_USER::RenameDirectory(Kernel::HLERequestContext& ctx) {
dest_dirname_size, dest_dir_path.DebugStr());
if (!archives.ArchiveIsSlow(src_archive_handle) &&
!archives.ArchiveIsSlow(dest_archive_handle)) {
!archives.ArchiveIsSlow(dest_archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.RenameDirectoryBetweenArchives(src_archive_handle, src_dir_path,
dest_archive_handle, dest_dir_path));
@ -575,7 +583,8 @@ void FS_USER::RenameDirectory(Kernel::HLERequestContext& ctx) {
async_data->dest_archive_handle = dest_archive_handle;
async_data->dest_dir_path = dest_dir_path;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.RenameDirectoryBetweenArchives(
async_data->src_archive_handle, async_data->src_dir_path,
@ -602,7 +611,7 @@ void FS_USER::OpenDirectory(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size,
dir_path.DebugStr());
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
ResultVal<std::shared_ptr<Directory>> dir_res =
archives.OpenDirectoryFromArchive(archive_handle, dir_path);
@ -630,7 +639,8 @@ void FS_USER::OpenDirectory(Kernel::HLERequestContext& ctx) {
async_data->archive_handle = archive_handle;
async_data->dir_path = dir_path;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->dir_res =
archives.OpenDirectoryFromArchive(async_data->archive_handle, async_data->dir_path);
@ -669,7 +679,7 @@ void FS_USER::OpenArchive(Kernel::HLERequestContext& ctx) {
u64 program_id = slot->program_id;
// Conventional opening
if (!archives.ArchiveIsSlow(archive_id)) {
if (!archives.ArchiveIsSlow(archive_id) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
const ResultVal<ArchiveHandle> handle =
archives.OpenArchive(archive_id, archive_path, program_id);
@ -699,7 +709,8 @@ void FS_USER::OpenArchive(Kernel::HLERequestContext& ctx) {
async_data->archive_path = archive_path;
async_data->program_id = program_id;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->handle = archives.OpenArchive(
async_data->archive_id, async_data->archive_path, async_data->program_id);
@ -727,7 +738,7 @@ void FS_USER::ControlArchive(Kernel::HLERequestContext& ctx) {
const auto input_size = rp.Pop<u32>();
const auto output_size = rp.Pop<u32>();
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
auto input = rp.PopMappedBuffer();
auto output = rp.PopMappedBuffer();
std::vector<u8> in_data(input_size);
@ -766,7 +777,8 @@ void FS_USER::ControlArchive(Kernel::HLERequestContext& ctx) {
async_data->in_buffer = &rp.PopMappedBuffer();
async_data->out_buffer = &rp.PopMappedBuffer();
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
std::vector<u8> in_data(async_data->in_size);
async_data->in_buffer->Read(in_data.data(), 0, in_data.size());
@ -792,7 +804,7 @@ void FS_USER::CloseArchive(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto archive_handle = rp.PopRaw<ArchiveHandle>();
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.CloseArchive(archive_handle));
return;
@ -806,7 +818,8 @@ void FS_USER::CloseArchive(Kernel::HLERequestContext& ctx) {
auto async_data = std::make_shared<AsyncData>();
async_data->handle = archive_handle;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.CloseArchive(async_data->handle);
return 0;
@ -1395,7 +1408,7 @@ void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
const u32 unique_id = rp.Pop<u32>();
const u8 title_variation = rp.Pop<u8>();
if (!secure_value_backend->BackendIsSlow()) {
if (!secure_value_backend->BackendIsSlow() && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(secure_value_backend->ObsoletedSetSaveDataSecureValue(unique_id, title_variation,
secure_value_slot, value));
@ -1416,7 +1429,8 @@ void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
async_data->unique_id = unique_id;
async_data->title_variation = title_variation;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = secure_value_backend->ObsoletedSetSaveDataSecureValue(
async_data->unique_id, async_data->title_variation, async_data->secure_value_slot,
@ -1436,7 +1450,7 @@ void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
const u32 unique_id = rp.Pop<u32>();
const u8 title_variation = rp.Pop<u8>();
if (!secure_value_backend->BackendIsSlow()) {
if (!secure_value_backend->BackendIsSlow() && !Settings::values.async_fs_operations) {
auto res = secure_value_backend->ObsoletedGetSaveDataSecureValue(unique_id, title_variation,
secure_value_slot);
if (res.Failed()) {
@ -1463,7 +1477,8 @@ void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
async_data->unique_id = unique_id;
async_data->title_variation = title_variation;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = secure_value_backend->ObsoletedGetSaveDataSecureValue(
async_data->unique_id, async_data->title_variation, async_data->secure_value_slot);
@ -1511,7 +1526,7 @@ void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
const u32 secure_value_slot = rp.Pop<u32>();
const u64 value = rp.Pop<u64>();
if (!secure_value_backend->BackendIsSlow()) {
if (!secure_value_backend->BackendIsSlow() && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(secure_value_backend->SetThisSaveDataSecureValue(secure_value_slot, value));
return;
@ -1527,7 +1542,8 @@ void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
async_data->value = value;
async_data->secure_value_slot = secure_value_slot;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = secure_value_backend->SetThisSaveDataSecureValue(
async_data->secure_value_slot, async_data->value);
@ -1544,7 +1560,7 @@ void FS_USER::GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 secure_value_slot = rp.Pop<u32>();
if (!secure_value_backend->BackendIsSlow()) {
if (!secure_value_backend->BackendIsSlow() && !Settings::values.async_fs_operations) {
auto res = secure_value_backend->GetThisSaveDataSecureValue(secure_value_slot);
if (res.Failed()) {
@ -1569,7 +1585,8 @@ void FS_USER::GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
auto async_data = std::make_shared<AsyncData>();
async_data->secure_value_slot = secure_value_slot;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res =
secure_value_backend->GetThisSaveDataSecureValue(async_data->secure_value_slot);
@ -1599,7 +1616,7 @@ void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
const u64 value = rp.Pop<u64>();
const bool flush = rp.Pop<bool>();
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(archives.SetSaveDataSecureValue(archive_handle, secure_value_slot, value, flush));
@ -1620,7 +1637,8 @@ void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
async_data->secure_value_slot = secure_value_slot;
async_data->flush = flush;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.SetSaveDataSecureValue(async_data->archive_handle,
async_data->secure_value_slot,
@ -1639,7 +1657,7 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
const auto archive_handle = rp.PopRaw<ArchiveHandle>();
const u32 secure_value_slot = rp.Pop<u32>();
if (!archives.ArchiveIsSlow(archive_handle)) {
if (!archives.ArchiveIsSlow(archive_handle) && !Settings::values.async_fs_operations) {
auto res = archives.GetSaveDataSecureValue(archive_handle, secure_value_slot);
if (res.Failed()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -1665,7 +1683,8 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
async_data->archive_handle = archive_handle;
async_data->secure_value_slot = secure_value_slot;
ctx.RunAsync(
ctx.RunOnThreadWorker(
fs_async_worker,
[this, async_data](Kernel::HLERequestContext& ctx) {
async_data->res = archives.GetSaveDataSecureValue(async_data->archive_handle,
async_data->secure_value_slot);
@ -1900,6 +1919,9 @@ FS_USER::FS_USER(Core::System& system)
template <class Archive>
void Service::FS::FS_USER::serialize(Archive& ar, const unsigned int) {
DEBUG_SERIALIZATION_POINT;
if (!Archive::is_loading::value) {
fs_async_worker.WaitForRequests();
}
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
ar & priority;
ar & secure_value_backend;

View File

@ -49,6 +49,9 @@ private:
class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
public:
explicit FS_USER(Core::System& system);
~FS_USER() {
fs_async_worker.WaitForRequests();
}
// On real HW this is part of FSReg (FSReg:Register). But since that module is only used by
// loader and pm, which we HLEed, we can just directly use it here
@ -756,6 +759,8 @@ private:
std::shared_ptr<FileSys::SecureValueBackend> secure_value_backend;
Common::ThreadWorker fs_async_worker{1, "FSUSER_Worker"};
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;