Program Listing for File Engine.hpp

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

#pragma once

#include <future>
#include <mutex>
#include <string>
#include <chrono>
#include <filesystem>
#include <thread>
#include <queue>
#include <functional>

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

using namespace std::chrono_literals;

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

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

        //======================= CONFIGURATION =======================

        struct CACAO_API Config {
            std::chrono::milliseconds fixedTickRate = 20ms;

            int maxFrameLag;

            bool alwaysRerenderUI = false;
        } config;

        struct CACAO_API InitConfig {
            bool standalone = false;

            std::string initialRequestedBackend;

            std::string preferredWindowProvider;

            bool suppressConsoleLogging = false;

            bool suppressFileLogging = false;

            ClientIdentity clientID;
        };

        const InitConfig& GetInitConfig() const {
            return icfg;
        }

        //===================== UTILITY FUNCTIONS =====================

        const std::filesystem::path GetDataDirectory();

        template<typename F, typename... Args, typename R = std::invoke_result_t<F&&, Args&&...>>
            requires std::invocable<F&&, Args&&...>
        std::shared_future<R> RunTaskOnMainThread(F func, Args... args) {
            Check<BadStateException>(state == State::Running, "Engine must be in running state to submit main thread 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();

            //Is this the main thread?
            if(std::this_thread::get_id() == mainThread) {
                (*task)();
                return result;
            }

            //Add task to queue
            {
                std::lock_guard lk(mttQueueMtx);
                mainThreadTasksQueue.emplace([task]() { (*task)(); });
            }

            //Return future
            return result;
        }

        //========================= LIFECYCLE =========================

        enum class State {
            Dead,
            Alive,
            Ready,
            Running
        };

        void CoreInit(const InitConfig& initCfg);

        void GfxInit();

        void Run();

        void Quit();

        void GfxShutdown();

        void CoreShutdown();

        State GetState() {
            std::lock_guard lk(stateMtx);
            return state;
        }

      private:
        InitConfig icfg;
        State state;
        std::mutex stateMtx;

        std::queue<std::function<void()>> mainThreadTasksQueue;
        std::mutex mttQueueMtx;
        std::thread::id mainThread;

        Engine();
        ~Engine();
    };
}