diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 214241f2b..991f85ea1 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -47,7 +47,7 @@ TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback ca } void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, - std::uintptr_t user_data, std::size_t core_id) { + std::uintptr_t user_data, std::size_t core_id, bool thread_safe_mode) { if (event_queue_locked) { return; } @@ -61,18 +61,29 @@ void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_ timer = timers.at(core_id).get(); } - s64 timeout = timer->GetTicks() + cycles_into_future; - if (current_timer == timer) { - // If this event needs to be scheduled before the next advance(), force one early - if (!timer->is_timer_sane) - timer->ForceExceptionCheck(cycles_into_future); + if (thread_safe_mode) { + // Events scheduled in thread safe mode come after blocking operations with + // unpredictable timings in the host machine, so there is no need to be cycle accurate. + // To prevent the event from scheduling before the next advance(), we set a minimum time + // of MAX_SLICE_LENGTH * 2 cycles into the future. + cycles_into_future = std::max(static_cast(MAX_SLICE_LENGTH * 2), cycles_into_future); - timer->event_queue.emplace_back( - Event{timeout, timer->event_fifo_id++, user_data, event_type}); - std::push_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>()); - } else { timer->ts_queue.Push(Event{static_cast(timer->GetTicks() + cycles_into_future), 0, user_data, event_type}); + } else { + s64 timeout = timer->GetTicks() + cycles_into_future; + if (current_timer == timer) { + // If this event needs to be scheduled before the next advance(), force one early + if (!timer->is_timer_sane) + timer->ForceExceptionCheck(cycles_into_future); + + timer->event_queue.emplace_back( + Event{timeout, timer->event_fifo_id++, user_data, event_type}); + std::push_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>()); + } else { + timer->ts_queue.Push(Event{static_cast(timer->GetTicks() + cycles_into_future), 0, + user_data, event_type}); + } } } diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 006d10959..9de9011a5 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -254,9 +254,12 @@ public: */ TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback); + // Make sure to use thread_safe_mode = true if called from a different thread than the + // emulator thread, such as coroutines. void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, std::uintptr_t user_data = 0, - std::size_t core_id = std::numeric_limits::max()); + std::size_t core_id = std::numeric_limits::max(), + bool thread_safe_mode = false); void UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data); diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 3c287946a..f8c4a31a7 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -247,6 +248,76 @@ public: std::chrono::nanoseconds timeout, std::shared_ptr callback); +private: + template + class AsyncWakeUpCallback : public WakeupCallback { + public: + explicit AsyncWakeUpCallback(ResultFunctor res_functor, std::future fut) + : functor(res_functor) { + future = std::move(fut); + } + + void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + functor(ctx); + } + + private: + ResultFunctor functor; + std::future future; + + template + void serialize(Archive& ar, const unsigned int) { + if (!Archive::is_loading::value && future.valid()) { + future.wait(); + } + ar& functor; + } + friend class boost::serialization::access; + }; + +public: + /** + * Puts the game thread to sleep and calls the specified async_section asynchronously. + * Once the execution of the async section finishes, result_function is called. Use this + * mechanism to run blocking IO operations, so that other game threads are allowed to run + * while the one performing the blocking operation waits. + * @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 RunAsync(AsyncFunctor async_section, ResultFunctor result_function, + bool really_async = true) { + + if (really_async) { + this->SleepClientThread( + "RunAsync", std::chrono::nanoseconds(-1), + std::make_shared>( + result_function, + std::move(std::async(std::launch::async, [this, async_section] { + s64 sleep_for = async_section(*this); + this->thread->WakeAfterDelay(sleep_for, true); + })))); + + } else { + s64 sleep_for = async_section(*this); + if (sleep_for > 0) { + auto parallel_wakeup = std::make_shared>( + result_function, std::move(std::future())); + this->SleepClientThread("RunAsync", 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/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 635e73f89..fa3f88784 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -244,13 +244,15 @@ void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) { thread->ResumeFromWait(); } -void Thread::WakeAfterDelay(s64 nanoseconds) { +void Thread::WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode) { // Don't schedule a wakeup if the thread wants to wait forever if (nanoseconds == -1) return; + size_t core = thread_safe_mode ? core_id : std::numeric_limits::max(); thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds), - thread_manager.ThreadWakeupEventType, thread_id); + thread_manager.ThreadWakeupEventType, thread_id, + core, thread_safe_mode); } void Thread::ResumeFromWait() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 493d6d5c6..955962695 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -238,8 +238,10 @@ public: /** * Schedules an event to wake up the specified thread after the specified delay * @param nanoseconds The time this thread will be allowed to sleep for + * @param thread_safe_mode Set to true if called from a different thread than the emulator + * thread, such as coroutines. */ - void WakeAfterDelay(s64 nanoseconds); + void WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode = false); /** * Sets the result after the thread awakens (from either WaitSynchronization SVC)