// // Created by Zoe Roux on 2021-05-14. // #pragma once #include #include #include #include #include "Exception/WalError.hpp" #include "System/ISystem.hpp" #include "Models/Callback.hpp" #include "Scene/Scene.hpp" #if defined(PLATFORM_WEB) #include #endif namespace WAL { class Entity; //! @brief The main WAL class, it is used to setup and run the ECS. class Wal { private: //! @brief The list of registered systems std::vector> _systems = {}; //! @brief Start the game loop //! @param callback A callback called after each update of the game. It allow you to update the engine based on a specific game state. (you can also update the game state here) //! @param state An initial game state. If not specified, it will be defaulted. //! @tparam T A type used to track your game state. It must be default constructable. template void _run(const Callback &callback, T state = T()) { auto lastTick = std::chrono::steady_clock::now(); std::chrono::nanoseconds fBehind(0); while (!this->shouldClose) { auto now = std::chrono::steady_clock::now(); std::chrono::nanoseconds dtime = now - lastTick; fBehind += dtime; lastTick = now; while (fBehind > Wal::timestep) { fBehind -= Wal::timestep; for (auto &system : this->_systems) system->fixedUpdate(); } for (auto &system : this->_systems) system->update(dtime); callback(*this, state); } } #if defined(PLATFORM_WEB) template static void _runIteration(void *param) { static auto [wal, callback, state] = *reinterpret_cast &, T &> *>(param); static auto lastTick = std::chrono::steady_clock::now(); static std::chrono::nanoseconds fBehind(0); auto now = std::chrono::steady_clock::now(); std::chrono::nanoseconds dtime = now - lastTick; fBehind += dtime; lastTick = now; while (fBehind > Wal::timestep) { fBehind -= Wal::timestep; for (auto &system : wal._systems) system->fixedUpdate(); } for (auto &system : wal._systems) system->update(dtime); callback(wal, state); } #endif public: //! @brief The scene that contains entities. std::shared_ptr scene; //! @brief True if the engine should close after the end of the current tick. bool shouldClose = false; //! @brief The time between each fixed update. static constexpr std::chrono::nanoseconds timestep = std::chrono::milliseconds(8); //! @brief Create a new system in place. //! @return The wal instance used to call this function is returned. This allow method chaining. template Wal &addSystem(Types &&...params) { const std::type_info &type = typeid(T); auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { return typeid(*sys) == type; }); if (existing != this->_systems.end()) throw DuplicateError("A system of the type \"" + std::string(type.name()) + "\" already exists."); this->_systems.push_back(std::make_unique(*this, std::forward(params)...)); return *this; } //! @brief Add a system by copy. //! @return The wal instance used to call this function is returned. This allow method chaining. template Wal &addSystem(const T &system) { const std::type_info &type = typeid(T); auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { return typeid(*sys) == type; }); if (existing != this->_systems.end()) throw DuplicateError("A system of the type \"" + std::string(type.name()) + "\" already exists."); this->_systems.push_back(std::make_unique(system)); return *this; } //! @brief Get a system of a specific type //! @tparam T the type of the system. template T &getSystem() { const std::type_info &type = typeid(T); auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { return typeid(*sys) == type; }); if (existing == this->_systems.end()) throw NotFoundError("A system of the type \"" + std::string(type.name()) + "\" could not be found."); return *static_cast(existing->get()); } //! @brief Remove a system using it's type. template Wal &removeSystem() { const std::type_info &type = typeid(T); auto existing = std::find_if(this->_systems.begin(), this->_systems.end(), [&type] (auto &sys) { return typeid(*sys) == type; }); if (existing == this->_systems.end()) throw NotFoundError("No system could be found with the type \"" + std::string(type.name()) + "\"."); this->_systems.erase(existing); return *this; } //! @brief Start the game loop //! @param callback A callback called after each update of the game. It allow you to update the engine based on a specific game state. (you can also update the game state here) //! @param state An initial game state. If not specified, it will be defaulted. //! @tparam T A type used to track your game state. It must be default constructable. template void run(const Callback &callback, T state = T()) { #if defined(PLATFORM_WEB) std::tuple &, T &> iterationParams(*this, callback, state); return emscripten_set_main_loop_arg((em_arg_callback_func)_runIteration, (void *)&iterationParams, 0, 1); #else return this->_run(callback, state); #endif } //! @brief A default constructor Wal() = default; //! @brief A WAL can't be copy constructed Wal(const Wal &) = delete; //! @brief A default destructor ~Wal() = default; //! @brief A WAL can't be assigned. Wal &operator=(const Wal &) = delete; }; } // namespace WAL