Program Listing for File ThreadPool.hpp

Return to documentation for file (engine/include/Cacao/ThreadPool.hpp)

#pragma once

#include "DllHelper.hpp"
#include "Exceptions.hpp"

#include <memory>
#include <future>
#include <concepts>
#include <vector>
#include <functional>
#include <algorithm>

namespace Cacao {
    class CACAO_API ThreadPool {
      public:
        static ThreadPool& Get();

        ThreadPool(const ThreadPool&) = delete;
        ThreadPool(ThreadPool&&) = delete;
        ThreadPool& operator=(const ThreadPool&) = delete;
        ThreadPool& operator=(ThreadPool&&) = delete;

        template<typename F, typename... Args, typename R = std::invoke_result_t<F&&, Args&&...>>
            requires std::invocable<F&&, Args&&...>
        std::shared_future<R> Exec(F func, Args... args) {
            Check<BadInitStateException>(IsRunning(), "The thread pool must be running to execute a task!");

            //Create a task and get a result future
            std::shared_ptr<std::packaged_task<R()>> task;
            if constexpr(sizeof...(args) == 0) {
                task = std::make_shared<std::packaged_task<R()>>(std::move(func));
            } else {
                //Wrap the function so it doesn't need any arguments
                auto wrapper = std::bind(std::forward<F>(func), std::forward<Args...>(args...));
                task = std::make_shared<std::packaged_task<R()>>(std::move(wrapper));
            }
            std::shared_future<R> result = task->get_future().share();

            //Use closures to capture the task so its future can be set
            ImplSubmit([task]() { (*task)(); });
            return result;
        }

        template<typename F, typename... Args>
            requires std::invocable<F&&, std::stop_token, Args&&...> && std::is_same_v<std::invoke_result_t<F&&, std::stop_token, Args&&...>, void>
        std::stop_source ExecContinuous(F func, Args... args) {
            Check<BadInitStateException>(IsRunning(), "The thread pool must be running to execute a continuous function!");

            //Create stop source
            std::stop_source& stop = stops.emplace_back();

            //Wrap the function so it doesn't need any arguments
            if constexpr(sizeof...(args) == 0) {
                auto wrapper = std::bind_front(std::forward<F>(func), stop.get_token());

                //Submit the function
                ImplSubmit([this, wrapper, &stop]() {
                    //Invoke function and then remove
                    wrapper();
                    if(auto it = std::find(stops.begin(), stops.end(), stop); it != stops.end()) {
                        stops.erase(it);
                    }
                });
            } else {
                auto wrapper = std::bind_front(std::forward<F>(func), stop.get_token(), std::forward<Args...>(args...));

                //Submit the function
                ImplSubmit([this, wrapper, &stop]() {
                    //Invoke function and then remove
                    wrapper();
                    if(auto it = std::find(stops.begin(), stops.end(), stop); it != stops.end()) {
                        stops.erase(it);
                    }
                });
            }

            return stop;
        }

        void Start();

        void Stop();

        bool IsRunning() const;

        std::size_t GetThreadCount() const;

      private:
        struct Impl;
        std::unique_ptr<Impl> impl;

        std::vector<std::stop_source> stops;

        void ImplSubmit(std::function<void()> job);

        ThreadPool();
        ~ThreadPool();
    };
}