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;