SW Task Event Loop Framework v1.0.0
High-performance C++ asynchronous event loop framework with timer management and promise-based programming
Loading...
Searching...
No Matches
SLLooper.tpp
Go to the documentation of this file.
1/**
2 * @file SLLooper.tpp
3 * @brief Template implementation for SLLooper - async task, timer, and promise management
4 * @author Tran Anh Tai
5 * @date 9/2025
6 * @version 1.0.0
7 */
8
9 #pragma once
10 #include "SLLooper.h"
11 #include "EventQueue.h"
12 #include "Promise.h"
13 #include "CpuTaskExecutor.h"
14
15 namespace swt {
16 // ========== DEBUG MACROS FOR CPU-BOUND DETECTION ==========
17
18 /**
19 * @def CPU_BOUND_DETECT_THRESHOLD_MS
20 * @brief Threshold (milliseconds) for warning about CPU-bound tasks in debug mode
21 *
22 * Tasks that execute longer than this threshold will generate warnings
23 * to help identify operations that should be moved to \ref swt::CpuTaskExecutor "CpuTaskExecutor".
24 * Only active in DEBUG builds for performance reasons.
25 */
26 #ifdef DEBUG
27 #define CPU_BOUND_DETECT_THRESHOLD_MS 3000
28
29 /**
30 * @def CPU_BOUND_DETECT_BEGIN
31 * @brief Start timing measurement for CPU-bound task detection
32 *
33 * Records the start time for execution duration measurement.
34 * No-op in release builds for zero runtime cost.
35 */
36 #define CPU_BOUND_DETECT_BEGIN auto __cpu_task_start = std::chrono::steady_clock::now();
37
38 /**
39 * @def CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname)
40 * @brief End timing measurement and warn if threshold exceeded
41 * @param file Source file name for diagnostic output
42 * @param line Source line number for diagnostic output
43 * @param funcname Function name for diagnostic output
44 *
45 * Calculates execution duration and prints warning if it exceeds
46 * CPU_BOUND_DETECT_THRESHOLD_MS. Includes source location information
47 * for easy identification of problematic code.
48 */
49 #define CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname) \
50 auto __cpu_task_end = std::chrono::steady_clock::now(); \
51 auto __cpu_task_dur = std::chrono::duration_cast<std::chrono::milliseconds>(__cpu_task_end - __cpu_task_start); \
52 if (__cpu_task_dur > std::chrono::milliseconds(CPU_BOUND_DETECT_THRESHOLD_MS)) { \
53 std::cerr << "[Warning] CPU-bound task detected: " << __cpu_task_dur.count() << " ms at " \
54 << (file ? file : "unknown") << ":" << line << " (" << (funcname ? funcname : "unknown") << ")\n"; \
55 }
56 #else
57 #define CPU_BOUND_DETECT_BEGIN
58 #define CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname)
59 #endif
60
61 // ========== INTERNAL DEBUG API IMPLEMENTATIONS ==========
62
63 /**
64 * @brief Internal post function with debug info for CPU-bound detection
65 * @tparam F Function type (auto-deduced)
66 * @tparam Args Variadic argument types (auto-deduced)
67 * @param func Function to execute asynchronously
68 * @param args Arguments to forward to the function
69 * @param file Source file name (for debug output)
70 * @param line Source line number (for debug output)
71 * @param funcname Function name (for debug output)
72 * @return std::future<ReturnType> Future containing the function result
73 *
74 * Internal implementation that wraps the function with CPU-bound detection
75 * logic in debug builds. Uses std::apply for perfect argument forwarding
76 * and handles both void and value-returning functions with constexpr if.
77 *
78 * Key implementation details:
79 * - **Argument capture**: Uses std::make_tuple with perfect forwarding
80 * - **Lazy evaluation**: Arguments evaluated in execution thread context
81 * - **Debug integration**: Timing measurement with source location tracking
82 * - **Type safety**: constexpr if for void vs value-returning functions
83 * - **Zero cost**: Complete no-op in release builds
84 *
85 * @note For internal use by SLLooper::post() macro expansion
86 * @note Debug overhead only in DEBUG builds
87 * @note Uses std::apply for tuple argument unpacking
88 * @see \ref swt::SLLooper "SLLooper"
89 */
90 template<typename F, typename... Args>
91 auto SLLooper::post_internal(F&& func, Args&&... args, const char* file, int line, const char* funcname)
92 -> std::future<decltype(func(args...))>
93 {
94 using ReturnType = decltype(func(args...));
95 return mEventQueue->enqueueFunction(
96 [func = std::forward<F>(func), args_tuple = std::make_tuple(std::forward<Args>(args)...), file, line, funcname]() mutable {
97 CPU_BOUND_DETECT_BEGIN
98 if constexpr (std::is_void_v<ReturnType>) {
99 // Handle void-returning function
100 std::apply(func, args_tuple);
101 CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname)
102 } else {
103 // Handle value-returning function
104 auto result = std::apply(func, args_tuple);
105 CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname)
106 return result;
107 }
108 }
109 );
110 }
111
112 /**
113 * @brief Internal postDelayed function with debug info for CPU-bound detection
114 * @tparam F Function type (auto-deduced)
115 * @tparam Args Variadic argument types (auto-deduced)
116 * @param delayMs Delay in milliseconds before execution
117 * @param func Function to execute asynchronously
118 * @param args Arguments to forward to the function
119 * @param file Source file name (for debug output)
120 * @param line Source line number (for debug output)
121 * @param funcname Function name (for debug output)
122 * @return std::future<ReturnType> Future containing the function result
123 *
124 * Internal implementation for delayed execution with CPU-bound detection.
125 * Identical to post_internal but uses enqueueFunctionDelayed for timing.
126 *
127 * @note For internal use by SLLooper::postDelayed() macro expansion
128 * @note Same debug and performance characteristics as post_internal
129 * @see \ref swt::SLLooper "SLLooper"
130 */
131 template<typename F, typename... Args>
132 auto SLLooper::postDelayed_internal(int64_t delayMs, F&& func, Args&&... args, const char* file, int line, const char* funcname)
133 -> std::future<decltype(func(args...))>
134 {
135 using ReturnType = decltype(func(args...));
136 return mEventQueue->enqueueFunctionDelayed(
137 delayMs,
138 [func = std::forward<F>(func), args_tuple = std::make_tuple(std::forward<Args>(args)...), file, line, funcname]() mutable {
139 CPU_BOUND_DETECT_BEGIN
140 if constexpr (std::is_void_v<ReturnType>) {
141 // Handle void-returning function
142 std::apply(func, args_tuple);
143 CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname)
144 } else {
145 // Handle value-returning function
146 auto result = std::apply(func, args_tuple);
147 CPU_BOUND_DETECT_END_WITH_INFO(file, line, funcname)
148 return result;
149 }
150 }
151 );
152 }
153
154 // ========== PUBLIC ASYNC API IMPLEMENTATIONS ==========
155
156 /**
157 * @brief Post function for immediate asynchronous execution
158 * @tparam F Function type (auto-deduced)
159 * @tparam Args Variadic argument types (auto-deduced)
160 * @param func Callable object to execute asynchronously
161 * @param args Arguments to forward to the function
162 * @return std::future<ReturnType> Future containing the function result
163 *
164 * Public API for immediate function posting. Uses std::bind for argument
165 * binding, which is simpler than tuple-based approach but may have slightly
166 * different semantics for perfect forwarding.
167 *
168 * Implementation choice rationale:
169 * - **std::bind**: Simpler implementation, standard argument binding
170 * - **Perfect forwarding**: Preserves argument value categories
171 * - **Direct delegation**: Leverages EventQueue template implementation
172 * - **No debug overhead**: Pure release mode implementation
173 *
174 * @code{.cpp}
175 * // Lambda with capture
176 * auto future1 = looper->post([x = 42](int y) { return x + y; }, 8);
177 *
178 * // Free function with arguments
179 * auto future2 = looper->post(std::sin, 3.14159);
180 *
181 * // Member function call
182 * MyClass obj;
183 * auto future3 = looper->post(&MyClass::method, &obj, arg1, arg2);
184 * @endcode
185 *
186 * @note Thread-safe operation
187 * @note Arguments bound immediately, evaluated later
188 * @note Direct EventQueue delegation for optimal performance
189 * @see \ref swt::SLLooper "SLLooper", \ref swt::EventQueue "EventQueue"
190 */
191 template<typename F, typename... Args>
192 auto SLLooper::post(F&& func, Args&&... args) -> std::future<decltype(func(args...))> {
193 return mEventQueue->enqueueFunction(
194 std::bind(std::forward<F>(func), std::forward<Args>(args)...)
195 );
196 }
197
198 /**
199 * @brief Post function for delayed asynchronous execution
200 * @tparam F Function type (auto-deduced)
201 * @tparam Args Variadic argument types (auto-deduced)
202 * @param delayMs Delay in milliseconds before execution
203 * @param func Callable object to execute asynchronously
204 * @param args Arguments to forward to the function
205 * @return std::future<ReturnType> Future containing the function result
206 *
207 * Public API for delayed function posting. Uses std::bind for argument
208 * binding with the same characteristics as immediate post().
209 *
210 * @code{.cpp}
211 * // Execute lambda after 1 second
212 * auto future = looper->postDelayed(1000, []() {
213 * return "Hello after delay!";
214 * });
215 *
216 * // Delayed function with arguments
217 * auto future2 = looper->postDelayed(500, calculateSum, a, b, c);
218 * @endcode
219 *
220 * @note Delay measured from posting time, not execution time
221 * @note Arguments bound immediately for consistent behavior
222 * @see \ref swt::SLLooper "SLLooper", \ref swt::EventQueue "EventQueue"
223 */
224 template<typename F, typename... Args>
225 auto SLLooper::postDelayed(int64_t delayMs, F&& func, Args&&... args) -> std::future<decltype(func(args...))> {
226 return mEventQueue->enqueueFunctionDelayed(
227 delayMs,
228 std::bind(std::forward<F>(func), std::forward<Args>(args)...)
229 );
230 }
231
232 // ========== PROMISE API IMPLEMENTATIONS ==========
233
234 /**
235 * @brief Create a new promise object for manual result setting
236 * @tparam T Value type for the promise
237 * @return swt::Promise<T> New promise object
238 *
239 * Creates a new promise object that can be resolved manually from any thread.
240 * The promise callbacks will execute in this SLLooper's thread context when
241 * the promise is resolved or rejected.
242 *
243 * Implementation note: Currently creates a simple Promise<T> object rather
244 * than using EventQueue::enqueuePromise(). This provides more direct control
245 * over promise lifecycle and callback execution context.
246 *
247 * @code{.cpp}
248 * auto promise = looper->createPromise<int>();
249 *
250 * // Set up continuation
251 * promise.then(looper, [](int result) {
252 * std::cout << "Promise resolved with: " << result << std::endl;
253 * });
254 *
255 * // Resolve from another thread
256 * std::thread([promise]() mutable {
257 * std::this_thread::sleep_for(1s);
258 * promise.set_value(42);
259 * }).detach();
260 * @endcode
261 *
262 * @note Promise can be resolved from any thread
263 * @note Callbacks execute in this looper's thread context
264 * @note Each promise can only be resolved once
265 * @see \ref swt::Promise "Promise"
266 */
267 template<typename T>
268 swt::Promise<T> SLLooper::createPromise() {
269 // Direct Promise creation - callbacks will use this looper's context
270 // when promise.then(shared_from_this(), callback) is called
271 return swt::Promise<T>{};
272 }
273
274 // ========== CPU-BOUND TASK API IMPLEMENTATIONS ==========
275
276 /**
277 * @brief Execute CPU-intensive task asynchronously
278 * @tparam Func Function type (auto-deduced)
279 * @param func CPU-intensive function to execute
280 * @return swt::Promise<ReturnType> Promise for result retrieval and chaining
281 *
282 * Delegates CPU-bound task execution to \ref swt::CpuTaskExecutor "CpuTaskExecutor", which handles
283 * separate thread execution and result delivery back to this event loop.
284 * This ensures the main event loop remains responsive during CPU-intensive
285 * operations.
286 *
287 * Key benefits:
288 * - **Non-blocking**: Main event loop continues processing other tasks
289 * - **Thread isolation**: CPU task executes in dedicated thread
290 * - **Result integration**: Results delivered via Promise in event loop context
291 * - **Exception safety**: All exceptions captured and propagated properly
292 *
293 * @code{.cpp}
294 * auto promise = looper->postWork([]() {
295 * // CPU-intensive computation runs in separate thread
296 * return fibonacci(45);
297 * });
298 *
299 * promise.then(shared_from_this(), [](long result) {
300 * // This callback runs in main event loop thread
301 * std::cout << "Computation result: " << result << std::endl;
302 * }).catch_error(shared_from_this(), [](std::exception_ptr ex) {
303 * std::cerr << "Computation failed!" << std::endl;
304 * });
305 * @endcode
306 *
307 * @note Uses shared_from_this() to ensure SLLooper lifetime during async operation
308 * @note Function executes in separate thread - ensure thread safety
309 * @note Preferred over regular post() for CPU-intensive operations
310 * @see \ref swt::CpuTaskExecutor "CpuTaskExecutor", \ref swt::Promise "Promise"
311 */
312 template<typename Func>
313 auto SLLooper::postWork(Func&& func) -> swt::Promise<decltype(func())> {
314 return swt::CpuTaskExecutor::executeAsync(shared_from_this(), std::forward<Func>(func));
315 }
316
317 /**
318 * @brief Execute CPU-intensive task with timeout protection
319 * @tparam Func Function type (auto-deduced)
320 * @param func CPU-intensive function to execute
321 * @param timeout Maximum execution time
322 * @return swt::Promise<ReturnType> Promise for result retrieval
323 *
324 * Delegates CPU-bound task execution with timeout protection to \ref swt::CpuTaskExecutor "CpuTaskExecutor".
325 * If the task doesn't complete within the timeout, the promise is rejected
326 * with a CpuTaskTimeoutException.
327 *
328 * @code{.cpp}
329 * auto promise = looper->postWork([]() {
330 * return longRunningComputation();
331 * }, 5000ms); // 5 second timeout
332 *
333 * promise.then(shared_from_this(), [](auto result) {
334 * std::cout << "Completed in time: " << result << std::endl;
335 * }).catch_error(shared_from_this(), [](std::exception_ptr ex) {
336 * try {
337 * std::rethrow_exception(ex);
338 * } catch (const swt::CpuTaskTimeoutException& timeout) {
339 * std::cerr << "Task timed out!" << std::endl;
340 * }
341 * });
342 * @endcode
343 *
344 * @note Timeout is enforced by CpuTaskExecutor's timeout mechanism
345 * @note Timed-out tasks may continue running but results are discarded
346 * @see \ref swt::CpuTaskExecutor "CpuTaskExecutor", \ref swt::Promise "Promise"
347 */
348 template<typename Func>
349 auto SLLooper::postWork(Func&& func, std::chrono::milliseconds timeout)
350 -> swt::Promise<decltype(func())> {
351 return swt::CpuTaskExecutor::executeAsync(shared_from_this(), std::forward<Func>(func), timeout);
352 }
353
354 // ========== TIMER API IMPLEMENTATIONS (Chrono Support) ==========
355
356 /**
357 * @brief Add one-shot timer with chrono duration
358 * @tparam Rep Duration representation type (e.g., int, long)
359 * @tparam Period Duration period type (e.g., std::milli, std::micro)
360 * @param callback Function to call when timer expires
361 * @param delay Chrono duration (e.g., 500ms, 1s, 100us)
362 * @return Timer RAII object for timer management
363 *
364 * Template wrapper that converts chrono durations to milliseconds for
365 * internal timer management. Supports any chrono duration type and
366 * automatically converts to the internal millisecond representation.
367 *
368 * Conversion details:
369 * - **Duration casting**: Uses std::chrono::duration_cast for precision
370 * - **Millisecond resolution**: Internal timer system uses millisecond granularity
371 * - **Truncation behavior**: Sub-millisecond durations truncated to 0ms
372 * - **Overflow protection**: Large durations may overflow - consider reasonable limits
373 *
374 * @code{.cpp}
375 * using namespace std::chrono_literals;
376 *
377 * // Various duration types supported
378 * auto timer1 = looper->addTimer(callback, 500ms); // 500 milliseconds
379 * auto timer2 = looper->addTimer(callback, 1s); // 1 second
380 * auto timer3 = looper->addTimer(callback, 2min); // 2 minutes
381 * auto timer4 = looper->addTimer(callback, 1500us); // 1.5ms (truncated to 1ms)
382 * @endcode
383 *
384 * @note Delegates to millisecond-based addTimer() after conversion
385 * @note Sub-millisecond durations may be truncated
386 * @note Timer precision limited by underlying system timer resolution
387 * @see \ref swt::SLLooper "SLLooper"
388 */
389 template<typename Rep, typename Period>
390 Timer SLLooper::addTimer(std::function<void()> callback,
391 const std::chrono::duration<Rep, Period>& delay) {
392 auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(delay).count();
393 return addTimer(std::move(callback), static_cast<uint64_t>(delay_ms));
394 }
395
396 /**
397 * @brief Add periodic timer with chrono interval
398 * @tparam Rep Duration representation type
399 * @tparam Period Duration period type
400 * @param callback Function to call on each timer expiration
401 * @param interval Chrono interval between expirations
402 * @return Timer RAII object for timer management
403 *
404 * Template wrapper for periodic timers with chrono duration support.
405 * Creates a repeating timer that fires at the specified interval until
406 * cancelled or the Timer object is destroyed.
407 *
408 * @code{.cpp}
409 * using namespace std::chrono_literals;
410 *
411 * // Heartbeat every 30 seconds
412 * auto heartbeat = looper->addPeriodicTimer([]() {
413 * sendHeartbeat();
414 * }, 30s);
415 *
416 * // Status check every 100 milliseconds
417 * auto statusCheck = looper->addPeriodicTimer([]() {
418 * checkSystemStatus();
419 * }, 100ms);
420 * @endcode
421 *
422 * @note Delegates to millisecond-based addPeriodicTimer() after conversion
423 * @note Same conversion characteristics as one-shot timer version
424 * @note Timer continues until cancelled or Timer object destroyed
425 * @see \ref swt::SLLooper "SLLooper"
426 */
427 template<typename Rep, typename Period>
428 Timer SLLooper::addPeriodicTimer(std::function<void()> callback,
429 const std::chrono::duration<Rep, Period>& interval) {
430 auto interval_ms = std::chrono::duration_cast<std::chrono::milliseconds>(interval).count();
431 return addPeriodicTimer(std::move(callback), static_cast<uint64_t>(interval_ms));
432 }
433
434 // ========== CONVENIENCE METHOD IMPLEMENTATIONS ==========
435
436 /**
437 * @brief Post function with timeout, returns Timer for cancellation
438 * @tparam Function Function type (auto-deduced)
439 * @param func Function to execute after timeout
440 * @param timeout_ms Timeout in milliseconds
441 * @return Timer object for cancellation control
442 *
443 * Convenience method that combines delayed execution with timer functionality.
444 * Only enabled for void-returning functions using SFINAE template constraints.
445 * Provides a Timer object for cancellation control, unlike postDelayed which
446 * returns a future.
447 *
448 * SFINAE constraint rationale:
449 * - **Void-only**: Timer callbacks don't return values, so function must be void
450 * - **Type safety**: Prevents accidental use with value-returning functions
451 * - **API consistency**: Maintains timer callback signature expectations
452 *
453 * Use cases:
454 * - **Timeout operations**: Cancel pending operations after delay
455 * - **Periodic tasks**: Combined with periodic timer for complex patterns
456 * - **UI timeouts**: Auto-hide notifications, tooltips, etc.
457 * - **Network timeouts**: Cleanup connections after inactivity
458 *
459 * @code{.cpp}
460 * // Auto-hide notification after 5 seconds
461 * Timer hideTimer = looper->postWithTimeout([]() {
462 * hideNotification();
463 * }, 5000);
464 *
465 * // User interaction cancels the auto-hide
466 * if (userInteracted) {
467 * hideTimer.cancel(); // Notification stays visible
468 * }
469 *
470 * // Timeout for network operation
471 * Timer networkTimeout = looper->postWithTimeout([]() {
472 * cancelNetworkOperation();
473 * showTimeoutError();
474 * }, 10000);
475 *
476 * // Success response cancels timeout
477 * onNetworkSuccess([&networkTimeout]() {
478 * networkTimeout.cancel();
479 * });
480 * @endcode
481 *
482 * @note SFINAE restricts to void-returning functions only
483 * @note Returns Timer for cancellation, not future for result
484 * @note Function captured by value for safe timer execution
485 * @warning Function must not throw - timer callbacks should be exception-safe
486 * @see \ref swt::SLLooper "SLLooper"
487 */
488 template<typename Function>
489 auto SLLooper::postWithTimeout(Function&& func, uint64_t timeout_ms)
490 -> std::enable_if_t<std::is_void_v<std::invoke_result_t<Function>>, Timer> {
491
492 return addTimer([func = std::forward<Function>(func)]() {
493 func();
494 }, timeout_ms);
495 }
496
497 } // namespace swt