Skip to content

Commit

Permalink
Check resources prior to running tasks (#3297)
Browse files Browse the repository at this point in the history
* basic resource checking prior to running tasks

# Conflicts:
#	src/OSLikeStuff/task_scheduler/task_scheduler.cpp

* fix tests

* add test and fix bug caught by test

* add a license

format

* prefix with resource

* prefix defines
  • Loading branch information
m-m-adams authored Jan 19, 2025
1 parent 9a57771 commit d37eb1f
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 73 deletions.
15 changes: 12 additions & 3 deletions src/OSLikeStuff/scheduler_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ extern "C" {
typedef void (*TaskHandle)();
typedef bool (*RunCondition)();
typedef int8_t TaskID;
typedef uint32_t ResourceID;
#define RESOURCE_NONE 0
// for actually reading the SD
#define RESOURCE_SD 1
#define RESOURCE_USB 2
// for things that can't run in the SD routine
#define RESOURCE_SD_ROUTINE 4

/// Schedule a task that will be called at a regular interval.
///
Expand All @@ -44,15 +51,17 @@ typedef int8_t TaskID;
/// @param priority Priority of the task. Tasks with lower numbers are given preference over tasks with higher numbers.
/// @param backOffTime Minimum time from completing the task to calling it again in seconds.
/// @param targetTimeBetweenCalls Desired time between calls to the task, including the runtime for the task itself.
/// @param resource
uint8_t addRepeatingTask(TaskHandle task, uint8_t priority, double backOffTime, double targetTimeBetweenCalls,
double maxTimeBetweenCalls, const char* name);
double maxTimeBetweenCalls, const char* name, ResourceID resource);

/// 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);
uint8_t addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const char* name, ResourceID resources);

/// 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);
uint8_t addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name,
ResourceID resources);
void ignoreForStats();
double getAverageRunTimeforCurrentTask();
double getSystemTime();
Expand Down
51 changes: 51 additions & 0 deletions src/OSLikeStuff/task_scheduler/resource_checker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright © 2025 Mark Adams
*
* This file is part of The Synthstrom Audible Deluge Firmware.
*
* The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef RESOURCE_CHECKER_H
#define RESOURCE_CHECKER_H
#include <OSLikeStuff/scheduler_api.h>
#include <bitset>
#include <extern.h>
extern uint8_t currentlyAccessingCard;
extern uint32_t usbLock;
// this is basically a bitset however the enums need to be exposed to C code and this is easier to keep synced
class ResourceChecker {
uint32_t resources_{0};

public:
ResourceChecker() = default;
ResourceChecker(ResourceID resources) : resources_(resources) {};
/// returns whether all resources are available. This is basically shitty priority ceiling to avoid the potential of
/// a task locking one resource and then trying to yield while it waits for another
bool checkResources() const {
if (resources_ == RESOURCE_NONE) {
return true;
}
bool anythingLocked = false;
if ((resources_ & RESOURCE_SD) != 0u) {
anythingLocked |= currentlyAccessingCard;
}
if ((resources_ & RESOURCE_USB) != 0u) {
anythingLocked |= usbLock;
}
if ((resources_ & RESOURCE_SD_ROUTINE) != 0u) {
anythingLocked |= sdRoutineLock;
}
return !anythingLocked;
}
};

#endif // RESOURCE_CHECKER_H
23 changes: 16 additions & 7 deletions src/OSLikeStuff/task_scheduler/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifndef DELUGE_TASK_H
#define DELUGE_TASK_H
#include "OSLikeStuff/scheduler_api.h"
#include "resource_checker.h"
// internal to the scheduler - do not include from anywhere else
struct StatBlock {

Expand Down Expand Up @@ -64,16 +65,19 @@ struct Task {
Task() = default;

// constructor for making a "once" task
Task(TaskHandle _handle, uint8_t _priority, Time timeNow, Time _timeToWait, const char* _name)
: handle(_handle), lastCallTime(timeNow), removeAfterUse(true), name(_name) {
Task(TaskHandle _handle, uint8_t _priority, Time timeNow, Time _timeToWait, const char* _name,
ResourceChecker checker)
: handle(_handle), lastCallTime(timeNow), removeAfterUse(true), name(_name), _checker(checker) {

schedule = TaskSchedule{_priority, _timeToWait, _timeToWait, _timeToWait * 2};
}
// makes a repeating task
Task(TaskHandle task, TaskSchedule _schedule, const char* _name) : handle(task), schedule(_schedule), name(_name) {}
Task(TaskHandle task, TaskSchedule _schedule, const char* _name, ResourceChecker checker)
: handle(task), schedule(_schedule), name(_name), _checker(checker) {}
// makes a conditional once task
Task(TaskHandle task, uint8_t priority, RunCondition _condition, const char* _name)
: handle(task), state(State::BLOCKED), condition(_condition), removeAfterUse(true), name(_name) {
Task(TaskHandle task, uint8_t priority, RunCondition _condition, const char* _name, ResourceChecker checker)
: handle(task), state(State::BLOCKED), condition(_condition), removeAfterUse(true), name(_name),
_checker(checker) {

// good to go as soon as it's marked as runnable
schedule = {priority, 0, 0, 0};
Expand Down Expand Up @@ -102,11 +106,14 @@ struct Task {
return false;
}

[[nodiscard]] bool isReady(Time currentTime) const { return state == State::READY && isReleased(currentTime); };
[[nodiscard]] bool isRunnable() const { return state == State::READY; }
[[nodiscard]] bool isReady(Time currentTime) const {
return state == State::READY && isReleased(currentTime) && resourcesAvailable();
};
[[nodiscard]] bool isRunnable() const { return state == State::READY && resourcesAvailable(); }
[[nodiscard]] bool isReleased(Time currentTime) const {
return currentTime - lastFinishTime > schedule.backOffPeriod;
}
[[nodiscard]] bool resourcesAvailable() const { return _checker.checkResources(); }
TaskHandle handle{nullptr};
TaskSchedule schedule{0, 0, 0, 0};
Time idealCallTime{0};
Expand All @@ -126,6 +133,8 @@ struct Task {
Time totalTime{0};
int32_t timesCalled{0};
Time lastRunTime;

ResourceChecker _checker;
};

#endif // DELUGE_TASK_H
23 changes: 16 additions & 7 deletions src/OSLikeStuff/task_scheduler/task_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
#include "OSLikeStuff/task_scheduler/task_scheduler.h"

#include "io/debug/log.h"
#include "resource_checker.h"
#include <algorithm>
#include <iostream>

#if !IN_UNIT_TESTS
#include "memory/general_memory_allocator.h"
Expand Down Expand Up @@ -90,6 +90,9 @@ TaskID TaskManager::chooseBestTask(Time deadline) {
// first look based on target time
for (int i = (numActiveTasks - 1); i >= 0; i--) {
struct Task* t = &list[sortedList[i].task];
if (!t->isRunnable()) {
continue;
}
struct TaskSchedule* s = &t->schedule;
if (currentTime + t->durationStats.average < nextFinishTime
&& currentTime - t->lastFinishTime > s->targetInterval
Expand All @@ -100,6 +103,9 @@ TaskID TaskManager::chooseBestTask(Time deadline) {
// then look based on min time just to avoid busy waiting
for (int i = (numActiveTasks - 1); i >= 0; i--) {
struct Task* t = &list[sortedList[i].task];
if (!t->isRunnable()) {
continue;
}
struct TaskSchedule* s = &t->schedule;
if (currentTime + t->durationStats.average < nextFinishTime
&& currentTime - t->lastFinishTime > s->backOffPeriod) {
Expand All @@ -124,33 +130,36 @@ TaskID TaskManager::insertTaskToList(Task task) {
return index;
};

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

TaskID index = insertTaskToList(Task{task, schedule, name});
TaskID index = insertTaskToList(Task{task, schedule, name, resources});

createSortedList();
return index;
}

TaskID TaskManager::addOnceTask(TaskHandle task, uint8_t priority, Time timeToWait, const char* name) {
TaskID TaskManager::addOnceTask(TaskHandle task, uint8_t priority, Time timeToWait, const char* name,
ResourceChecker resources) {
if (numRegisteredTasks >= (kMaxTasks)) {
return -1;
}
Time timeToStart = running ? getSecondsFromStart() : Time(0);
TaskID index = insertTaskToList(Task{task, priority, timeToStart, timeToWait, name});
TaskID index = insertTaskToList(Task{task, priority, timeToStart, timeToWait, name, resources});

createSortedList();
return index;
}

TaskID TaskManager::addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name) {
TaskID TaskManager::addConditionalTask(TaskHandle task, uint8_t priority, RunCondition condition, const char* name,
ResourceChecker resources) {
if (numRegisteredTasks >= (kMaxTasks)) {
return -1;
}
TaskID index = insertTaskToList(Task{task, priority, condition, name});
TaskID index = insertTaskToList(Task{task, priority, condition, name, resources});
createSortedList();
return index;
}
Expand Down
7 changes: 4 additions & 3 deletions src/OSLikeStuff/task_scheduler/task_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ struct TaskManager {
void removeTask(TaskID id);
void runTask(TaskID id);
TaskID chooseBestTask(Time deadline);
TaskID addRepeatingTask(TaskHandle task, TaskSchedule schedule, const char* name);
TaskID addRepeatingTask(TaskHandle task, TaskSchedule schedule, const char* name, ResourceChecker resources);

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

void createSortedList();
TaskID insertTaskToList(Task task);
Expand Down
35 changes: 26 additions & 9 deletions src/OSLikeStuff/task_scheduler/task_scheduler_c_api.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
//
// Created by Mark Adams on 2024-12-05.
//
/*
* Copyright © 2025 Mark Adams
*
* This file is part of The Synthstrom Audible Deluge Firmware.
*
* The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/

#include "OSLikeStuff/scheduler_api.h"
#include "OSLikeStuff/task_scheduler/task_scheduler.h"
#include "resource_checker.h"

extern TaskManager taskManager;

Expand All @@ -23,16 +38,18 @@ void setNextRunTimeforCurrentTask(double seconds) {
}

uint8_t addRepeatingTask(TaskHandle task, uint8_t priority, double backOffTime, double targetTimeBetweenCalls,
double maxTimeBetweenCalls, const char* name) {
double maxTimeBetweenCalls, const char* name, ResourceID resources) {
return taskManager.addRepeatingTask(
task, TaskSchedule{priority, backOffTime, targetTimeBetweenCalls, maxTimeBetweenCalls}, name);
task, TaskSchedule{priority, backOffTime, targetTimeBetweenCalls, maxTimeBetweenCalls}, name,
ResourceChecker{resources});
}
uint8_t addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const char* name) {
return taskManager.addOnceTask(task, priority, timeToWait, name);
uint8_t addOnceTask(TaskHandle task, uint8_t priority, double timeToWait, const char* name, ResourceID resources) {
return taskManager.addOnceTask(task, priority, timeToWait, name, ResourceChecker{resources});
}

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

void yield(RunCondition until) {
Expand Down
40 changes: 23 additions & 17 deletions src/deluge/deluge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -577,47 +577,53 @@ void registerTasks() {

// 0-9: High priority (10 for dyn tasks)
uint8_t p = 0;
addRepeatingTask(&(AudioEngine::routine), p++, 0.00001, 16 / 44100., 24 / 44100., "audio routine");
addRepeatingTask(&(AudioEngine::routine), p++, 0.00001, 16 / 44100., 24 / 44100., "audio routine", RESOURCE_NONE);
// this one runs quickly and frequently to check for encoder changes
addRepeatingTask([]() { encoders::readEncoders(); }, p++, 0.0005, 0.001, 0.001, "read encoders");
addRepeatingTask([]() { encoders::readEncoders(); }, p++, 0.0005, 0.001, 0.001, "read encoders", RESOURCE_NONE);
// formerly part of audio routine, updates midi and clock
addRepeatingTask([]() { playbackHandler.routine(); }, p++, 2 / 44100., 16 / 44100, 32 / 44100., "playback routine");
addRepeatingTask([]() { playbackHandler.routine(); }, p++, 2 / 44100., 16 / 44100, 32 / 44100., "playback routine",
RESOURCE_NONE);
addRepeatingTask([]() { playbackHandler.midiRoutine(); }, p++, 2 / 44100., 16 / 44100, 32 / 44100.,
"playback routine", RESOURCE_SD | RESOURCE_USB);
addRepeatingTask([]() { audioFileManager.loadAnyEnqueuedClusters(128, false); }, p++, 0.00001, 0.00001, 0.00002,
"load clusters");
"load clusters", RESOURCE_NONE);
// handles sd card recorders
// named "slow" but isn't actually, it handles audio recording setup
addRepeatingTask(&AudioEngine::slowRoutine, p++, 0.001, 0.005, 0.05, "audio slow");
addRepeatingTask(&(readButtonsAndPadsOnce), p++, 0.005, 0.005, 0.01, "buttons and pads");
addRepeatingTask(&AudioEngine::slowRoutine, p++, 0.001, 0.005, 0.05, "audio slow", RESOURCE_NONE);
addRepeatingTask(&(readButtonsAndPadsOnce), p++, 0.005, 0.005, 0.01, "buttons and pads", RESOURCE_NONE);

// 11-19: Medium priority (20 for dyn tasks)
p = 11;
addRepeatingTask([]() { encoders::interpretEncoders(true); }, p++, 0.005, 0.005, 0.01, "interpret encoders fast");
addRepeatingTask([]() { encoders::interpretEncoders(true); }, p++, 0.005, 0.005, 0.01, "interpret encoders fast",
RESOURCE_NONE);
// 30 Hz update desired?
addRepeatingTask(&doAnyPendingUIRendering, p++, 0.01, 0.01, 0.03, "pending UI");
addRepeatingTask(&doAnyPendingUIRendering, p++, 0.01, 0.01, 0.03, "pending UI", RESOURCE_NONE);
// this one actually actions them
addRepeatingTask([]() { encoders::interpretEncoders(false); }, p++, 0.005, 0.005, 0.01, "interpret encoders slow");
addRepeatingTask([]() { encoders::interpretEncoders(false); }, p++, 0.005, 0.005, 0.01, "interpret encoders slow",
RESOURCE_SD_ROUTINE);

// Check for and handle queued SysEx traffic
addRepeatingTask([]() { smSysex::handleNextSysEx(); }, p++, 0.0002, 0.0002, 0.01, "Handle pending SysEx traffic.");
addRepeatingTask([]() { smSysex::handleNextSysEx(); }, p++, 0.0002, 0.0002, 0.01, "Handle pending SysEx traffic.",
RESOURCE_SD);

// 21-29: Low priority (30 for dyn tasks)
p = 21;
// these ones are actually "slow" -> file manager just checks if an sd card has been inserted, audio recorder checks
// if recordings are finished
addRepeatingTask([]() { audioFileManager.slowRoutine(); }, p++, 0.1, 0.1, 0.2, "audio file slow");
addRepeatingTask([]() { audioRecorder.slowRoutine(); }, p++, 0.01, 0.1, 0.1, "audio recorder slow");
addRepeatingTask([]() { audioFileManager.slowRoutine(); }, p++, 0.1, 0.1, 0.2, "audio file slow", RESOURCE_SD);
addRepeatingTask([]() { audioRecorder.slowRoutine(); }, p++, 0.01, 0.1, 0.1, "audio recorder slow", RESOURCE_NONE);
// formerly part of cluster loading (why? no idea), actions undo/redo midi commands
addRepeatingTask([]() { playbackHandler.slowRoutine(); }, p++, 0.01, 0.1, 0.1, "playback routine");
addRepeatingTask([]() { playbackHandler.slowRoutine(); }, p++, 0.01, 0.1, 0.1, "playback routine", RESOURCE_SD);
// 31-39: Idle priority (40 for dyn tasks)
p = 31;
addRepeatingTask(&(PIC::flush), p++, 0.001, 0.001, 0.02, "PIC flush");
addRepeatingTask(&(PIC::flush), p++, 0.001, 0.001, 0.02, "PIC flush", RESOURCE_NONE);
if (hid::display::have_oled_screen) {
addRepeatingTask(&(oledRoutine), p++, 0.01, 0.01, 0.02, "oled routine");
addRepeatingTask(&(oledRoutine), p++, 0.01, 0.01, 0.02, "oled routine", RESOURCE_NONE);
}
// needs to be called very frequently,
// handles animations and checks on the timers for any infrequent actions
// long term this should probably be made into an idle task
addRepeatingTask([]() { uiTimerManager.routine(); }, p++, 0.0001, 0.0007, 0.01, "ui routine");
addRepeatingTask([]() { uiTimerManager.routine(); }, p++, 0.0001, 0.0007, 0.01, "ui routine", RESOURCE_NONE);

// addRepeatingTask([]() { AudioEngine::routineWithClusterLoading(true); }, 0, 1 / 44100., 16 / 44100., 32 / 44100.,
// true); addRepeatingTask(&(AudioEngine::routine), 0, 16 / 44100., 64 / 44100., true);
Expand Down Expand Up @@ -868,7 +874,7 @@ extern "C" int32_t deluge_main(void) {
midiFollow.readDefaultsFromFile();
PadLEDs::setBrightnessLevel(FlashStorage::defaultPadBrightness);
setupBlankSong(); // we always need to do this
addConditionalTask(setupStartupSong, 100, isCardReady, "load startup song");
addConditionalTask(setupStartupSong, 100, isCardReady, "load startup song", RESOURCE_SD | RESOURCE_SD_ROUTINE);

#ifdef TEST_VECTOR
NoteVector noteVector;
Expand Down
8 changes: 5 additions & 3 deletions src/deluge/playback/playback_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,18 @@ extern "C" uint32_t triggerClockRisingEdgeTimes[];
extern "C" uint32_t triggerClockRisingEdgesReceived;
extern "C" uint32_t triggerClockRisingEdgesProcessed;

// This function will be called repeatedly, at all times, to see if it's time to do a tick, and such
void PlaybackHandler::routine() {

void PlaybackHandler::midiRoutine() {
// Check incoming USB MIDI
midiEngine.checkIncomingUsbMidi();

// Check incoming Serial MIDI
for (int32_t i = 0; i < 12 && midiEngine.checkIncomingSerialMidi(); i++) {
;
}
}

// This function will be called repeatedly, at all times, to see if it's time to do a tick, and such
void PlaybackHandler::routine() {

// Check analog clock input
if (triggerClockRisingEdgesProcessed != triggerClockRisingEdgesReceived) {
Expand Down
1 change: 1 addition & 0 deletions src/deluge/playback/playback_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ constexpr uint16_t metronomeValueBoundaries[16] = {
class PlaybackHandler {
public:
PlaybackHandler();
void midiRoutine();
void routine();

void playButtonPressed(int32_t buttonPressLatency);
Expand Down
Loading

0 comments on commit d37eb1f

Please sign in to comment.