277 lines
8.9 KiB
C++
277 lines
8.9 KiB
C++
|
|
|
|
#include "doctest.h"
|
|
#include "fl/task.h"
|
|
#include "fl/async.h"
|
|
#include "fl/engine_events.h"
|
|
#include "fl/time.h"
|
|
|
|
|
|
|
|
using namespace fl;
|
|
|
|
namespace {
|
|
|
|
// Helper class to track frame events for testing
|
|
class TestFrameListener : public fl::EngineEvents::Listener {
|
|
public:
|
|
TestFrameListener() {
|
|
fl::EngineEvents::addListener(this);
|
|
}
|
|
|
|
~TestFrameListener() {
|
|
fl::EngineEvents::removeListener(this);
|
|
}
|
|
|
|
void onEndFrame() override {
|
|
frame_count++;
|
|
// Pump the scheduler to execute after_frame tasks specifically
|
|
fl::Scheduler::instance().update_after_frame_tasks();
|
|
}
|
|
|
|
int frame_count = 0;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
TEST_CASE("Task self-registration and destruction behavior [task]") {
|
|
|
|
SUBCASE("Task auto-registers when callback is set - SUCCESS") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
bool task_executed = false;
|
|
|
|
// Create task without manually adding to scheduler
|
|
// Auto-registration happens when .then() is called
|
|
{
|
|
fl::task::after_frame()
|
|
.then([&task_executed]() {
|
|
task_executed = true;
|
|
});
|
|
// Task temporary object destructs here, but it's already registered
|
|
}
|
|
|
|
// Simulate frame end event
|
|
TestFrameListener listener;
|
|
fl::EngineEvents::onEndFrame();
|
|
|
|
// Task should now execute due to auto-registration
|
|
CHECK(task_executed);
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("Fluent API pattern works with auto-registration") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
bool task_executed = false;
|
|
|
|
// This fluent pattern should now work correctly
|
|
fl::task::after_frame().then([&task_executed]() {
|
|
task_executed = true;
|
|
});
|
|
// Entire chain destructs here, but task was auto-registered
|
|
|
|
// Simulate frame end event
|
|
TestFrameListener listener;
|
|
fl::EngineEvents::onEndFrame();
|
|
|
|
// Task should execute
|
|
CHECK(task_executed);
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("Multiple auto-registering tasks work correctly") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
int tasks_executed = 0;
|
|
|
|
// Create multiple tasks without saving them - they auto-register
|
|
for (int i = 0; i < 3; i++) {
|
|
fl::task::after_frame()
|
|
.then([&tasks_executed]() {
|
|
tasks_executed++;
|
|
});
|
|
// Each task auto-registers when .then() is called
|
|
}
|
|
|
|
// Simulate frame end event
|
|
TestFrameListener listener;
|
|
fl::EngineEvents::onEndFrame();
|
|
|
|
// All 3 tasks should execute
|
|
CHECK_EQ(tasks_executed, 3);
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("Manual registration still works (backward compatibility)") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
bool task_executed = false;
|
|
|
|
// Old style should still work
|
|
auto task = fl::task::after_frame()
|
|
.then([&task_executed]() {
|
|
task_executed = true;
|
|
});
|
|
|
|
// Manual add should work (though now redundant since auto-registration already happened)
|
|
fl::Scheduler::instance().add_task(task);
|
|
|
|
// Simulate frame end event
|
|
TestFrameListener listener;
|
|
fl::EngineEvents::onEndFrame();
|
|
|
|
// Task should execute (only once, not twice)
|
|
CHECK(task_executed);
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("Task cancellation works with auto-registered tasks") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
bool task_executed = false;
|
|
|
|
// Create auto-registering task and save reference for cancellation
|
|
auto task = fl::task::after_frame()
|
|
.then([&task_executed]() {
|
|
task_executed = true;
|
|
});
|
|
// Task auto-registered when .then() was called
|
|
|
|
// Cancel the task
|
|
task.cancel();
|
|
|
|
// Simulate frame end event
|
|
TestFrameListener listener;
|
|
fl::EngineEvents::onEndFrame();
|
|
|
|
// Task should NOT execute due to cancellation
|
|
CHECK_FALSE(task_executed);
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("Tasks without callbacks don't auto-register") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
// Create task without callback - should not auto-register
|
|
auto task = fl::task::after_frame();
|
|
|
|
CHECK_FALSE(task.has_then());
|
|
CHECK(task.is_valid()); // Task should be valid but not auto-registered
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("every_ms task runs immediately once then respects timing interval") {
|
|
// Clear any leftover tasks from previous tests
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
int execution_count = 0;
|
|
|
|
// Create a task that runs every 100ms and auto-registers
|
|
auto task = fl::task::every_ms(100)
|
|
.then([&execution_count]() {
|
|
execution_count++;
|
|
});
|
|
|
|
// Task should be auto-registered and ready to run immediately
|
|
CHECK(task.is_valid());
|
|
CHECK(task.has_then());
|
|
|
|
// First update - should run immediately
|
|
fl::Scheduler::instance().update();
|
|
CHECK_EQ(execution_count, 1);
|
|
|
|
// Immediate second update - should NOT run (not enough time passed)
|
|
fl::Scheduler::instance().update();
|
|
CHECK_EQ(execution_count, 1); // Still 1, didn't run again
|
|
|
|
// Manually advance the task's last run time to simulate 50ms passing
|
|
uint32_t current_time = fl::time();
|
|
task.set_last_run_time(current_time - 50);
|
|
|
|
// Update - should still NOT run (only 50ms passed, need 100ms)
|
|
fl::Scheduler::instance().update();
|
|
CHECK_EQ(execution_count, 1); // Still 1
|
|
|
|
// Manually advance to simulate 100ms+ passing
|
|
task.set_last_run_time(current_time - 100);
|
|
|
|
// Update - should run now (100ms has passed)
|
|
fl::Scheduler::instance().update();
|
|
CHECK_EQ(execution_count, 2); // Should be 2 now
|
|
|
|
// Immediate update again - should NOT run
|
|
fl::Scheduler::instance().update();
|
|
CHECK_EQ(execution_count, 2); // Still 2
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
|
|
SUBCASE("after_frame task executes when FastLED.show() is called") {
|
|
// Clear any leftover tasks from previous tests - CRITICAL for test isolation
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
|
|
int execution_count = 0;
|
|
|
|
// Create an after_frame task that auto-registers
|
|
auto task = fl::task::after_frame()
|
|
.then([&execution_count]() {
|
|
execution_count++;
|
|
});
|
|
|
|
// Task should be auto-registered
|
|
CHECK(task.is_valid());
|
|
CHECK(task.has_then());
|
|
|
|
// Initial state - task hasn't run yet
|
|
CHECK_EQ(execution_count, 0);
|
|
|
|
// Manually calling scheduler update shouldn't trigger frame tasks yet
|
|
fl::Scheduler::instance().update();
|
|
CHECK_EQ(execution_count, 0); // Still 0
|
|
|
|
|
|
// Create a frame listener for this specific test
|
|
TestFrameListener listener;
|
|
|
|
// Instead of calling FastLED.show(), directly trigger the engine events
|
|
// This tests the task system without requiring LED hardware setup
|
|
fl::EngineEvents::onEndFrame();
|
|
|
|
// The after_frame task should have executed
|
|
CHECK_EQ(execution_count, 1);
|
|
|
|
// Calling onEndFrame() again should execute the task again (it's been removed as one-shot)
|
|
// But since it's already removed, create another task to test
|
|
auto task2 = fl::task::after_frame()
|
|
.then([&execution_count]() {
|
|
execution_count++;
|
|
});
|
|
|
|
fl::EngineEvents::onEndFrame();
|
|
CHECK_EQ(execution_count, 2);
|
|
|
|
// Clean up
|
|
fl::Scheduler::instance().clear_all_tasks();
|
|
}
|
|
}
|