Files
klubhaus-doorbell/libraries/FastLED/src/fl/async.h
2026-02-12 00:45:31 -08:00

261 lines
7.8 KiB
C++

#pragma once
/// @file async.h
/// @brief Generic asynchronous task management for FastLED
///
/// This module provides a unified system for managing asynchronous operations
/// across FastLED, including HTTP requests, timers, and other background tasks.
///
/// The async system integrates with FastLED's engine events and can be pumped
/// during delay() calls on WASM platforms for optimal responsiveness.
///
/// @section Usage
/// @code
/// #include "fl/async.h"
///
/// // Create a custom async runner
/// class Myasync_runner : public fl::async_runner {
/// public:
/// void update() override {
/// // Process your async tasks here
/// process_timers();
/// handle_network_events();
/// }
///
/// bool has_active_tasks() const override {
/// return !mTimers.empty() || mNetworkActive;
/// }
///
/// size_t active_task_count() const override {
/// return mTimers.size() + (mNetworkActive ? 1 : 0);
/// }
/// };
///
/// void setup() {
/// Myasync_runner* runner = new Myasync_runner();
/// fl::AsyncManager::instance().register_runner(runner);
///
/// // Now your async tasks will be automatically updated during:
/// // - FastLED.show() calls (via engine events)
/// // - delay() calls on WASM platforms
/// // - Manual fl::async_run() calls
/// }
/// @endcode
#include "fl/namespace.h"
#include "fl/vector.h"
#include "fl/function.h"
#include "fl/ptr.h"
#include "fl/variant.h"
#include "fl/promise.h"
#include "fl/promise_result.h"
#include "fl/singleton.h"
#include "fl/thread_local.h"
#include "fl/task.h"
#include "fl/time.h"
namespace fl {
// Forward declarations
class AsyncManager;
/// @brief Generic asynchronous task runner interface
class async_runner {
public:
virtual ~async_runner() = default;
/// Update this async runner (called during async pumping)
virtual void update() = 0;
/// Check if this runner has active tasks
virtual bool has_active_tasks() const = 0;
/// Get number of active tasks (for debugging/monitoring)
virtual size_t active_task_count() const = 0;
};
/// @brief Async task manager (singleton)
class AsyncManager {
public:
static AsyncManager& instance();
/// Register an async runner
void register_runner(async_runner* runner);
/// Unregister an async runner
void unregister_runner(async_runner* runner);
/// Update all registered async runners
void update_all();
/// Check if there are any active async tasks
bool has_active_tasks() const;
/// Get total number of active tasks across all runners
size_t total_active_tasks() const;
private:
fl::vector<async_runner*> mRunners;
};
/// @brief Platform-specific async yield function
///
/// This function pumps all async tasks and yields control appropriately for the platform:
/// - WASM: calls async_run() then emscripten_sleep(1) to yield to browser
/// - Other platforms: calls async_run() multiple times with simple yielding
///
/// This centralizes platform-specific async behavior instead of having #ifdef in generic code.
void async_yield();
/// @brief Run all registered async tasks once
///
/// This function updates all registered async runners (fetch, timers, etc.)
/// and is automatically called during:
/// - FastLED engine events (onEndFrame)
/// - delay() calls on WASM platforms (every 1ms)
/// - Manual calls for custom async pumping
///
/// @note This replaces the old fetch_update() function with a generic approach
void async_run();
/// @brief Get the number of active async tasks across all systems
/// @return Total number of active async tasks
size_t async_active_tasks();
/// @brief Check if any async systems have active tasks
/// @return True if any async tasks are running
bool async_has_tasks();
/// @brief Synchronously wait for a promise to complete (ONLY safe in top-level contexts)
/// @tparam T The type of value the promise resolves to (automatically deduced)
/// @param promise The promise to wait for
/// @return A PromiseResult containing either the resolved value T or an Error
///
/// This function blocks until the promise is either resolved or rejected,
/// then returns a PromiseResult that can be checked with ok() for success/failure.
/// While waiting, it continuously calls async_yield() to pump async tasks and yield appropriately.
///
/// **SAFETY WARNING**: This function should ONLY be called from top-level contexts
/// like Arduino loop() function. Never call this from:
/// - Promise callbacks (.then, .catch_)
/// - Nested async operations
/// - Interrupt handlers
/// - Library initialization code
///
/// The "_top_level" suffix emphasizes this safety requirement.
///
/// **Type Deduction**: The template parameter T is automatically deduced from the
/// promise parameter, so you don't need to specify it explicitly.
///
/// @section Usage
/// @code
/// auto promise = fl::fetch_get("http://example.com");
/// auto result = fl::await_top_level(promise); // Type automatically deduced!
///
/// if (result.ok()) {
/// const Response& resp = result.value();
/// FL_WARN("Success: " << resp.text());
/// } else {
/// FL_WARN("Error: " << result.error().message);
/// }
///
/// // Or use operator bool
/// if (result) {
/// FL_WARN("Got response: " << result.value().status());
/// }
///
/// // You can still specify the type explicitly if needed:
/// auto explicit_result = fl::await_top_level<response>(promise);
/// @endcode
template<typename T>
fl::result<T> await_top_level(fl::promise<T> promise) {
// Handle invalid promises
if (!promise.valid()) {
return fl::result<T>(Error("Invalid promise"));
}
// If already completed, return immediately
if (promise.is_completed()) {
if (promise.is_resolved()) {
return fl::result<T>(promise.value());
} else {
return fl::result<T>(promise.error());
}
}
// Track recursion depth to prevent infinite loops
static fl::ThreadLocal<int> await_depth(0);
if (await_depth.access() > 10) {
return fl::result<T>(Error("await_top_level recursion limit exceeded - possible infinite loop"));
}
++await_depth.access();
// Wait for promise to complete while pumping async tasks
int pump_count = 0;
const int max_pump_iterations = 10000; // Safety limit
while (!promise.is_completed() && pump_count < max_pump_iterations) {
// Update the promise first (in case it's not managed by async system)
promise.update();
// Check if completed after update
if (promise.is_completed()) {
break;
}
// Platform-agnostic async pump and yield
async_yield();
++pump_count;
}
--await_depth.access();
// Check for timeout
if (pump_count >= max_pump_iterations) {
return fl::result<T>(Error("await_top_level timeout - promise did not complete"));
}
// Return the result
if (promise.is_resolved()) {
return fl::result<T>(promise.value());
} else {
return fl::result<T>(promise.error());
}
}
class Scheduler {
public:
static Scheduler& instance();
int add_task(task t);
void update();
// New methods for frame task handling
void update_before_frame_tasks();
void update_after_frame_tasks();
// For testing: clear all tasks
void clear_all_tasks() { mTasks.clear(); mNextTaskId = 1; }
private:
friend class fl::Singleton<Scheduler>;
Scheduler() : mTasks() {}
void warn_no_then(int task_id, const fl::string& trace_label);
void warn_no_catch(int task_id, const fl::string& trace_label, const Error& error);
// Helper method for running specific task types
void update_tasks_of_type(TaskType task_type);
fl::vector<task> mTasks;
int mNextTaskId = 1;
};
} // namespace fl