Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check resources prior to running tasks #3297

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading