diff --git a/CMakeModules/GenerateSettingKeys.cmake b/CMakeModules/GenerateSettingKeys.cmake
index 96a131f72..38397a63b 100644
--- a/CMakeModules/GenerateSettingKeys.cmake
+++ b/CMakeModules/GenerateSettingKeys.cmake
@@ -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"
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt
index de2ab1118..f37e3636c 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/SettingKeys.kt
@@ -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
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt
index cfa4aa946..9fcba652f 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt
@@ -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
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 3c12a5a8f..282caad3b 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -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
+ )
+ )
}
}
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 8ac729fce..08f63ad77 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -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);
diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h
index bfe406d77..444120e8e 100644
--- a/src/android/app/src/main/jni/default_ini.h
+++ b/src/android/app/src/main/jni/default_ini.h
@@ -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
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 101eb4648..0a26d32fa 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -233,6 +233,8 @@
Storage
Compress installed CIA content
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.
+ Asynchronous filesystem operations
+ Makes emulated filesystem accesses asynchronous. Greatly reduces filesystem related stutter, but may slightly increase load times.
Inner Camera
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 30d54a93f..ff0a63c1a 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -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(""));
diff --git a/src/citra_qt/configuration/configure_storage.cpp b/src/citra_qt/configuration/configure_storage.cpp
index a8cabd987..aa468d4c1 100644
--- a/src/citra_qt/configuration/configure_storage.cpp
+++ b/src/citra_qt/configuration/configure_storage.cpp
@@ -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,
diff --git a/src/citra_qt/configuration/configure_storage.ui b/src/citra_qt/configuration/configure_storage.ui
index 5cfd0c449..6e8909a3a 100644
--- a/src/citra_qt/configuration/configure_storage.ui
+++ b/src/citra_qt/configuration/configure_storage.ui
@@ -180,7 +180,7 @@
-
-
+
-
@@ -191,6 +191,16 @@
+ -
+
+
+ Asynchronous filesystem operations
+
+
+ <html><head/><body><p>Makes emulated filesystem accesses asynchronous. Greatly reduces filesystem related stutter, but may slightly increase load times.</p></body></html>
+
+
+
diff --git a/src/common/settings.h b/src/common/settings.h
index c5c924201..6bddf2ee5 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -484,6 +484,7 @@ struct Values {
Setting use_virtual_sd{true, Keys::use_virtual_sd};
Setting use_custom_storage{false, Keys::use_custom_storage};
Setting compress_cia_installs{false, Keys::compress_cia_installs};
+ Setting async_fs_operations{true, Keys::async_fs_operations};
// System
SwitchableSetting region_value{REGION_VALUE_AUTO_SELECT, Keys::region_value};
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 1b3b8c93e..bdd7877fc 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -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
+ 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>([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>(
+ 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>(
+ kernel, result_function, std::move(std::future()));
+ 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.
diff --git a/src/core/hle/service/fs/directory.cpp b/src/core/hle/service/fs/directory.cpp
index 0a862f097..13f011b5c 100644
--- a/src/core/hle/service/fs/directory.cpp
+++ b/src/core/hle/service/fs/directory.cpp
@@ -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();
- auto& buffer = rp.PopMappedBuffer();
- std::vector entries(count);
- LOG_TRACE(Service_FS, "Read {}: count={}", GetName(), count);
- // Number of entries actually read
- u32 read = backend->Read(static_cast(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();
+ async_data->count = rp.Pop();
+ async_data->buffer = &rp.PopMappedBuffer();
+
+ ctx.RunAsync(
+ [this, async_data](Kernel::HLERequestContext& ctx) {
+ std::vector entries(async_data->count);
+ LOG_TRACE(Service_FS, "Read {}: count={}", GetName(), count);
+ // Number of entries actually read
+ async_data->read = backend->Read(static_cast(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
diff --git a/src/core/hle/service/fs/file.cpp b/src/core/hle/service/fs/file.cpp
index 7ba13f935..6ba8f2b77 100644
--- a/src/core/hle/service/fs/file.cpp
+++ b/src/core/hle/service/fs/file.cpp
@@ -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 data = std::make_unique_for_overwrite(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 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);
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index c9641a518..fdbdfbc58 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -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 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> 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 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();
const auto output_size = rp.Pop();
- 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 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 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();
- 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();
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();
const u8 title_variation = rp.Pop();
- 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();
const u8 title_variation = rp.Pop();
- 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();
const u64 value = rp.Pop();
- 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();
- 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();
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();
const bool flush = rp.Pop();
- 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();
const u32 secure_value_slot = rp.Pop();
- 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
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(*this);
ar & priority;
ar & secure_value_backend;
diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h
index f94f318f2..9dd021bdc 100644
--- a/src/core/hle/service/fs/fs_user.h
+++ b/src/core/hle/service/fs/fs_user.h
@@ -49,6 +49,9 @@ private:
class FS_USER final : public ServiceFramework {
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 secure_value_backend;
+ Common::ThreadWorker fs_async_worker{1, "FSUSER_Worker"};
+
template
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;