You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.2 KiB
109 lines
3.2 KiB
/*
|
|
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-only
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <future>
|
|
#include <memory>
|
|
#include <memory_resource>
|
|
#include <string>
|
|
|
|
#include "esp_heap_caps.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/portmacro.h"
|
|
#include "freertos/projdefs.h"
|
|
#include "freertos/queue.h"
|
|
#include "freertos/task.h"
|
|
#include "span.hpp"
|
|
|
|
namespace tasks {
|
|
|
|
/*
|
|
* Enumeration of every task (basically a thread) started within the firmware.
|
|
* These are centralised so that it is easier to reason about the relative
|
|
* priorities of tasks, as well as the amount and location of memory allocated
|
|
* to each one.
|
|
*/
|
|
enum class Type {
|
|
// The main UI task. This runs the LVGL main loop.
|
|
kUi,
|
|
// The main audio pipeline task. Decodes files into PCM stream.
|
|
kAudioDecoder,
|
|
// Second audio task. Converts the PCM stream into one suitable for the
|
|
// current output (e.g. downsampling for bluetooth).
|
|
kAudioConverter,
|
|
// Task for running database queries.
|
|
kDatabase,
|
|
// Task for async background work
|
|
kBackgroundWorker,
|
|
};
|
|
|
|
template <Type t>
|
|
auto Name() -> std::pmr::string;
|
|
template <Type t>
|
|
auto AllocateStack() -> cpp::span<StackType_t>;
|
|
template <Type t>
|
|
auto Priority() -> UBaseType_t;
|
|
|
|
auto PersistentMain(void* fn) -> void;
|
|
|
|
template <Type t>
|
|
auto StartPersistent(const std::function<void(void)>& fn) -> void {
|
|
StaticTask_t* task_buffer = static_cast<StaticTask_t*>(heap_caps_malloc(
|
|
sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
|
cpp::span<StackType_t> stack = AllocateStack<t>();
|
|
xTaskCreateStatic(&PersistentMain, Name<t>().c_str(), stack.size(),
|
|
new std::function<void(void)>(fn), Priority<t>(),
|
|
stack.data(), task_buffer);
|
|
}
|
|
|
|
template <Type t>
|
|
auto StartPersistent(BaseType_t core, const std::function<void(void)>& fn)
|
|
-> void {
|
|
StaticTask_t* task_buffer = static_cast<StaticTask_t*>(heap_caps_malloc(
|
|
sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
|
cpp::span<StackType_t> stack = AllocateStack<t>();
|
|
xTaskCreateStaticPinnedToCore(&PersistentMain, Name<t>().c_str(),
|
|
stack.size(), new std::function<void(void)>(fn),
|
|
Priority<t>(), stack.data(), task_buffer, core);
|
|
}
|
|
|
|
class WorkerPool {
|
|
private:
|
|
QueueHandle_t queue_;
|
|
using WorkItem = std::function<void(void)>*;
|
|
static auto Main(void* instance);
|
|
|
|
public:
|
|
WorkerPool();
|
|
~WorkerPool();
|
|
|
|
/*
|
|
* Schedules the given function to be executed on the worker task, and
|
|
* asynchronously returns the result as a future.
|
|
*/
|
|
template <typename T>
|
|
auto Dispatch(const std::function<T(void)> fn) -> std::future<T> {
|
|
std::shared_ptr<std::promise<T>> promise =
|
|
std::make_shared<std::promise<T>>();
|
|
WorkItem item =
|
|
new std::function([=]() { promise->set_value(std::invoke(fn)); });
|
|
xQueueSend(queue_, &item, portMAX_DELAY);
|
|
return promise->get_future();
|
|
}
|
|
|
|
WorkerPool(const WorkerPool&) = delete;
|
|
WorkerPool& operator=(const WorkerPool&) = delete;
|
|
};
|
|
|
|
/* Specialisation of Evaluate for functions that return nothing. */
|
|
template <>
|
|
auto WorkerPool::Dispatch(const std::function<void(void)> fn)
|
|
-> std::future<void>;
|
|
|
|
} // namespace tasks
|
|
|