ralpha-assets/Plugins/CesiumForUnreal/Source/ThirdParty/include/CesiumAsync/Impl/QueuedScheduler.h

96 lines
3.3 KiB
C++

#pragma once
#include "ImmediateScheduler.h"
#include "cesium-async++.h"
#include <atomic>
namespace CesiumAsync {
// Begin omitting doxygen warnings for Impl namespace
//! @cond Doxygen_Suppress
namespace CesiumImpl {
class QueuedScheduler {
public:
QueuedScheduler();
~QueuedScheduler();
void schedule(async::task_run_handle t);
void dispatchQueuedContinuations();
bool dispatchZeroOrOneContinuation();
template <typename T> T dispatchUntilTaskCompletes(async::task<T>&& task) {
// Set up a continuation to unblock the blocking dispatch when this task
// completes.
//
// We use the `isDone` flag as the loop termination condition to
// avoid a race condition that can lead to a deadlock. If we used
// `unblockTask.ready()` as the termination condition instead, then it's
// possible for events to happen as follows:
//
// 1. The original `task` completes in a worker thread and the `unblockTask`
// continuation is invoked immediately in the same thread.
// 2. The unblockTask continuation calls `unblock`, which terminates the
// `wait` on the condition variable in the main thread.
// 3. The main thread resumes and the while loop in this function spins back
// around and evaluates `unblockTask.ready()`. This returns false because
// the unblockTask continuation has not actually finished running in the
// worker thread yet. The main thread starts waiting on the condition
// variable again.
// 4. The `unblockTask` continuation finally finishes, making
// `unblockTask.ready()` return true, but it's too late. The main thread is
// already waiting on the condition variable.
//
// By setting the atomic `isDone` flag before calling `unblock`, we ensure
// that the loop termination condition is satisfied before the main thread
// is awoken, avoiding the potential deadlock.
std::atomic<bool> isDone = false;
async::task<T> unblockTask = task.then(
async::inline_scheduler(),
[this, &isDone](async::task<T>&& task) {
isDone = true;
this->unblock();
return task.get();
});
while (!isDone) {
this->dispatchInternal(true);
}
return std::move(unblockTask).get();
}
template <typename T>
T dispatchUntilTaskCompletes(const async::shared_task<T>& task) {
// Set up a continuation to unblock the blocking dispatch when this task
// completes. This case is simpler than the one above because a SharedFuture
// supports multiple continuations. We can use readiness of the _original_
// task to terminate the loop while unblocking in a separate continuation
// guaranteed to run only after that termination condition is satisfied.
async::task<void> unblockTask = task.then(
async::inline_scheduler(),
[this](const async::shared_task<T>&) { this->unblock(); });
while (!task.ready()) {
this->dispatchInternal(true);
}
return task.get();
}
ImmediateScheduler<QueuedScheduler> immediate{this};
private:
bool dispatchInternal(bool blockIfNoTasks);
void unblock();
struct Impl;
std::unique_ptr<Impl> _pImpl;
};
//! @endcond
// End omitting doxygen warnings for Impl namespace
} // namespace CesiumImpl
} // namespace CesiumAsync