Skip to content

Commit

Permalink
Os/conditional tasks (SynthstromAudible#2071)
Browse files Browse the repository at this point in the history
* basic framework

* add condition checking when no tasks to run

* remove excess prints from scheduler tests

* add external api for conditional tasks
  • Loading branch information
m-m-adams committed Jun 2, 2024
1 parent 8877236 commit 368a871
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 27 deletions.
80 changes: 63 additions & 17 deletions src/OSLikeStuff/task_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "io/debug/log.h"
#include "util/container/static_vector.hpp"
#include <algorithm>
#include <iostream>

#define SCHEDULER_DETAILED_STATS (1 && ENABLE_TEXT_OUTPUT)

Expand Down Expand Up @@ -61,6 +62,16 @@ struct Task {
name = _name;
removeAfterUse = false;
}
// makes a conditional task
Task(TaskHandle task, uint8_t priority, RunCondition _condition, const char* _name) {
handle = task;
// good to go as soon as it's marked as runnable
schedule = {priority, 0, 0, 0};
name = _name;
removeAfterUse = true;
runnable = false;
condition = _condition;
}
TaskHandle handle{nullptr};
TaskSchedule schedule{0, 0, 0, 0};
double lastCallTime{0};
Expand All @@ -70,7 +81,8 @@ struct Task {
#if SCHEDULER_DETAILED_STATS
StatBlock latency;
#endif

bool runnable{true};
RunCondition condition{nullptr};
bool removeAfterUse{false};
const char* name{nullptr};

Expand All @@ -93,6 +105,7 @@ struct TaskManager {
// Sorted list of the current numActiveTasks, lowest priority (highest number) first
std::array<SortedTask, kMaxTasks> sortedList;
uint8_t numActiveTasks = 0;
uint8_t numRegisteredTasks = 0;
double mustEndBefore = 128; // use for testing or I guess if you want a second temporary task manager?
bool running{false};
double cpuTime{0};
Expand All @@ -102,25 +115,28 @@ struct TaskManager {
void runTask(TaskID id);
TaskID chooseBestTask(double deadline);
TaskID addRepeatingTask(TaskHandle task, TaskSchedule schedule, const char* name);
TaskID addRepeatingTask(TaskHandle task, uint8_t priority, double backOffTime, double targetTimeBetweenCalls,
double maxTimeBetweenCalls, const char* name);

TaskID addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const char* name);
TaskID addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name);

void createSortedList();
void clockRolledOver();
TaskID insertTaskToList(Task task);
void printStats();
bool checkConditionalTasks();
};

TaskManager taskManager;

void TaskManager::createSortedList() {
int j = 0;
for (TaskID i = 0; i < kMaxTasks; i++) {
if (list[i].handle != nullptr) {
if (list[i].handle != nullptr && list[i].runnable) {
sortedList[j] = (SortedTask{list[i].schedule.priority, i});
j++;
}
}
numActiveTasks = j;
if (numActiveTasks > 1) {
// &array[size] is UB, or at least Clang + MSVC manages to choke
// on it. So grab what is at worst size-1, and then increment
Expand Down Expand Up @@ -198,18 +214,14 @@ TaskID TaskManager::insertTaskToList(Task task) {
}
if (index < kMaxTasks) {
list[index] = task;
numActiveTasks++;
numRegisteredTasks++;
}

return index;
};
TaskID TaskManager::addRepeatingTask(TaskHandle task, uint8_t priority, double backOffTime,
double targetTimeBetweenCalls, double maxTimeBetweenCalls, const char* name) {
return addRepeatingTask(task, TaskSchedule{priority, backOffTime, targetTimeBetweenCalls, maxTimeBetweenCalls},
name);
}

TaskID TaskManager::addRepeatingTask(TaskHandle task, TaskSchedule schedule, const char* name) {
if (numActiveTasks >= (kMaxTasks)) {
if (numRegisteredTasks >= (kMaxTasks)) {
return -1;
}

Expand All @@ -220,7 +232,7 @@ TaskID TaskManager::addRepeatingTask(TaskHandle task, TaskSchedule schedule, con
}

TaskID TaskManager::addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const char* name) {
if (numActiveTasks >= (kMaxTasks)) {
if (numRegisteredTasks >= (kMaxTasks)) {
return -1;
}
double timeToStart = running ? getTimerValueSeconds(0) : 0;
Expand All @@ -230,9 +242,18 @@ TaskID TaskManager::addOnceTask(TaskHandle task, uint8_t priority, double timeTo
return index;
}

TaskID TaskManager::addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name) {
if (numRegisteredTasks >= (kMaxTasks)) {
return -1;
}
TaskID index = insertTaskToList(Task{task, priority, condition, name});
// don't sort since it's not runnable yet anyway, so no change to the active list
return index;
}

void TaskManager::removeTask(TaskID id) {
list[id] = Task{};
numActiveTasks--;
numRegisteredTasks--;
createSortedList();
return;
}
Expand Down Expand Up @@ -298,13 +319,34 @@ void TaskManager::start(double duration) {
if (task >= 0) {
runTask(task);
}
else if (newTime > lastPrintedStats + 10) {
lastPrintedStats = newTime;
// couldn't find anything so here we go
printStats();
else {
bool addedTask = checkConditionalTasks();
// if we sorted our list then we should get back to running things and not print stats
if (!addedTask && newTime > lastPrintedStats + 10) {
lastPrintedStats = newTime;
// couldn't find anything so here we go
printStats();
}
}
}
}
bool TaskManager::checkConditionalTasks() {
bool addedTask = false;
for (int i = 0; i < numRegisteredTasks; i++) {
struct Task* t = &list[i];
if (t->handle != nullptr && !(t->runnable)) {
t->runnable = t->condition();
if (t->runnable) {
addedTask = true;
}
}
}
if (addedTask) {
createSortedList();
return true;
}
return false;
}
void TaskManager::printStats() {
D_PRINTLN("Dumping task manager stats:");
for (auto task : list) {
Expand Down Expand Up @@ -349,6 +391,10 @@ uint8_t addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const
return taskManager.addOnceTask(task, priority, timeToWait, name);
}

uint8_t addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name) {
return taskManager.addConditionalTask(task, priority, condition, name);
}

void removeTask(TaskID id) {
return taskManager.removeTask(id);
}
5 changes: 5 additions & 0 deletions src/OSLikeStuff/task_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern "C" {

/// void function with no arguments
typedef void (*TaskHandle)();
typedef bool (*RunCondition)();
typedef int8_t TaskID;
struct TaskSchedule {
// 0 is highest priority
Expand Down Expand Up @@ -54,6 +55,10 @@ uint8_t addRepeatingTask(TaskHandle task, uint8_t priority, double backOffTime,

/// Add a task to run once, aiming to run at current time + timeToWait and worst case run at timeToWait*10
uint8_t addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const char* name);

/// add a task that runs only after the condition returns true. Condition checks should be very fast or they could
/// interfere with scheduling
uint8_t addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name);
void removeTask(TaskID id);
/// start the task scheduler
void startTaskManager();
Expand Down
32 changes: 22 additions & 10 deletions tests/unit/scheduler_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ TEST(Scheduler, schedule) {
mock().clear();
// will be called one less time due to the time the sleep takes not being zero
mock().expectNCalls(0.01 / 0.001 - 1, "sleep_50ns");
taskManager.addRepeatingTask(sleep_50ns, 0, 0.001, 0.001, 0.001, "sleep_50ns");
addRepeatingTask(sleep_50ns, 0, 0.001, 0.001, 0.001, "sleep_50ns");
// run the scheduler for just under 10ms, calling the function to sleep 50ns every 1ms
taskManager.start(0.0095);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

Expand All @@ -73,7 +72,6 @@ TEST(Scheduler, remove) {

// run the scheduler for just under 10ms, calling the function to sleep 50ns every 1ms
taskManager.start(0.0095);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

Expand All @@ -84,18 +82,36 @@ TEST(Scheduler, scheduleOnce) {
addOnceTask(sleep_50ns, 0, 0.001, "sleep 50ns");
// run the scheduler for just under 10ms, calling the function to sleep 50ns every 1ms
taskManager.start(0.0095);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

TEST(Scheduler, scheduleConditional) {
mock().clear();
mock().expectNCalls(1, "sleep_50ns");
// will load as blocked but immediately pass condition
addConditionalTask(sleep_50ns, 0, []() { return true; }, "sleep 50ns");
// run the scheduler for just under 10ms, calling the function to sleep 50ns every 1ms
taskManager.start(0.0095);
mock().checkExpectations();
};

TEST(Scheduler, scheduleConditionalDoesntRun) {
mock().clear();
mock().expectNCalls(0, "sleep_50ns");
// will load as blocked but immediately pass condition
addConditionalTask(sleep_50ns, 0, []() { return false; }, "sleep 50ns");
// run the scheduler for just under 10ms, calling the function to sleep 50ns every 1ms
taskManager.start(0.0095);
mock().checkExpectations();
};

TEST(Scheduler, backOffTime) {
mock().clear();
// will be called one less time due to the time the sleep takes not being zero
mock().expectNCalls(9, "sleep_50ns");
taskManager.addRepeatingTask(sleep_50ns, 1, 0.01, 0.001, 1, "sleep_50ns");
addRepeatingTask(sleep_50ns, 1, 0.01, 0.001, 1, "sleep_50ns");
// run the scheduler for just under 10ms, calling the function to sleep 50ns every 1ms
taskManager.start(0.1);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

Expand All @@ -108,7 +124,6 @@ TEST(Scheduler, scheduleOnceWithRepeating) {
addOnceTask(sleep_2ms, 11, 0.0094, "sleep 2ms");
// run the scheduler for 10ms
taskManager.start(0.0095);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

Expand All @@ -124,7 +139,6 @@ TEST(Scheduler, removeWithPriZero) {
addOnceTask(sleep_2ms, 11, 0.009, "sleep 2ms");
// run the scheduler for 10ms
taskManager.start(0.01);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

Expand Down Expand Up @@ -174,7 +188,6 @@ TEST(Scheduler, scheduleMultiple) {
addOnceTask(sleep_2ms, 11, 0.0094, "sleep 2ms");
// run the scheduler for 10ms
taskManager.start(0.0095);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;
mock().checkExpectations();
};

Expand All @@ -193,7 +206,6 @@ TEST(Scheduler, overSchedule) {
auto twomsHandle = addRepeatingTask(sleep_2ms, 100, 0.001, 0.002, 0.005, "sleep 2ms");
// run the scheduler for 10ms
taskManager.start(0.0099);
std::cout << "ending tests at " << getTimerValueSeconds(0) << std::endl;

mock().checkExpectations();
};
Expand Down

0 comments on commit 368a871

Please sign in to comment.