yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ui_thread_queue.h
Go to the documentation of this file.
1#ifndef YAZE_UTIL_UI_THREAD_QUEUE_H_
2#define YAZE_UTIL_UI_THREAD_QUEUE_H_
3
4#include <chrono>
5#include <condition_variable>
6#include <cstddef>
7#include <deque>
8#include <functional>
9#include <memory>
10#include <mutex>
11#include <optional>
12#include <thread>
13#include <type_traits>
14#include <utility>
15
16#include "absl/status/status.h"
17#include "absl/status/statusor.h"
18
19namespace yaze {
20namespace util {
21
45 public:
46 // Long enough for a slow frame (ROM load, large redraw), short enough
47 // that a stuck UI thread does not hang an agent call forever.
48 static constexpr std::chrono::milliseconds kDefaultTimeout{5000};
49
50 UiThreadQueue() = default;
52
53 UiThreadQueue(const UiThreadQueue&) = delete;
55
56 // Process-wide instance used by the Controller and the services.
57 static UiThreadQueue& Get();
58
59 // Marks the calling thread as the UI thread and re-arms the queue after a
60 // previous Shutdown(), so a second Controller in the same process works.
62
63 // True when the calling thread is the bound UI thread.
64 bool IsUiThread() const;
65
66 // True when RunOnUiThread() would run the task on the calling thread:
67 // either the caller is the UI thread, or no UI thread is bound (CLI and
68 // unit-test paths that never start a frame loop) and the queue is not
69 // shut down.
70 bool RunsInline() const;
71
72 // Called when a Post() makes the queue non-empty, to interrupt a blocking
73 // event wait. Later posts before the next Drain() do not call it again,
74 // because that Drain() takes them too. It runs on the posting thread, so
75 // it must be thread-safe and must not call back into this queue. Passing
76 // an empty function blocks until any wake in progress has returned, so
77 // the Controller can destroy the window backend safely right after
78 // SetWaker({}).
79 void SetWaker(std::function<void()> waker);
80
81 // Queues a task for the next Drain(). Returns false, and drops the task,
82 // after Shutdown().
83 bool Post(std::function<void()> task);
84
85 // Runs the tasks that were queued when Drain() began. Tasks posted while
86 // draining wait for the next Drain(), so a task that re-posts itself
87 // cannot starve the frame. Returns the number of tasks that ran.
88 size_t Drain();
89
90 bool HasPendingTasks() const;
91
92 // Drops queued tasks, fails waiting RunOnUiThread() callers with
93 // Cancelled, and makes later Post() and RunOnUiThread() calls fail fast
94 // until BindToCurrentThread() is called again.
95 void Shutdown();
96
97 bool IsShutdown() const;
98
110 template <typename Fn, typename R = std::invoke_result_t<Fn&>>
111 R RunOnUiThread(Fn fn, std::chrono::milliseconds timeout = kDefaultTimeout) {
112 static_assert(std::is_same_v<R, absl::Status> || IsStatusOr<R>::value,
113 "RunOnUiThread requires a callable returning absl::Status "
114 "or absl::StatusOr<T>");
115 // Shared ownership keeps the result slot alive if the waiter gives up
116 // while the queue still holds the task.
117 auto result = std::make_shared<std::optional<R>>();
118 auto shared_fn = std::make_shared<Fn>(std::move(fn));
119 absl::Status wait_status = RunAndWait(
120 [result, shared_fn]() { result->emplace((*shared_fn)()); }, timeout);
121 if (!wait_status.ok()) {
122 return R(wait_status);
123 }
124 if (!result->has_value()) {
125 return R(absl::InternalError("UI thread task produced no result"));
126 }
127 return std::move(**result);
128 }
129
130 private:
131 template <typename T>
132 struct IsStatusOr : std::false_type {};
133 template <typename T>
134 struct IsStatusOr<absl::StatusOr<T>> : std::true_type {};
135
136 // State of one RunOnUiThread() call, shared by the waiter and the task.
137 struct Call {
139 std::mutex mu;
140 std::condition_variable cv;
142 };
143
144 struct Entry {
145 std::function<void()> task;
146 // Set only for RunOnUiThread() entries, so Shutdown() can wake waiters.
147 std::shared_ptr<Call> call;
148 };
149
150 absl::Status RunAndWait(std::function<void()> body,
151 std::chrono::milliseconds timeout);
152 bool Enqueue(Entry entry);
153 void Wake();
154
155 mutable std::mutex mu_;
156 std::deque<Entry> queue_;
157 std::thread::id ui_thread_id_;
158 bool bound_ = false;
159 bool shutdown_ = false;
160
161 // Separate from mu_ so a slow waker never blocks Drain() or Post().
162 std::mutex waker_mu_;
163 std::function<void()> waker_;
164};
165
170 // Something needs continuous frames (animation, emulator, screenshot,
171 // UI test).
172 bool busy = false;
173 // UiThreadQueue has tasks waiting for Drain().
174 bool ui_tasks_pending = false;
175 bool window_hidden = false;
176 // Time since the last real window event.
177 std::chrono::steady_clock::duration quiet_for{};
178};
179
180// Quiet time before a visible window starts to idle.
181inline constexpr std::chrono::milliseconds kMainLoopIdleAfter{1500};
182// Event wait used while a visible window idles.
183inline constexpr int kMainLoopIdleWaitMs = 100;
184// Event wait used while the window is hidden (service mode).
185inline constexpr int kMainLoopHiddenWaitMs = 250;
186
194int MainLoopEventWaitMs(const MainLoopIdleState& state);
195
196} // namespace util
197} // namespace yaze
198
199#endif // YAZE_UTIL_UI_THREAD_QUEUE_H_
Moves work from service threads (gRPC, HTTP) onto the UI thread.
R RunOnUiThread(Fn fn, std::chrono::milliseconds timeout=kDefaultTimeout)
Runs fn on the UI thread and returns its result.
std::deque< Entry > queue_
UiThreadQueue(const UiThreadQueue &)=delete
bool Post(std::function< void()> task)
UiThreadQueue & operator=(const UiThreadQueue &)=delete
std::function< void()> waker_
static UiThreadQueue & Get()
absl::Status RunAndWait(std::function< void()> body, std::chrono::milliseconds timeout)
static constexpr std::chrono::milliseconds kDefaultTimeout
void SetWaker(std::function< void()> waker)
constexpr int kMainLoopHiddenWaitMs
int MainLoopEventWaitMs(const MainLoopIdleState &state)
Returns how long the frame loop may block waiting for an event.
constexpr std::chrono::milliseconds kMainLoopIdleAfter
constexpr int kMainLoopIdleWaitMs
Inputs for the frame loop's idle decision.
std::chrono::steady_clock::duration quiet_for