mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Add HLERequestContext::RunAsync (#7027)
This commit is contained in:
		
							parent
							
								
									38a0a85777
								
							
						
					
					
						commit
						9ec4954380
					
				
					 5 changed files with 103 additions and 14 deletions
				
			
		|  | @ -47,7 +47,7 @@ TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback ca | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, | 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) { |     if (event_queue_locked) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  | @ -61,6 +61,16 @@ void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_ | ||||||
|         timer = timers.at(core_id).get(); |         timer = timers.at(core_id).get(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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<s64>(MAX_SLICE_LENGTH * 2), cycles_into_future); | ||||||
|  | 
 | ||||||
|  |         timer->ts_queue.Push(Event{static_cast<s64>(timer->GetTicks() + cycles_into_future), 0, | ||||||
|  |                                    user_data, event_type}); | ||||||
|  |     } else { | ||||||
|         s64 timeout = timer->GetTicks() + cycles_into_future; |         s64 timeout = timer->GetTicks() + cycles_into_future; | ||||||
|         if (current_timer == timer) { |         if (current_timer == timer) { | ||||||
|             // If this event needs to be scheduled before the next advance(), force one early
 |             // If this event needs to be scheduled before the next advance(), force one early
 | ||||||
|  | @ -75,6 +85,7 @@ void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_ | ||||||
|                                        user_data, event_type}); |                                        user_data, event_type}); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void Timing::UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data) { | void Timing::UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data) { | ||||||
|     if (event_queue_locked) { |     if (event_queue_locked) { | ||||||
|  |  | ||||||
|  | @ -254,9 +254,12 @@ public: | ||||||
|      */ |      */ | ||||||
|     TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback); |     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, |     void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, | ||||||
|                        std::uintptr_t user_data = 0, |                        std::uintptr_t user_data = 0, | ||||||
|                        std::size_t core_id = std::numeric_limits<std::size_t>::max()); |                        std::size_t core_id = std::numeric_limits<std::size_t>::max(), | ||||||
|  |                        bool thread_safe_mode = false); | ||||||
| 
 | 
 | ||||||
|     void UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data); |     void UnscheduleEvent(const TimingEventType* event_type, std::uintptr_t user_data); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <array> | #include <array> | ||||||
| #include <chrono> | #include <chrono> | ||||||
|  | #include <future> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | @ -247,6 +248,76 @@ public: | ||||||
|                                              std::chrono::nanoseconds timeout, |                                              std::chrono::nanoseconds timeout, | ||||||
|                                              std::shared_ptr<WakeupCallback> callback); |                                              std::shared_ptr<WakeupCallback> callback); | ||||||
| 
 | 
 | ||||||
|  | private: | ||||||
|  |     template <typename ResultFunctor> | ||||||
|  |     class AsyncWakeUpCallback : public WakeupCallback { | ||||||
|  |     public: | ||||||
|  |         explicit AsyncWakeUpCallback(ResultFunctor res_functor, std::future<void> fut) | ||||||
|  |             : functor(res_functor) { | ||||||
|  |             future = std::move(fut); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, | ||||||
|  |                     Kernel::ThreadWakeupReason reason) { | ||||||
|  |             functor(ctx); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     private: | ||||||
|  |         ResultFunctor functor; | ||||||
|  |         std::future<void> future; | ||||||
|  | 
 | ||||||
|  |         template <class Archive> | ||||||
|  |         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 <typename AsyncFunctor, typename ResultFunctor> | ||||||
|  |     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<AsyncWakeUpCallback<ResultFunctor>>( | ||||||
|  |                     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<AsyncWakeUpCallback<ResultFunctor>>( | ||||||
|  |                     result_function, std::move(std::future<void>())); | ||||||
|  |                 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 |      * 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. |      * "HLE handle protocol" section in the class documentation for more details. | ||||||
|  |  | ||||||
|  | @ -244,13 +244,15 @@ void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) { | ||||||
|     thread->ResumeFromWait(); |     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
 |     // Don't schedule a wakeup if the thread wants to wait forever
 | ||||||
|     if (nanoseconds == -1) |     if (nanoseconds == -1) | ||||||
|         return; |         return; | ||||||
|  |     size_t core = thread_safe_mode ? core_id : std::numeric_limits<std::size_t>::max(); | ||||||
| 
 | 
 | ||||||
|     thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds), |     thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds), | ||||||
|                                                thread_manager.ThreadWakeupEventType, thread_id); |                                                thread_manager.ThreadWakeupEventType, thread_id, | ||||||
|  |                                                core, thread_safe_mode); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Thread::ResumeFromWait() { | void Thread::ResumeFromWait() { | ||||||
|  |  | ||||||
|  | @ -238,8 +238,10 @@ public: | ||||||
|     /**
 |     /**
 | ||||||
|      * Schedules an event to wake up the specified thread after the specified delay |      * 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 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) |      * Sets the result after the thread awakens (from either WaitSynchronization SVC) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue