Add HLERequestContext::RunAsync (#7027)
This commit is contained in:
parent
38a0a85777
commit
9ec4954380
|
@ -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,18 +61,29 @@ void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_
|
||||||
timer = timers.at(core_id).get();
|
timer = timers.at(core_id).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
s64 timeout = timer->GetTicks() + cycles_into_future;
|
if (thread_safe_mode) {
|
||||||
if (current_timer == timer) {
|
// Events scheduled in thread safe mode come after blocking operations with
|
||||||
// If this event needs to be scheduled before the next advance(), force one early
|
// unpredictable timings in the host machine, so there is no need to be cycle accurate.
|
||||||
if (!timer->is_timer_sane)
|
// To prevent the event from scheduling before the next advance(), we set a minimum time
|
||||||
timer->ForceExceptionCheck(cycles_into_future);
|
// 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->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<s64>(timer->GetTicks() + cycles_into_future), 0,
|
timer->ts_queue.Push(Event{static_cast<s64>(timer->GetTicks() + cycles_into_future), 0,
|
||||||
user_data, event_type});
|
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<s64>(timer->GetTicks() + cycles_into_future), 0,
|
||||||
|
user_data, event_type});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Reference in New Issue