commit dbc07373d44d4f33647ac7d1cfa2228d1f3a6ec2 Author: niansa Date: Thu May 4 10:38:09 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ad18f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + +# Qt Creator +CMakeLists.txt.user* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d4415f2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libasync"] + path = libasync + url = https://gitlab.com/niansa/libasync.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3a1e5ac --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.5) + +project(cosched LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_subdirectory(libasync) + +add_library(cosched STATIC scheduler.cpp include/scheduler.hpp) +target_link_libraries(cosched PUBLIC async) +target_include_directories(cosched PUBLIC include/) + +install(TARGETS cosched + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/include/scheduler.hpp b/include/scheduler.hpp new file mode 100644 index 0000000..b92b633 --- /dev/null +++ b/include/scheduler.hpp @@ -0,0 +1,157 @@ +#ifndef _SCHEDULER_HPP +#define _SCHEDULER_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace CoSched { +using Priority = uint8_t; +enum { + PRIO_LOWEST = -99, + PRIO_LOW = -20, + PRIO_NORMAL = 0, + PRIO_HIGH = 20, + PRIO_HIGHEST = 99, + PRIO_REALTIME = PRIO_HIGHEST +}; + +enum class TaskState { + running, + sleeping, + starting, + stopping +}; + + +class Task { + friend class Scheduler; + + class Scheduler *scheduler; + async::recurring_event resume; + + std::chrono::system_clock::time_point stopped_at; + + std::string name; + Priority priority = PRIO_NORMAL; + TaskState state = TaskState::starting; + +public: + Task(Scheduler *scheduler, const std::string& name) + : scheduler(scheduler), name(name) {} + Task(const Task&) = delete; + Task(Task&&) = delete; + + const std::string& get_name() const { + return name; + } + void set_name(const std::string& value) { + name = value; + } + + Priority get_priority() const { + return priority; + } + void set_priority(Priority value) { + priority = value; + } + + TaskState get_state() const { + return state; + } + + Scheduler& get_scheduler() const { + return *scheduler; + } + + async::result yield(); +}; + + +class TaskPtr { + unsigned *ref_count; + Task *task; + + void finalize(); + +public: + TaskPtr(Task *task) : task(task) { + ref_count = new unsigned(1); + } + ~TaskPtr() { + if (!task) return; + if (--*ref_count == 0) { + delete ref_count; + finalize(); + } + } + TaskPtr(const TaskPtr& o) : ref_count(o.ref_count), task(o.task) { + ++*ref_count; + } + TaskPtr(TaskPtr&& o) : ref_count(o.ref_count), task(o.task) { + o.task = nullptr; + } + + auto operator ->() { + return task; + } +}; + + +class Scheduler { + friend class Task; + friend class TaskPtr; + + std::mutex tasks_mutex; + std::vector> tasks; + + async::result yield(Task *task); + + void delete_task(Task *task) { + std::scoped_lock L(tasks_mutex); + tasks.erase(std::find_if(tasks.begin(), tasks.end(), [task] (const auto& o) {return o.get() == task;})); + } + + Task *get_next_task(); + +public: + Scheduler() {} + Scheduler(const Scheduler&) = delete; + Scheduler(Scheduler&&) = delete; + + const auto& get_tasks() const { + return tasks; + } + + TaskPtr create_task(const std::string& name) { + std::scoped_lock L(tasks_mutex); + return TaskPtr(tasks.emplace_back(std::make_unique(this, name)).get()); + } + + void run() { + while (!tasks.empty()) { + // Get next task + auto next_task = get_next_task(); + + // Resume task if any + if (next_task) next_task->resume.raise(); + } + } +}; + + +inline async::result Task::yield() { + co_await scheduler->yield(this); +} + +inline void TaskPtr::finalize() { + task->get_scheduler().delete_task(task); +} +} +#endif // _SCHEDULER_HPP diff --git a/libasync b/libasync new file mode 160000 index 0000000..7211953 --- /dev/null +++ b/libasync @@ -0,0 +1 @@ +Subproject commit 721195383b9005006826856fe84c5eabdf350882 diff --git a/scheduler.cpp b/scheduler.cpp new file mode 100644 index 0000000..6cc934c --- /dev/null +++ b/scheduler.cpp @@ -0,0 +1,42 @@ +#include "scheduler.hpp" + + + +async::result CoSched::Scheduler::yield(Task *task) { + task->state = TaskState::sleeping; + task->stopped_at = std::chrono::system_clock::now(); + co_await task->resume.async_wait(); + task->state = TaskState::running; +} + +CoSched::Task *CoSched::Scheduler::get_next_task() { + std::scoped_lock L(tasks_mutex); + + // Get tasks with highest priority + std::vector max_prio_tasks; + Priority max_prio = std::numeric_limits::min(); + for (auto& task : tasks) { + // Filter tasks that aren't sleeping + if (task->state != TaskState::sleeping) continue; + // Update max priority + if (task->priority > max_prio) { + max_prio = task->priority; + max_prio_tasks.clear(); + } + // Add task if matching + if (task->priority == max_prio) { + max_prio_tasks.push_back(task.get()); + } + } + + // Get least recently stopped task + Task *next_task = nullptr; + for (auto task : max_prio_tasks) { + if (!next_task || task->stopped_at < next_task->stopped_at) { + next_task = task; + } + } + + // Return next task; + return next_task; +} diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..802e127 --- /dev/null +++ b/test.cpp @@ -0,0 +1,25 @@ +#include "scheduler.hpp" + +#include +#include + + + +async::result test_task(CoSched::TaskPtr t) { + for (unsigned x = 100; x != 0; x--) { + std::cout << t->get_name() << ": " << x << '\n'; + co_await t->yield(); + } +} + +int main () { + CoSched::Scheduler scheduler; + for (const auto& name : {"A", "B", "C", "D", "E", "F"}) { + auto task = scheduler.create_task(name); + async::detach(test_task(task)); + if (task->get_name() == "B" || task->get_name() == "D") { + task->set_priority(CoSched::PRIO_HIGH); + } + } + scheduler.run(); +}