Anklang-0.3.0.dev712+gdc4e642f anklang-0.3.0.dev712+gdc4e642f
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
looptasks.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include <cstdio>
3#include <atomic>
4#include <chrono>
5#include <coroutine>
6#include <exception>
7#include <functional>
8#include <memory>
9#include <string>
10#include <thread>
11#include <type_traits>
12#include <variant>
13#include <vector>
14#include <ase/loop.hh>
15#include "../testing.hh"
16
17namespace { // Anon
18
19static void
20loop_current_test()
21{
22 using namespace Ase;
23 auto loop1 = Loop::current();
24 auto loop2 = Loop::current();
25 TCHECK (loop1 == loop2, "Loop::current() should return the same loop instance");
26 auto loop3 = Loop::current();
27 TCHECK (loop1 == loop3, "Loop::current() should always return the same instance");
28}
29TEST_ADD (loop_current_test);
30
31static void
32loop_current_multithread_test()
33{
34 using namespace Ase;
35 auto main_thread_loop = Loop::current();
36 std::atomic<bool> thread1_ready{false}, thread2_ready{false};
37 Ase::LoopP thread1_loop, thread2_loop;
38
39 std::thread t1 ([&]() {
40 thread1_loop = Loop::current();
41 TCHECK (thread1_loop != nullptr, "Thread 1 should get a valid loop");
42 auto same = Loop::current();
43 TCHECK (thread1_loop == same, "Thread 1 should always get the same loop");
44 thread1_ready = true;
45 });
46
47 std::thread t2 ([&]() {
48 thread2_loop = Loop::current();
49 TCHECK (thread2_loop != nullptr, "Thread 2 should get a valid loop");
50 auto same = Loop::current();
51 TCHECK (thread2_loop == same, "Thread 2 should always get the same loop");
52 thread2_ready = true;
53 });
54
55 t1.join();
56 t2.join();
57
58 TCHECK (thread1_ready && thread2_ready, "Both threads should complete");
59 TCHECK (thread1_loop && thread2_loop, "Both thread loops should exist");
60 TCHECK (thread1_loop != main_thread_loop, "Thread 1 loop differs from main");
61 TCHECK (thread2_loop != main_thread_loop, "Thread 2 loop differs from main");
62 TCHECK (thread1_loop != thread2_loop, "Each thread gets its own loop");
63}
64TEST_ADD (loop_current_multithread_test);
65
66static void
67loop_add_timer_test()
68{
69 using namespace Ase;
70 auto interval = std::chrono::milliseconds (1);
71 auto priority = Ase::LoopPriority::NORMAL;
72 auto loop = Loop::current();
73
74 int counter = 0;
75 loop->add ([&counter] ()
76 {
77 counter++;
78 return false;
79 }, interval, priority);
80
81 for (int i = 0; i < 4; i++) {
82 usleep (1000);
83 while (loop->pending())
84 loop->iterate (true);
85 }
86
87 TCHECK (counter == 1, "add_timer() callback should be called once");
88}
89TEST_ADD (loop_add_timer_test);
90
91// === CoTask tests ===
92
93static void
94cotask_void_test()
95{
96 auto loop = Ase::Loop::current();
97 auto completedp = std::make_shared<std::atomic<bool>> (false);
98
99 auto coroutine = [=]() -> Ase::CoTaskVoid
100 {
101 auto promise = loop->make_promise ([=] (std::function<void()> resolve)
102 {
103 // resolve asynchrnously via event loop
104 loop->add ([=]() { resolve(); return false; });
105 });
106 co_await promise;
107 *completedp = true;
108 co_return;
109 };
110 loop->add (coroutine);
111
112 for (int i = 0; i < 500 && !*completedp; i++) {
113 usleep (1000);
114 while (loop->pending())
115 loop->iterate (true);
116 }
117 TCHECK (*completedp, "CoTask<void> should complete");
118}
119TEST_ADD (cotask_void_test);
120
121static void
122cotask_int64_result_test()
123{
124 auto loop = Ase::Loop::current();
125 auto result = std::make_shared<std::atomic<int64_t>> (0);
126
127 auto coroutine = [=]() -> Ase::CoTaskVoid
128 {
129 auto promise = loop->make_promise<int64_t> ([=] (std::function<void(int64_t)> resolve) {
130 loop->add ([=]() { resolve (42); return false; });
131 });
132 *result = co_await promise;
133 co_return;
134 };
135
136 loop->add (coroutine);
137
138 for (int i = 0; i < 500 && *result == 0; i++) {
139 usleep (1000);
140 while (loop->pending())
141 loop->iterate (true);
142 }
143 TCHECK (*result == 42, "CoTask<int64_t> should return 42");
144}
145TEST_ADD (cotask_int64_result_test);
146
147static void
148cotask_string_result_test()
149{
150 auto loop = Ase::Loop::current();
151 auto result = std::make_shared<std::string>();
152
153 auto coroutine = [=]() -> Ase::CoTaskVoid {
154 auto promise = loop->make_promise<std::string> ([=] (std::function<void(std::string)> resolve) {
155 loop->add ([=]() { resolve (std::string {"hello"}); return false; });
156 });
157 *result = co_await promise;
158 co_return;
159 };
160
161 loop->add (coroutine);
162
163 for (int i = 0; i < 500 && result->empty(); i++) {
164 usleep (1000);
165 while (loop->pending())
166 loop->iterate (true);
167 }
168 TCHECK (*result == "hello", "CoTask<std::string> should return 'hello'");
169}
170TEST_ADD (cotask_string_result_test);
171
172static void
173promise_void_test()
174{
175 auto loop = Ase::Loop::current();
176 auto completed = std::make_shared<std::atomic<bool>> (false);
177
178 auto promise = loop->make_promise ([=] (std::function<void()> resolve)
179 {
180 loop->add ([=]() { resolve(); return false; });
181 });
182
183 auto coroutine = [=]() -> Ase::CoTaskVoid
184 {
185 co_await promise;
186 *completed = true;
187 co_return;
188 };
189
190 loop->add (coroutine);
191
192 for (int i = 0; i < 500 && !*completed; i++) {
193 usleep (1000);
194 while (loop->pending())
195 loop->iterate (true);
196 }
197 TCHECK (*completed, "Promise<void> should resolve");
198}
199TEST_ADD (promise_void_test);
200
201static void
202promise_int64_test()
203{
204 auto loop = Ase::Loop::current();
205 auto result = std::make_shared<std::atomic<int64_t>> (0);
206
207 auto promise = loop->make_promise<int64_t> ([=] (std::function<void(int64_t)> resolve)
208 {
209 loop->add ([=]() { resolve (123); return false; });
210 });
211
212 auto coroutine = [=]() -> Ase::CoTaskVoid
213 {
214 *result = co_await promise;
215 co_return;
216 };
217
218 loop->add (coroutine);
219
220 for (int i = 0; i < 500 && *result == 0; i++) {
221 usleep (1000);
222 while (loop->pending())
223 loop->iterate (true);
224 }
225 TCHECK (*result == 123, "Promise<int64_t> should resolve with 123");
226}
227TEST_ADD (promise_int64_test);
228
229static void
230promise_string_test()
231{
232 auto loop = Ase::Loop::current();
233 auto result = std::make_shared<std::string>();
234
235 auto promise = loop->make_promise<std::string> ([=] (std::function<void(std::string)> resolve)
236 {
237 loop->add ([=]() { resolve (std::string {"world"}); return false; });
238 });
239
240 auto coroutine = [=]() -> Ase::CoTaskVoid
241 {
242 *result = co_await promise;
243 co_return;
244 };
245
246 loop->add (coroutine);
247
248 for (int i = 0; i < 500 && result->empty(); i++) {
249 usleep (1000);
250 while (loop->pending())
251 loop->iterate (true);
252 }
253 TCHECK (*result == "world", "Promise<std::string> should resolve with 'world'");
254}
255TEST_ADD (promise_string_test);
256
257static void
258promise_multi_waiter_test()
259{
260 auto loop = Ase::Loop::current();
261 auto promise = loop->make_promise<int64_t> ([] (std::function<void(int64_t)>) {});
262 auto waiter1_done = std::make_shared<std::atomic<int>> (0);
263 auto waiter2_done = std::make_shared<std::atomic<int>> (0);
264
265 auto cotask1 = [=]() -> Ase::CoTaskVoid
266 {
267 *waiter1_done = co_await promise;
268 co_return;
269 };
270
271 auto cotask2 = [=]() -> Ase::CoTaskVoid
272 {
273 *waiter2_done = co_await promise;
274 co_return;
275 };
276
277 loop->add (cotask1);
278 loop->add (cotask2);
279 loop->add ([=]() { promise->resolve (99); return false; });
280
281 for (int i = 0; i < 500 && (*waiter1_done == 0 || *waiter2_done == 0); i++) {
282 usleep (1000);
283 while (loop->pending())
284 loop->iterate (true);
285 }
286 TCHECK (*waiter1_done == 99, "First waiter should get 99");
287 TCHECK (*waiter2_done == 99, "Second waiter should get 99");
288}
289TEST_ADD (promise_multi_waiter_test);
290
291static void
292promise_exception_test()
293{
294 auto loop = Ase::Loop::current();
295 auto caught_exception = std::make_shared<std::atomic<bool>> (false);
296
297 auto promise = loop->make_promise<int64_t> ([] (std::function<void(int64_t)>) {});
298 loop->add ([=]()
299 {
300 promise->reject (std::make_exception_ptr (std::runtime_error ("test error")));
301 return false;
302 });
303
304 auto coroutine = [=]() -> Ase::CoTaskVoid
305 {
306 try {
307 co_await promise;
308 } catch (const std::runtime_error &) {
309 *caught_exception = true;
310 }
311 co_return;
312 };
313
314 loop->add (coroutine);
315
316 for (int i = 0; i < 500 && !*caught_exception; i++) {
317 usleep (1000);
318 while (loop->pending())
319 loop->iterate (true);
320 }
321 TCHECK (*caught_exception, "Promise rejection should propagate exception");
322}
323TEST_ADD (promise_exception_test);
324
326inner_int64_task (Ase::LoopP loop, int64_t value)
327{
328 auto promise = loop->make_promise<int64_t> ([loop, value] (std::function<void(int64_t)> resolve)
329 {
330 loop->add ([resolve, value]() { resolve (value * 2); return false; });
331 });
332 co_return co_await promise;
333}
334
335static void
336nested_cotask_test()
337{
338 auto loop = Ase::Loop::current();
339 auto result = std::make_shared<std::atomic<int64_t>> (0);
340
341 auto coroutine = [=]() -> Ase::CoTaskVoid
342 {
343 auto v1 = co_await inner_int64_task (loop, 10);
344 TCHECK (v1 == 20, "Nested CoTask should chain results: 10 -> 20");
345 auto v2 = co_await inner_int64_task (loop, v1);
346 *result = v2;
347 co_return;
348 };
349
350 loop->add (coroutine);
351
352 for (int i = 0; i < 500 && *result == 0; i++) {
353 usleep (1000);
354 while (loop->pending())
355 loop->iterate (true);
356 }
357 TCHECK (*result == 40, "Nested CoTask should chain results: 10 -> 20 -> 40");
358}
359TEST_ADD (nested_cotask_test);
360
361static void
362promise_already_resolved_test()
363{
364 auto loop = Ase::Loop::current();
365 auto result = std::make_shared<std::atomic<int64_t>> (0);
366
367 auto promise = loop->make_promise<int64_t> ([=] (std::function<void(int64_t)> resolve)
368 {
369 resolve (77);
370 });
371
372 auto coroutine = [=]() -> Ase::CoTaskVoid
373 {
374 *result = co_await promise;
375 co_return;
376 };
377
378 loop->add (coroutine);
379
380 for (int i = 0; i < 500 && *result == 0; i++) {
381 usleep (1000);
382 while (loop->pending())
383 loop->iterate (true);
384 }
385 TCHECK (*result == 77, "Already-resolved promise should return value immediately");
386}
387TEST_ADD (promise_already_resolved_test);
388
389static void
390delay_promise_test()
391{
392 auto loop = Ase::Loop::current();
393 auto elapsed_ms = std::make_shared<std::atomic<int64_t>> (-1);
394
395 auto coroutine = [=]() -> Ase::CoTaskVoid
396 {
397 *elapsed_ms = co_await loop->delay (std::chrono::milliseconds (50));
398 co_return;
399 };
400
401 loop->add (coroutine);
402
403 for (int i = 0; i < 500 && -1 == *elapsed_ms; i++) {
404 usleep (1000);
405 while (loop->pending())
406 loop->iterate (true);
407 }
408 TCHECK (*elapsed_ms >= 0, "delay promise should resolve");
409 TCHECK (*elapsed_ms >= 50 - 1, "delay should wait at least 50ms");
410 TCHECK (*elapsed_ms < 1000, "delay should last less than a second");
411}
412TEST_ADD (delay_promise_test);
413
414} // Anon
static LoopP current()
Return the thread-local singleton loop, created on first call.
Definition loop.cc:306
T join(T... args)
T make_exception_ptr(T... args)
The Anklang C++ API namespace.
Definition api.hh:9
@ NORMAL
Normal importantance, GUI event processing, RPC.
typedef int64_t
Like CoTask<Result> without return type.
Definition cotask.hh:146
General purpose coroutine task.
Definition cotask.hh:92
#define TEST_ADD(fun)
Register a function to run as part of the unit test suite.
Definition testing.hh:31
#define TCHECK(cond,...)
Verbose assertion, calls TPASS() on success./*#end#*‍/.
Definition testing.hh:19