yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ui_thread_queue.cc
Go to the documentation of this file.
2
3#include <string>
4#include <utility>
5
6namespace yaze {
7namespace util {
8
10 // Wake any caller still waiting so it returns Cancelled instead of
11 // waiting out its timeout. Waiters hold their Call by shared_ptr, so they
12 // do not touch this object after waking.
13 Shutdown();
14}
15
17 static UiThreadQueue* instance = new UiThreadQueue();
18 // Leaked on purpose: gRPC and HTTP threads can still call into the queue
19 // while static destructors run at exit.
20 return *instance;
21}
22
24 std::lock_guard<std::mutex> lock(mu_);
25 ui_thread_id_ = std::this_thread::get_id();
26 bound_ = true;
27 shutdown_ = false;
28}
29
31 std::lock_guard<std::mutex> lock(mu_);
32 return bound_ && ui_thread_id_ == std::this_thread::get_id();
33}
34
36 std::lock_guard<std::mutex> lock(mu_);
37 if (shutdown_) {
38 return false;
39 }
40 if (bound_) {
41 return ui_thread_id_ == std::this_thread::get_id();
42 }
43 return true;
44}
45
46void UiThreadQueue::SetWaker(std::function<void()> waker) {
47 // Taking waker_mu_ waits for any Wake() in progress, so the old waker's
48 // captures (the window backend) are no longer in use when this returns.
49 std::lock_guard<std::mutex> lock(waker_mu_);
50 waker_ = std::move(waker);
51}
52
53bool UiThreadQueue::Post(std::function<void()> task) {
54 if (!task) {
55 return false;
56 }
57 return Enqueue(Entry{std::move(task), nullptr});
58}
59
61 bool was_empty = false;
62 {
63 std::lock_guard<std::mutex> lock(mu_);
64 if (shutdown_) {
65 return false;
66 }
67 was_empty = queue_.empty();
68 queue_.push_back(std::move(entry));
69 }
70 // Only the first task after a Drain() needs a wake: a non-empty queue
71 // already has a wake in flight, and the next Drain() takes every task.
72 // This keeps a burst of agent calls from flooding the SDL event queue.
73 // Wake outside mu_ so the waker cannot deadlock with Drain().
74 if (was_empty) {
75 Wake();
76 }
77 return true;
78}
79
81 std::lock_guard<std::mutex> lock(waker_mu_);
82 if (waker_) {
83 waker_();
84 }
85}
86
88 std::deque<Entry> batch;
89 {
90 std::lock_guard<std::mutex> lock(mu_);
91 batch.swap(queue_);
92 }
93 // Tasks run without mu_ held, so they may Post() follow-up work; that
94 // work lands in queue_ and waits for the next Drain().
95 for (Entry& entry : batch) {
96 entry.task();
97 }
98 return batch.size();
99}
100
102 std::lock_guard<std::mutex> lock(mu_);
103 return !queue_.empty();
104}
105
107 std::deque<Entry> dropped;
108 {
109 std::lock_guard<std::mutex> lock(mu_);
110 shutdown_ = true;
111 dropped.swap(queue_);
112 }
113 for (Entry& entry : dropped) {
114 if (!entry.call) {
115 continue;
116 }
117 {
118 std::lock_guard<std::mutex> lock(entry.call->mu);
119 if (entry.call->state == Call::State::kPending) {
120 entry.call->state = Call::State::kCancelled;
121 }
122 }
123 entry.call->cv.notify_all();
124 }
125}
126
128 std::lock_guard<std::mutex> lock(mu_);
129 return shutdown_;
130}
131
132absl::Status UiThreadQueue::RunAndWait(std::function<void()> body,
133 std::chrono::milliseconds timeout) {
134 bool run_inline = false;
135 {
136 std::lock_guard<std::mutex> lock(mu_);
137 if (shutdown_) {
138 return absl::CancelledError("UI thread queue is shut down");
139 } else if (bound_ && ui_thread_id_ == std::this_thread::get_id()) {
140 // Posting from the UI thread and waiting would deadlock.
141 run_inline = true;
142 } else if (!bound_) {
143 // No frame loop exists to drain the queue (CLI, unit tests).
144 run_inline = true;
145 }
146 }
147 if (run_inline) {
148 body();
149 return absl::OkStatus();
150 }
151
152 auto call = std::make_shared<Call>();
153 Entry entry;
154 entry.call = call;
155 entry.task = [call, body = std::move(body)]() {
156 {
157 std::lock_guard<std::mutex> lock(call->mu);
158 // An abandoned call's waiter has returned; body may reference its
159 // stack, so it must not run.
160 if (call->state != Call::State::kPending) {
161 return;
162 }
163 call->state = Call::State::kRunning;
164 }
165 body();
166 {
167 std::lock_guard<std::mutex> lock(call->mu);
168 call->state = Call::State::kDone;
169 }
170 call->cv.notify_all();
171 };
172 if (!Enqueue(std::move(entry))) {
173 return absl::CancelledError("UI thread queue is shut down");
174 }
175
176 std::unique_lock<std::mutex> lock(call->mu);
177 const bool finished = call->cv.wait_for(lock, timeout, [&call]() {
178 return call->state == Call::State::kDone ||
179 call->state == Call::State::kCancelled;
180 });
181 if (!finished) {
182 if (call->state == Call::State::kPending) {
183 call->state = Call::State::kAbandoned;
184 return absl::DeadlineExceededError(
185 "UI thread did not run the task within " +
186 std::to_string(timeout.count()) + " ms");
187 }
188 // The task already started; its captures point at this stack, so
189 // returning now would let it write to freed memory.
190 call->cv.wait(lock,
191 [&call]() { return call->state == Call::State::kDone; });
192 }
193 if (call->state == Call::State::kCancelled) {
194 return absl::CancelledError(
195 "UI thread queue shut down before the task ran");
196 }
197 return absl::OkStatus();
198}
199
201 if (state.busy || state.ui_tasks_pending) {
202 return 0;
203 }
204 if (state.window_hidden) {
206 }
207 if (state.quiet_for >= kMainLoopIdleAfter) {
208 return kMainLoopIdleWaitMs;
209 }
210 return 0;
211}
212
213} // namespace util
214} // namespace yaze
Moves work from service threads (gRPC, HTTP) onto the UI thread.
std::deque< Entry > queue_
bool Post(std::function< void()> task)
std::function< void()> waker_
static UiThreadQueue & Get()
absl::Status RunAndWait(std::function< void()> body, std::chrono::milliseconds timeout)
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