From a4d11f4427859cbe82fc825f8493844e17bb668f Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Sat, 10 Sep 2022 01:48:15 -0700
Subject: [PATCH] core: Partially persist emulation state across game boots.

---
 src/core/core.cpp              | 65 +++++++++++++++++++---------------
 src/core/core.h                | 10 ++++--
 src/core/core_timing.cpp       | 29 +++++++--------
 src/core/core_timing.h         |  7 ++--
 src/tests/core/core_timing.cpp |  3 --
 src/yuzu/bootmanager.cpp       |  4 +--
 src/yuzu/main.cpp              |  1 +
 src/yuzu_cmd/yuzu.cpp          |  4 ++-
 8 files changed, 65 insertions(+), 58 deletions(-)

diff --git a/src/core/core.cpp b/src/core/core.cpp
index 1deeee154..2c4c0dbe4 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -133,6 +133,30 @@ struct System::Impl {
         : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
           cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
 
+    void Initialize(System& system) {
+        device_memory = std::make_unique<Core::DeviceMemory>();
+
+        is_multicore = Settings::values.use_multi_core.GetValue();
+
+        core_timing.SetMulticore(is_multicore);
+        core_timing.Initialize([&system]() { system.RegisterHostThread(); });
+
+        const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
+        const auto current_time =
+            std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
+        Settings::values.custom_rtc_differential =
+            Settings::values.custom_rtc.value_or(current_time) - current_time;
+
+        // Create a default fs if one doesn't already exist.
+        if (virtual_filesystem == nullptr)
+            virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
+        if (content_provider == nullptr)
+            content_provider = std::make_unique<FileSys::ContentProviderUnion>();
+
+        // Create default implementations of applets if one is not provided.
+        applet_manager.SetDefaultAppletsIfMissing();
+    }
+
     SystemResultStatus Run() {
         std::unique_lock<std::mutex> lk(suspend_guard);
         status = SystemResultStatus::Success;
@@ -178,37 +202,17 @@ struct System::Impl {
         debugger = std::make_unique<Debugger>(system, port);
     }
 
-    SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
+    SystemResultStatus SetupForMainProcess(System& system, Frontend::EmuWindow& emu_window) {
         LOG_DEBUG(Core, "initialized OK");
 
-        device_memory = std::make_unique<Core::DeviceMemory>();
-
-        is_multicore = Settings::values.use_multi_core.GetValue();
         is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
 
         kernel.SetMulticore(is_multicore);
         cpu_manager.SetMulticore(is_multicore);
         cpu_manager.SetAsyncGpu(is_async_gpu);
-        core_timing.SetMulticore(is_multicore);
 
         kernel.Initialize();
         cpu_manager.Initialize();
-        core_timing.Initialize([&system]() { system.RegisterHostThread(); });
-
-        const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
-        const auto current_time =
-            std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
-        Settings::values.custom_rtc_differential =
-            Settings::values.custom_rtc.value_or(current_time) - current_time;
-
-        // Create a default fs if one doesn't already exist.
-        if (virtual_filesystem == nullptr)
-            virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
-        if (content_provider == nullptr)
-            content_provider = std::make_unique<FileSys::ContentProviderUnion>();
-
-        /// Create default implementations of applets if one is not provided.
-        applet_manager.SetDefaultAppletsIfMissing();
 
         /// Reset all glue registrations
         arp_manager.ResetAll();
@@ -253,11 +257,11 @@ struct System::Impl {
             return SystemResultStatus::ErrorGetLoader;
         }
 
-        SystemResultStatus init_result{Init(system, emu_window)};
+        SystemResultStatus init_result{SetupForMainProcess(system, emu_window)};
         if (init_result != SystemResultStatus::Success) {
             LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
                          static_cast<int>(init_result));
-            Shutdown();
+            ShutdownMainProcess();
             return init_result;
         }
 
@@ -276,7 +280,7 @@ struct System::Impl {
         const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
         if (load_result != Loader::ResultStatus::Success) {
             LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
-            Shutdown();
+            ShutdownMainProcess();
 
             return static_cast<SystemResultStatus>(
                 static_cast<u32>(SystemResultStatus::ErrorLoader) + static_cast<u32>(load_result));
@@ -335,7 +339,7 @@ struct System::Impl {
         return status;
     }
 
-    void Shutdown() {
+    void ShutdownMainProcess() {
         SetShuttingDown(true);
 
         // Log last frame performance stats if game was loded
@@ -369,7 +373,7 @@ struct System::Impl {
         cheat_engine.reset();
         telemetry_session.reset();
         time_manager.Shutdown();
-        core_timing.Shutdown();
+        core_timing.ClearPendingEvents();
         app_loader.reset();
         audio_core.reset();
         gpu_core.reset();
@@ -377,7 +381,6 @@ struct System::Impl {
         perf_stats.reset();
         kernel.Shutdown();
         memory.Reset();
-        applet_manager.ClearAll();
 
         if (auto room_member = room_network.GetRoomMember().lock()) {
             Network::GameInfo game_info{};
@@ -520,6 +523,10 @@ const CpuManager& System::GetCpuManager() const {
     return impl->cpu_manager;
 }
 
+void System::Initialize() {
+    impl->Initialize(*this);
+}
+
 SystemResultStatus System::Run() {
     return impl->Run();
 }
@@ -540,8 +547,8 @@ void System::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) {
     impl->kernel.InvalidateCpuInstructionCacheRange(addr, size);
 }
 
-void System::Shutdown() {
-    impl->Shutdown();
+void System::ShutdownMainProcess() {
+    impl->ShutdownMainProcess();
 }
 
 bool System::IsShuttingDown() const {
diff --git a/src/core/core.h b/src/core/core.h
index 7843cc8ad..4ebedffd9 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -142,6 +142,12 @@ public:
     System(System&&) = delete;
     System& operator=(System&&) = delete;
 
+    /**
+     * Initializes the system
+     * This function will initialize core functionaility used for system emulation
+     */
+    void Initialize();
+
     /**
      * Run the OS and Application
      * This function will start emulation and run the relevant devices
@@ -166,8 +172,8 @@ public:
 
     void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
 
-    /// Shutdown the emulated system.
-    void Shutdown();
+    /// Shutdown the main emulated process.
+    void ShutdownMainProcess();
 
     /// Check if the core is shutting down.
     [[nodiscard]] bool IsShuttingDown() const;
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 2678ce532..2afb2696c 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -40,7 +40,17 @@ struct CoreTiming::Event {
 CoreTiming::CoreTiming()
     : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
 
-CoreTiming::~CoreTiming() = default;
+CoreTiming::~CoreTiming() {
+    paused = true;
+    shutting_down = true;
+    pause_event.Set();
+    event.Set();
+    if (timer_thread) {
+        timer_thread->join();
+    }
+    timer_thread.reset();
+    has_started = false;
+}
 
 void CoreTiming::ThreadEntry(CoreTiming& instance) {
     constexpr char name[] = "HostTiming";
@@ -65,17 +75,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
     }
 }
 
-void CoreTiming::Shutdown() {
-    paused = true;
-    shutting_down = true;
-    pause_event.Set();
-    event.Set();
-    if (timer_thread) {
-        timer_thread->join();
-    }
-    ClearPendingEvents();
-    timer_thread.reset();
-    has_started = false;
+void CoreTiming::ClearPendingEvents() {
+    event_queue.clear();
 }
 
 void CoreTiming::Pause(bool is_paused) {
@@ -196,10 +197,6 @@ u64 CoreTiming::GetClockTicks() const {
     return CpuCyclesToClockCycles(ticks);
 }
 
-void CoreTiming::ClearPendingEvents() {
-    event_queue.clear();
-}
-
 void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
     std::scoped_lock lock{basic_lock};
 
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 3259397b2..7996b529f 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -61,8 +61,8 @@ public:
     /// required to end slice - 1 and start slice 0 before the first cycle of code is executed.
     void Initialize(std::function<void()>&& on_thread_init_);
 
-    /// Tears down all timing related functionality.
-    void Shutdown();
+    /// Clear all pending events. This should ONLY be done on exit.
+    void ClearPendingEvents();
 
     /// Sets if emulation is multicore or single core, must be set before Initialize
     void SetMulticore(bool is_multicore_) {
@@ -136,9 +136,6 @@ public:
 private:
     struct Event;
 
-    /// Clear all pending events. This should ONLY be done on exit.
-    void ClearPendingEvents();
-
     static void ThreadEntry(CoreTiming& instance);
     void ThreadLoop();
 
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index 7c432a63c..284b2ae66 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -40,9 +40,6 @@ struct ScopeInit final {
         core_timing.SetMulticore(true);
         core_timing.Initialize([]() {});
     }
-    ~ScopeInit() {
-        core_timing.Shutdown();
-    }
 
     Core::Timing::CoreTiming core_timing;
 };
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 24251247d..6acfb7b06 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -120,8 +120,8 @@ void EmuThread::run() {
         }
     }
 
-    // Shutdown the core emulation
-    system.Shutdown();
+    // Shutdown the main emulated process
+    system.ShutdownMainProcess();
 
 #if MICROPROFILE_ENABLED
     MicroProfileOnThreadExit();
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a94624be6..501c34255 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -294,6 +294,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
 #ifdef __linux__
     SetupSigInterrupts();
 #endif
+    system->Initialize();
 
     Common::Log::Initialize();
     LoadTranslation();
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 3a0f33cba..e16f79eb4 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -302,6 +302,8 @@ int main(int argc, char** argv) {
     }
 
     Core::System system{};
+    system.Initialize();
+
     InputCommon::InputSubsystem input_subsystem{};
 
     // Apply the command line arguments
@@ -392,7 +394,7 @@ int main(int argc, char** argv) {
     }
     system.DetachDebugger();
     void(system.Pause());
-    system.Shutdown();
+    system.ShutdownMainProcess();
 
     detached_tasks.WaitForAllTasks();
     return 0;