From a3804c170f228275c311c75b4d0f1a370feb1bd5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 2 Jun 2021 21:29:14 +0200 Subject: [PATCH 01/22] Optimizing component handling --- lib/wal/sources/Entity/Entity.cpp | 19 +++++++------------ lib/wal/sources/Entity/Entity.hpp | 23 ++++++++++------------- sources/Map/Map.cpp | 19 +++++++++---------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/lib/wal/sources/Entity/Entity.cpp b/lib/wal/sources/Entity/Entity.cpp index 56627a16..1dd1a1b3 100644 --- a/lib/wal/sources/Entity/Entity.cpp +++ b/lib/wal/sources/Entity/Entity.cpp @@ -21,7 +21,7 @@ namespace WAL _disabled(other._disabled) { for (const auto &cmp : other._components) - this->addComponent(*cmp); + this->addComponent(*cmp.second); } unsigned Entity::getUid() const @@ -46,25 +46,20 @@ namespace WAL Entity &Entity::addComponent(const Component &component) { - if (this->hasComponent(typeid(component))) - throw DuplicateError("A component of the type \"" + std::string(typeid(component).name()) + "\" already exists."); - this->_components.emplace_back(component.clone(*this)); + const std::type_index &type = typeid(component); + if (this->hasComponent(type)) + throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists."); + this->_components.emplace(type, component.clone(*this)); return *this; } bool Entity::hasComponent(const std::type_info &type) const { - auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { - return typeid(*cmp) == type; - }); - return existing != this->_components.end(); + return this->hasComponent(static_cast(type)); } bool Entity::hasComponent(const std::type_index &type) const { - auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { - return std::type_index(typeid(*cmp)) == type; - }); - return existing != this->_components.end(); + return this->_components.contains(type); } } // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp index 8e157c6e..e9fa672e 100644 --- a/lib/wal/sources/Entity/Entity.hpp +++ b/lib/wal/sources/Entity/Entity.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include #include #include "Component/Component.hpp" @@ -24,7 +24,7 @@ namespace WAL //! @brief Is this entity enabled? bool _disabled = false; //! @brief The list of the components of this entity - std::vector> _components = {}; + std::unordered_map> _components = {}; //! @brief This ID will be the one of the next entity created. static unsigned nextID; @@ -45,13 +45,11 @@ namespace WAL template T &getComponent() { - const std::type_info &type = typeid(T); - auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { - return typeid(*cmp) == type; - }); + const std::type_index &type = typeid(T); + auto existing = this->_components.find(type); if (existing == this->_components.end()) throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); - return *static_cast(existing->get()); + return *static_cast(existing->second.get()); } //! @brief Check if this entity has a component. @@ -77,9 +75,10 @@ namespace WAL template Entity &addComponent(Types &&...params) { - if (this->hasComponent()) - throw DuplicateError("A component of the type \"" + std::string(typeid(T).name()) + "\" already exists."); - this->_components.push_back(std::make_unique(*this, std::forward(params)...)); + const std::type_index &type = typeid(T); + if (this->hasComponent(type)) + throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists."); + this->_components[type] = std::make_unique(*this, std::forward(params)...); return *this; } @@ -94,9 +93,7 @@ namespace WAL Entity &removeComponent() { const std::type_info &type = typeid(T); - auto existing = std::find_if(this->_components.begin(), this->_components.end(), [&type] (const auto &cmp) { - return typeid(*cmp) == type; - }); + auto existing = this->_components.find(type); if (existing == this->_components.end()) throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); this->_components.erase(existing); diff --git a/sources/Map/Map.cpp b/sources/Map/Map.cpp index c90609f4..338a1f42 100644 --- a/sources/Map/Map.cpp +++ b/sources/Map/Map.cpp @@ -3,6 +3,7 @@ // Edited by Benjamin Henry on 5/26/21. // +#include #include "Map.hpp" namespace RAY3D = RAY::Drawables::Drawables3D; @@ -18,8 +19,8 @@ namespace BBM for (int j = 0; j < height + 1; j++) { if (!(i % 2) && !(j % 2)) { scene->addEntity("Unbreakable Wall") - .addComponent(Vector3f(i, 0, j)) - //.addComponent(1) + .addComponent(i, 0, j) + .addComponent(1) .addComponent>(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj)); } } @@ -33,19 +34,19 @@ namespace BBM scene->addEntity("Bottom Wall") .addComponent(Vector3f((width + 1) / 2, 0, -1)) - //.addComponent(1) + .addComponent(1) .addComponent>(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(width + 3, 1, 1)); scene->addEntity("Upper Wall") .addComponent(Vector3f((width + 1) / 2, 0, height + 1)) - //.addComponent(1) + .addComponent(1) .addComponent>(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(width + 3, 1, 1)); scene->addEntity("Left Wall") .addComponent(Vector3f(width + 1, 0, (height + 1) / 2)) - //.addComponent(1) + .addComponent(1) .addComponent>(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(1, 1, height + 3)); scene->addEntity("Right Wall") .addComponent(Vector3f(-1, 0, (height + 1) / 2)) - //.addComponent(1) + .addComponent(1) .addComponent>(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(1, 1, height + 3)); } @@ -53,7 +54,6 @@ namespace BBM { scene->addEntity("Floor") .addComponent(Vector3f(width / 2, -1, height / 2)) - //.addComponent(1) .addComponent>("assets/wall/floor.obj", std::make_pair(MAP_DIFFUSE, "assets/wall/floor.png"), RAY::Vector3(width + 2, 0, height + 2)); } @@ -81,7 +81,7 @@ namespace BBM scene->addEntity("Breakable Block") .addComponent(coords) .addComponent(1) - //.addComponent(1) + .addComponent(1) .addComponent>("assets/wall/breakable_wall.obj", std::make_pair(MAP_DIFFUSE, "assets/wall/breakable_wall.png")); } @@ -89,7 +89,6 @@ namespace BBM { scene->addEntity("Floor") .addComponent(Vector3f(coords)) - //.addComponent(1) .addComponent>("assets/wall/floor.obj", std::make_pair(MAP_DIFFUSE, "assets/wall/floor.png")); } @@ -97,7 +96,7 @@ namespace BBM { scene->addEntity("Unbreakable Block") .addComponent(coords) - //.addComponent(1) + .addComponent(1) .addComponent>("assets/wall/unbreakable_wall.obj", std::make_pair(MAP_DIFFUSE, "assets/wall/unbreakable_wall.png")); } From ae2e41983275f7008a70d7d38861b56117651f7f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 2 Jun 2021 23:30:39 +0200 Subject: [PATCH 02/22] Creating a templated view class --- lib/wal/CMakeLists.txt | 2 +- lib/wal/sources/Scene/Scene.hpp | 3 +++ lib/wal/sources/View/View.hpp | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 lib/wal/sources/View/View.hpp diff --git a/lib/wal/CMakeLists.txt b/lib/wal/CMakeLists.txt index e8cd8655..ba2a60a0 100644 --- a/lib/wal/CMakeLists.txt +++ b/lib/wal/CMakeLists.txt @@ -17,6 +17,6 @@ add_library(wal sources/Component/Component.cpp sources/System/System.cpp sources/Models/Callback.hpp -) + sources/View/View.hpp) target_include_directories(wal PUBLIC sources) \ No newline at end of file diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp index bddf1e5c..840d2ec5 100644 --- a/lib/wal/sources/Scene/Scene.hpp +++ b/lib/wal/sources/Scene/Scene.hpp @@ -7,6 +7,7 @@ #include #include +#include "View/View.hpp" #include "Entity/Entity.hpp" namespace WAL @@ -17,6 +18,8 @@ namespace WAL private: //! @brief The list of registered entities std::vector _entities = {}; + //! @brief A list of cached views. +// std::vector _views = {}; public: //! @brief Get the list of entities. std::vector &getEntities(); diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp new file mode 100644 index 00000000..9f004188 --- /dev/null +++ b/lib/wal/sources/View/View.hpp @@ -0,0 +1,35 @@ +// +// Created by Zoe Roux on 2021-06-02. +// + + +#pragma once + +#include +#include + +namespace WAL +{ + //! @brief A view caching entities containing requested components + template + class View + { + //! @brief A list of reference to entities that contains the + std::vector> entities; + + explicit View(std::vector &entities) + : entities() + { + std::copy_if(entities.begin(), entities.end(), std::back_inserter(this->entities), [](Entity &entity) { + return (entity.hasComponent() && ...); + }); + } + + //! @brief A default copy constructor. + View(const View &) = default; + //! @brief A default destructor. + ~View() = default; + //! @brief A View is assignable. + View &operator=(const View &) = default; + }; +} \ No newline at end of file From 0d37a560d7a71e905ef166dbede34f57102ba3f0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 3 Jun 2021 18:20:37 +0200 Subject: [PATCH 03/22] Reworking the view --- CMakeLists.txt | 1 + lib/wal/CMakeLists.txt | 2 +- lib/wal/sources/Entity/Entity.cpp | 5 +++- lib/wal/sources/Entity/Entity.hpp | 21 ++++++++++++- lib/wal/sources/Scene/Scene.cpp | 16 ++++++++++ lib/wal/sources/Scene/Scene.hpp | 50 ++++++++++++++++++++++++------- lib/wal/sources/System/System.hpp | 5 +++- lib/wal/sources/View/View.hpp | 35 ---------------------- lib/wal/sources/Wal.cpp | 1 + lib/wal/sources/Wal.hpp | 5 ++-- sources/Runner/Runner.cpp | 8 ++--- tests/CallbackTest.cpp | 4 ++- tests/CollisionTest.cpp | 4 +-- tests/EntityTests.cpp | 10 +++++-- tests/MoveTests.cpp | 3 +- tests/ViewTest.cpp | 33 ++++++++++++++++++++ 16 files changed, 141 insertions(+), 62 deletions(-) delete mode 100644 lib/wal/sources/View/View.hpp create mode 100644 tests/ViewTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f1f860c8..142d675a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ add_executable(unit_tests EXCLUDE_FROM_ALL tests/CallbackTest.cpp tests/CollisionTest.cpp tests/MoveTests.cpp + tests/ViewTest.cpp ) target_include_directories(unit_tests PUBLIC sources) target_link_libraries(unit_tests PUBLIC wal ray) diff --git a/lib/wal/CMakeLists.txt b/lib/wal/CMakeLists.txt index ba2a60a0..e8cd8655 100644 --- a/lib/wal/CMakeLists.txt +++ b/lib/wal/CMakeLists.txt @@ -17,6 +17,6 @@ add_library(wal sources/Component/Component.cpp sources/System/System.cpp sources/Models/Callback.hpp - sources/View/View.hpp) +) target_include_directories(wal PUBLIC sources) \ No newline at end of file diff --git a/lib/wal/sources/Entity/Entity.cpp b/lib/wal/sources/Entity/Entity.cpp index 1dd1a1b3..90e9a41a 100644 --- a/lib/wal/sources/Entity/Entity.cpp +++ b/lib/wal/sources/Entity/Entity.cpp @@ -10,13 +10,15 @@ namespace WAL { unsigned Entity::nextID = 0; - Entity::Entity(std::string name) + Entity::Entity(Scene &scene, std::string name) : _uid(Entity::nextID++), + _scene(scene), _name(std::move(name)) { } Entity::Entity(const Entity &other) : _uid(Entity::nextID++), + _scene(other._scene), _name(other._name), _disabled(other._disabled) { @@ -50,6 +52,7 @@ namespace WAL if (this->hasComponent(type)) throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists."); this->_components.emplace(type, component.clone(*this)); + this->_scene._componentAdded(*this, type); return *this; } diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp index e9fa672e..60bd0090 100644 --- a/lib/wal/sources/Entity/Entity.hpp +++ b/lib/wal/sources/Entity/Entity.hpp @@ -10,9 +10,23 @@ #include #include "Component/Component.hpp" #include "Exception/WalError.hpp" +#include "Wal.hpp" namespace WAL { + + class Scene { + public: + //! @brief Notify this scene that a component has been added to the given entity. + //! @param entity The entity with the new component + //! @param type The type of the component added. + void _componentAdded(const Entity &entity, std::type_index type); + //! @brief Notify this scene that a component has been removed to the given entity. + //! @param entity The entity with the removed component + //! @param type The type of the component removed. namespace WAL + void _componentRemoved(const Entity &entity, std::type_index type); + }; + //! @brief An entity of the WAL's ECS. class Entity { @@ -28,6 +42,9 @@ namespace WAL //! @brief This ID will be the one of the next entity created. static unsigned nextID; + protected: + //! @brief A reference to the ECS. + Scene &_scene; public: //! @brief Get the ID of the entity. unsigned getUid() const; @@ -79,6 +96,7 @@ namespace WAL if (this->hasComponent(type)) throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists."); this->_components[type] = std::make_unique(*this, std::forward(params)...); + this->_scene._componentAdded(*this, type); return *this; } @@ -97,11 +115,12 @@ namespace WAL if (existing == this->_components.end()) throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); this->_components.erase(existing); + this->_scene._componentRemoved(*this, type); return *this; } //! @brief A default constructor - explicit Entity(std::string name); + explicit Entity(Scene &wal, std::string name); //! @brief An entity is copyable Entity(const Entity &); //! @brief An entity is movable. diff --git a/lib/wal/sources/Scene/Scene.cpp b/lib/wal/sources/Scene/Scene.cpp index 3c07f3c8..930382f7 100644 --- a/lib/wal/sources/Scene/Scene.cpp +++ b/lib/wal/sources/Scene/Scene.cpp @@ -10,8 +10,24 @@ namespace WAL { return this->_entities; } + Scene &Scene::operator=(const Scene &) { return *this; } + + Entity &Scene::addEntity(const std::string &name) + { + return this->_entities.emplace_back(*this, name); + } + + void Scene::_componentAdded(const Entity &entity, std::type_index type) + { + + } + + void Scene::_componentRemoved(const Entity &entity, std::type_index type) + { + + } } // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp index 840d2ec5..4ccf9316 100644 --- a/lib/wal/sources/Scene/Scene.hpp +++ b/lib/wal/sources/Scene/Scene.hpp @@ -3,11 +3,11 @@ // -#pragma once +#ifndef WAL_SCENE +#define WAL_SCENE #include #include -#include "View/View.hpp" #include "Entity/Entity.hpp" namespace WAL @@ -18,20 +18,44 @@ namespace WAL private: //! @brief The list of registered entities std::vector _entities = {}; - //! @brief A list of cached views. -// std::vector _views = {}; + + //! @brief Notify this scene that a component has been added to the given entity. + //! @param entity The entity with the new component + //! @param type The type of the component added. + void _componentAdded(const Entity &entity, std::type_index type); + //! @brief Notify this scene that a component has been removed to the given entity. + //! @param entity The entity with the removed component + //! @param type The type of the component removed. + void _componentRemoved(const Entity &entity, std::type_index type); public: //! @brief Get the list of entities. std::vector &getEntities(); - //! @brief Add a new entity to the scene, you can use this method with the same arguments as the entity's constructor. - //! @return The current scene is returned to allow you to chain call. - template - Entity &addEntity(Params &&...params) + //! @brief Add a new entity to the scene. + //! @param name The name of the created entity. + //! @return The created entity is returned. + Entity &addEntity(const std::string &name); + + template + std::vector> &view() { - return this->_entities.emplace_back(std::forward(params)...); + return this->view(typeid(Components)...); } +#pragma clang diagnostic push +#pragma ide diagnostic ignored "NotImplementedFunctions" + template + std::vector> &view(const Components &...index) requires(std::is_same_v) + { + static std::vector> view; + + std::copy_if(this->_entities.begin(), this->_entities.end(), std::back_inserter(view), [&index...](Entity &entity) { + return (entity.hasComponent(index) && ...); + }); + return view; + } +#pragma clang diagnostic pop + //! @brief A default constructor Scene() = default; //! @brief A scene is copy constructable @@ -41,5 +65,11 @@ namespace WAL //! @brief A scene is assignable Scene &operator=(const Scene &); Scene(Scene &&) = default; + + friend Entity; }; -} // namespace WAL \ No newline at end of file +} // namespace WAL + +#else + +#endif \ No newline at end of file diff --git a/lib/wal/sources/System/System.hpp b/lib/wal/sources/System/System.hpp index 59c82c4a..c3e5ee83 100644 --- a/lib/wal/sources/System/System.hpp +++ b/lib/wal/sources/System/System.hpp @@ -6,10 +6,13 @@ #include #include -#include "Entity/Entity.hpp" +#include +#include namespace WAL { + class Entity; + //! @brief A base system of WAL class System { diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp deleted file mode 100644 index 9f004188..00000000 --- a/lib/wal/sources/View/View.hpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Created by Zoe Roux on 2021-06-02. -// - - -#pragma once - -#include -#include - -namespace WAL -{ - //! @brief A view caching entities containing requested components - template - class View - { - //! @brief A list of reference to entities that contains the - std::vector> entities; - - explicit View(std::vector &entities) - : entities() - { - std::copy_if(entities.begin(), entities.end(), std::back_inserter(this->entities), [](Entity &entity) { - return (entity.hasComponent() && ...); - }); - } - - //! @brief A default copy constructor. - View(const View &) = default; - //! @brief A default destructor. - ~View() = default; - //! @brief A View is assignable. - View &operator=(const View &) = default; - }; -} \ No newline at end of file diff --git a/lib/wal/sources/Wal.cpp b/lib/wal/sources/Wal.cpp index 875e3bd8..d4262b73 100644 --- a/lib/wal/sources/Wal.cpp +++ b/lib/wal/sources/Wal.cpp @@ -5,6 +5,7 @@ #include #include #include "Wal.hpp" +#include "Scene/Scene.hpp" namespace WAL { diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index 50a87188..a16e4f5c 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -10,13 +10,14 @@ #include #include #include "Exception/WalError.hpp" -#include "Scene/Scene.hpp" -#include "Entity/Entity.hpp" #include "System/System.hpp" #include "Models/Callback.hpp" namespace WAL { + class Entity; + class Scene; + //! @brief The main WAL class, it is used to setup and run the ECS. class Wal { diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index db3896c9..7e03bfef 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -64,9 +64,9 @@ namespace BBM wal.addSystem(window); } - std::shared_ptr loadGameScene() + std::shared_ptr loadGameScene(WAL::Wal &wal) { - auto scene = std::make_shared(); + auto scene = std::make_shared(wal); scene->addEntity("player") .addComponent() .addComponent>("assets/player/player.iqm", std::make_pair(MAP_DIFFUSE, "assets/player/blue.png")) @@ -90,7 +90,7 @@ namespace BBM scene->addEntity("camera") .addComponent(8, 20, 7) .addComponent(Vector3f(8, 0, 8)); - std::srand(std::time(NULL)); + std::srand(std::time(nullptr)); MapGenerator::loadMap(16, 16, MapGenerator::createMap(16, 16), scene); return scene; } @@ -100,7 +100,7 @@ namespace BBM WAL::Wal wal; addSystems(wal); enableRaylib(wal); - wal.scene = loadGameScene(); + wal.scene = loadGameScene(wal); try { wal.run(updateState); diff --git a/tests/CallbackTest.cpp b/tests/CallbackTest.cpp index 534ca9f4..44b3a103 100644 --- a/tests/CallbackTest.cpp +++ b/tests/CallbackTest.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "Entity/Entity.hpp" #include "Models/Callback.hpp" @@ -36,6 +37,7 @@ TEST_CASE("Callback multiple arguments", "[Callback]") callback.addCallback([](const std::string& str, int a, unsigned *value, Entity &entity) { throw std::runtime_error(""); }); - Entity entity("name"); + Wal wal; + Entity entity(wal, "name"); REQUIRE_THROWS_AS(callback("1", 0, nullptr, entity), std::runtime_error); } diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp index 69c75207..42b7d186 100644 --- a/tests/CollisionTest.cpp +++ b/tests/CollisionTest.cpp @@ -21,7 +21,7 @@ TEST_CASE("Collision test", "[Component][System]") { Wal wal; CollisionSystem collision(wal); - wal.scene = std::shared_ptr(new Scene); + wal.scene = std::make_shared(wal); wal.scene->addEntity("player") .addComponent() .addComponent([](Entity &actual, const Entity &) { @@ -65,7 +65,7 @@ TEST_CASE("Collsion test with movable", "[Component][System]") Wal wal; CollisionSystem collision(wal); MovableSystem movable; - wal.scene = std::shared_ptr(new Scene); + wal.scene = std::make_shared(wal); wal.scene->addEntity("player") .addComponent() .addComponent([](Entity &actual, const Entity &) {}, [](Entity &actual, const Entity &) {}, 5.0) diff --git a/tests/EntityTests.cpp b/tests/EntityTests.cpp index f58fd0c1..7a0a3490 100644 --- a/tests/EntityTests.cpp +++ b/tests/EntityTests.cpp @@ -5,13 +5,15 @@ #include "Entity/Entity.hpp" #include "Component/Position/PositionComponent.hpp" #include +#include using namespace WAL; using namespace BBM; TEST_CASE("Component", "[Entity]") { - Entity entity("Bob"); + Wal wal; + Entity entity(wal, "Bob"); entity.addComponent(2, 3, 4); SECTION("Check value") { @@ -31,13 +33,15 @@ TEST_CASE("Component", "[Entity]") TEST_CASE("ComponentNotFound", "[Entity]") { - Entity entity("Bob"); + Wal wal; + Entity entity(wal, "Bob"); REQUIRE_THROWS_AS(entity.getComponent(), NotFoundError); } TEST_CASE("Add component by reference", "[Entity]") { - Entity entity("Bob"); + Wal wal; + Entity entity(wal, "Bob"); PositionComponent component(entity, 4, 5, 6); REQUIRE(&entity.addComponent(component) == &entity); diff --git a/tests/MoveTests.cpp b/tests/MoveTests.cpp index cd37d22a..0a47bd10 100644 --- a/tests/MoveTests.cpp +++ b/tests/MoveTests.cpp @@ -19,7 +19,8 @@ using namespace BBM; TEST_CASE("Move test", "[Component][System]") { - Scene scene; + Wal wal; + Scene scene(wal); scene.addEntity("player") .addComponent() .addComponent() diff --git a/tests/ViewTest.cpp b/tests/ViewTest.cpp new file mode 100644 index 00000000..90328acd --- /dev/null +++ b/tests/ViewTest.cpp @@ -0,0 +1,33 @@ +// +// Created by Zoe Roux on 6/3/21. +// + +#include "Entity/Entity.hpp" +#include "Component/Position/PositionComponent.hpp" +#include "System/Movable/MovableSystem.hpp" +#include "System/Controllable/ControllableSystem.hpp" +#include +#include +#include + +#define private public +#include + +using namespace WAL; +using namespace BBM; + +TEST_CASE("View creation", "[View]") +{ + Wal wal; + Scene scene(wal); + scene.addEntity("player") + .addComponent() + .addComponent(); + scene.addEntity("Box") + .addComponent(); + REQUIRE(scene.view().size() == 2); + REQUIRE(scene.view().size() == 1); + Entity &entity = *scene.getEntities().begin(); + Entity &firstView = *scene.view().begin(); + REQUIRE(&entity == &firstView); +} \ No newline at end of file From a11bd21ec308c7fc2b528dfb4e5724246fd1037a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 3 Jun 2021 22:42:38 +0200 Subject: [PATCH 04/22] Creating a basic view --- lib/wal/CMakeLists.txt | 2 +- lib/wal/sources/Entity/Entity.cpp | 11 +++++ lib/wal/sources/Entity/Entity.hpp | 25 ++++------ lib/wal/sources/Scene/Scene.cpp | 23 +++++++-- lib/wal/sources/Scene/Scene.hpp | 36 +++++--------- lib/wal/sources/System/System.hpp | 3 +- lib/wal/sources/View/BaseView.hpp | 50 ++++++++++++++++++++ lib/wal/sources/Wal.hpp | 1 + sources/Runner/Runner.cpp | 2 +- sources/System/Collision/CollisionSystem.cpp | 1 + tests/CallbackTest.cpp | 5 +- tests/CollisionTest.cpp | 4 +- tests/EntityTests.cpp | 13 ++--- tests/MoveTests.cpp | 3 +- tests/ViewTest.cpp | 5 +- 15 files changed, 121 insertions(+), 63 deletions(-) create mode 100644 lib/wal/sources/View/BaseView.hpp diff --git a/lib/wal/CMakeLists.txt b/lib/wal/CMakeLists.txt index e8cd8655..088188b4 100644 --- a/lib/wal/CMakeLists.txt +++ b/lib/wal/CMakeLists.txt @@ -17,6 +17,6 @@ add_library(wal sources/Component/Component.cpp sources/System/System.cpp sources/Models/Callback.hpp -) + sources/View/BaseView.hpp) target_include_directories(wal PUBLIC sources) \ No newline at end of file diff --git a/lib/wal/sources/Entity/Entity.cpp b/lib/wal/sources/Entity/Entity.cpp index 90e9a41a..35fa2c53 100644 --- a/lib/wal/sources/Entity/Entity.cpp +++ b/lib/wal/sources/Entity/Entity.cpp @@ -3,6 +3,7 @@ // #include "Entity/Entity.hpp" +#include "Scene/Scene.hpp" #include #include @@ -65,4 +66,14 @@ namespace WAL { return this->_components.contains(type); } + + void Entity::_componentAdded(const std::type_index &type) + { + this->_scene._componentAdded(*this, type); + } + + void Entity::_componentRemoved(const std::type_index &type) + { + this->_scene._componentRemoved(*this, type); + } } // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp index 60bd0090..b7e46dc2 100644 --- a/lib/wal/sources/Entity/Entity.hpp +++ b/lib/wal/sources/Entity/Entity.hpp @@ -10,22 +10,10 @@ #include #include "Component/Component.hpp" #include "Exception/WalError.hpp" -#include "Wal.hpp" namespace WAL { - - class Scene { - public: - //! @brief Notify this scene that a component has been added to the given entity. - //! @param entity The entity with the new component - //! @param type The type of the component added. - void _componentAdded(const Entity &entity, std::type_index type); - //! @brief Notify this scene that a component has been removed to the given entity. - //! @param entity The entity with the removed component - //! @param type The type of the component removed. namespace WAL - void _componentRemoved(const Entity &entity, std::type_index type); - }; + class Scene; //! @brief An entity of the WAL's ECS. class Entity @@ -42,6 +30,13 @@ namespace WAL //! @brief This ID will be the one of the next entity created. static unsigned nextID; + + //! @brief Callback called when a component is added + //! @param type The type of component + void _componentAdded(const std::type_index &type); + //! @brief Callback called when a component is removed + //! @param type The type of component + void _componentRemoved(const std::type_index &type); protected: //! @brief A reference to the ECS. Scene &_scene; @@ -96,7 +91,7 @@ namespace WAL if (this->hasComponent(type)) throw DuplicateError("A component of the type \"" + std::string(type.name()) + "\" already exists."); this->_components[type] = std::make_unique(*this, std::forward(params)...); - this->_scene._componentAdded(*this, type); + this->_componentAdded(type); return *this; } @@ -115,7 +110,7 @@ namespace WAL if (existing == this->_components.end()) throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); this->_components.erase(existing); - this->_scene._componentRemoved(*this, type); + this->_componentRemoved(type); return *this; } diff --git a/lib/wal/sources/Scene/Scene.cpp b/lib/wal/sources/Scene/Scene.cpp index 930382f7..db5c9d6a 100644 --- a/lib/wal/sources/Scene/Scene.cpp +++ b/lib/wal/sources/Scene/Scene.cpp @@ -21,13 +21,28 @@ namespace WAL return this->_entities.emplace_back(*this, name); } - void Scene::_componentAdded(const Entity &entity, std::type_index type) + void Scene::_componentAdded(Entity &entity, const std::type_index &type) { - + for (auto &view : this->_views) { + if (std::find(view->types.begin(), view->types.end(), type) == view->types.end()) + continue; + bool valid = std::all_of(view->types.begin(), view->types.end(), [&entity](const auto &type){ + return entity.hasComponent(type); + }); + if (valid) + view->entities.emplace_back(entity); + } } - void Scene::_componentRemoved(const Entity &entity, std::type_index type) + void Scene::_componentRemoved(const Entity &entity, const std::type_index &type) { - + for (auto &view : this->_views) { + if (std::find(view->types.begin(), view->types.end(), type) == view->types.end()) + continue; + view->entities.erase(std::remove_if(view->entities.begin(), view->entities.end(), [&entity](const auto &ref) + { + return &ref.get() == &entity; + }), view->entities.end()); + } } } // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp index 4ccf9316..e233debc 100644 --- a/lib/wal/sources/Scene/Scene.hpp +++ b/lib/wal/sources/Scene/Scene.hpp @@ -3,11 +3,11 @@ // -#ifndef WAL_SCENE -#define WAL_SCENE +#pragma once #include #include +#include #include "Entity/Entity.hpp" namespace WAL @@ -18,15 +18,17 @@ namespace WAL private: //! @brief The list of registered entities std::vector _entities = {}; + //! @brief The list of cached views to update. + std::vector> _views = {}; //! @brief Notify this scene that a component has been added to the given entity. //! @param entity The entity with the new component //! @param type The type of the component added. - void _componentAdded(const Entity &entity, std::type_index type); + void _componentAdded(Entity &entity, const std::type_index &type); //! @brief Notify this scene that a component has been removed to the given entity. //! @param entity The entity with the removed component //! @param type The type of the component removed. - void _componentRemoved(const Entity &entity, std::type_index type); + void _componentRemoved(const Entity &entity, const std::type_index &type); public: //! @brief Get the list of entities. std::vector &getEntities(); @@ -37,25 +39,13 @@ namespace WAL Entity &addEntity(const std::string &name); template - std::vector> &view() + View &view() { - return this->view(typeid(Components)...); + static auto view = std::make_shared>(this->_entities); + this->_views.emplace_back(view); + return *view; } -#pragma clang diagnostic push -#pragma ide diagnostic ignored "NotImplementedFunctions" - template - std::vector> &view(const Components &...index) requires(std::is_same_v) - { - static std::vector> view; - - std::copy_if(this->_entities.begin(), this->_entities.end(), std::back_inserter(view), [&index...](Entity &entity) { - return (entity.hasComponent(index) && ...); - }); - return view; - } -#pragma clang diagnostic pop - //! @brief A default constructor Scene() = default; //! @brief A scene is copy constructable @@ -68,8 +58,4 @@ namespace WAL friend Entity; }; -} // namespace WAL - -#else - -#endif \ No newline at end of file +} // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/System/System.hpp b/lib/wal/sources/System/System.hpp index c3e5ee83..8dc164a7 100644 --- a/lib/wal/sources/System/System.hpp +++ b/lib/wal/sources/System/System.hpp @@ -8,11 +8,10 @@ #include #include #include +#include "Entity/Entity.hpp" namespace WAL { - class Entity; - //! @brief A base system of WAL class System { diff --git a/lib/wal/sources/View/BaseView.hpp b/lib/wal/sources/View/BaseView.hpp new file mode 100644 index 00000000..4a8502d4 --- /dev/null +++ b/lib/wal/sources/View/BaseView.hpp @@ -0,0 +1,50 @@ +// +// Created by Zoe Roux on 2021-06-03. +// + + +#pragma once + +#include +#include +#include +#include +#include "Entity/Entity.hpp" + +namespace WAL +{ + class BaseView + { + public: + std::vector> entities = {}; + + std::vector types = {}; + + size_t size() + { + return entities.size(); + } + +// std::vector> &view(const Components &...index) requires(std::is_same_v) +// { +// static std::vector> view; +// +// std::copy_if(this->_entities.begin(), this->_entities.end(), std::back_inserter(view), [&index...](Entity &entity) { +// return (entity.hasComponent(index) && ...); +// }); +// return view; +// } + }; + + template + class View : public BaseView + { + public: + explicit View(std::vector &scene) + { + std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { + return (entity.hasComponent() && ...); + }); + } + }; +} \ No newline at end of file diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index a16e4f5c..7e361904 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -12,6 +12,7 @@ #include "Exception/WalError.hpp" #include "System/System.hpp" #include "Models/Callback.hpp" +#include "Scene/Scene.hpp" namespace WAL { diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index 7e03bfef..c6270366 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -66,7 +66,7 @@ namespace BBM std::shared_ptr loadGameScene(WAL::Wal &wal) { - auto scene = std::make_shared(wal); + auto scene = std::make_shared(); scene->addEntity("player") .addComponent() .addComponent>("assets/player/player.iqm", std::make_pair(MAP_DIFFUSE, "assets/player/blue.png")) diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index 24971304..6bb58d51 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -6,6 +6,7 @@ #include "Component/Position/PositionComponent.hpp" #include "Component/Collision/CollisionComponent.hpp" #include "System/Collision/CollisionSystem.hpp" +#include "Scene/Scene.hpp" namespace BBM { diff --git a/tests/CallbackTest.cpp b/tests/CallbackTest.cpp index 44b3a103..30d67e76 100644 --- a/tests/CallbackTest.cpp +++ b/tests/CallbackTest.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Entity/Entity.hpp" #include "Models/Callback.hpp" @@ -37,7 +38,7 @@ TEST_CASE("Callback multiple arguments", "[Callback]") callback.addCallback([](const std::string& str, int a, unsigned *value, Entity &entity) { throw std::runtime_error(""); }); - Wal wal; - Entity entity(wal, "name"); + Scene scene; + Entity entity(scene, "name"); REQUIRE_THROWS_AS(callback("1", 0, nullptr, entity), std::runtime_error); } diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp index 42b7d186..447a8dbb 100644 --- a/tests/CollisionTest.cpp +++ b/tests/CollisionTest.cpp @@ -21,7 +21,7 @@ TEST_CASE("Collision test", "[Component][System]") { Wal wal; CollisionSystem collision(wal); - wal.scene = std::make_shared(wal); + wal.scene = std::make_shared(); wal.scene->addEntity("player") .addComponent() .addComponent([](Entity &actual, const Entity &) { @@ -65,7 +65,7 @@ TEST_CASE("Collsion test with movable", "[Component][System]") Wal wal; CollisionSystem collision(wal); MovableSystem movable; - wal.scene = std::make_shared(wal); + wal.scene = std::make_shared(); wal.scene->addEntity("player") .addComponent() .addComponent([](Entity &actual, const Entity &) {}, [](Entity &actual, const Entity &) {}, 5.0) diff --git a/tests/EntityTests.cpp b/tests/EntityTests.cpp index 7a0a3490..3520f862 100644 --- a/tests/EntityTests.cpp +++ b/tests/EntityTests.cpp @@ -6,14 +6,15 @@ #include "Component/Position/PositionComponent.hpp" #include #include +#include using namespace WAL; using namespace BBM; TEST_CASE("Component", "[Entity]") { - Wal wal; - Entity entity(wal, "Bob"); + Scene scene; + Entity entity(scene, "Bob"); entity.addComponent(2, 3, 4); SECTION("Check value") { @@ -33,15 +34,15 @@ TEST_CASE("Component", "[Entity]") TEST_CASE("ComponentNotFound", "[Entity]") { - Wal wal; - Entity entity(wal, "Bob"); + Scene scene; + Entity entity(scene, "Bob"); REQUIRE_THROWS_AS(entity.getComponent(), NotFoundError); } TEST_CASE("Add component by reference", "[Entity]") { - Wal wal; - Entity entity(wal, "Bob"); + Scene scene; + Entity entity(scene, "Bob"); PositionComponent component(entity, 4, 5, 6); REQUIRE(&entity.addComponent(component) == &entity); diff --git a/tests/MoveTests.cpp b/tests/MoveTests.cpp index 0a47bd10..cd37d22a 100644 --- a/tests/MoveTests.cpp +++ b/tests/MoveTests.cpp @@ -19,8 +19,7 @@ using namespace BBM; TEST_CASE("Move test", "[Component][System]") { - Wal wal; - Scene scene(wal); + Scene scene; scene.addEntity("player") .addComponent() .addComponent() diff --git a/tests/ViewTest.cpp b/tests/ViewTest.cpp index 90328acd..21315f2b 100644 --- a/tests/ViewTest.cpp +++ b/tests/ViewTest.cpp @@ -18,8 +18,7 @@ using namespace BBM; TEST_CASE("View creation", "[View]") { - Wal wal; - Scene scene(wal); + Scene scene; scene.addEntity("player") .addComponent() .addComponent(); @@ -28,6 +27,6 @@ TEST_CASE("View creation", "[View]") REQUIRE(scene.view().size() == 2); REQUIRE(scene.view().size() == 1); Entity &entity = *scene.getEntities().begin(); - Entity &firstView = *scene.view().begin(); + Entity &firstView = *scene.view().entities.begin(); REQUIRE(&entity == &firstView); } \ No newline at end of file From 4f14d380bff2031d4766354e284a33017e774485 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 3 Jun 2021 23:13:22 +0200 Subject: [PATCH 05/22] Adding some tests --- lib/wal/CMakeLists.txt | 2 +- lib/wal/sources/Scene/Scene.hpp | 2 +- lib/wal/sources/View/BaseView.hpp | 50 ------------------------ lib/wal/sources/View/View.hpp | 65 +++++++++++++++++++++++++++++++ tests/ViewTest.cpp | 48 ++++++++++++++++++++--- 5 files changed, 109 insertions(+), 58 deletions(-) delete mode 100644 lib/wal/sources/View/BaseView.hpp create mode 100644 lib/wal/sources/View/View.hpp diff --git a/lib/wal/CMakeLists.txt b/lib/wal/CMakeLists.txt index 088188b4..ba2a60a0 100644 --- a/lib/wal/CMakeLists.txt +++ b/lib/wal/CMakeLists.txt @@ -17,6 +17,6 @@ add_library(wal sources/Component/Component.cpp sources/System/System.cpp sources/Models/Callback.hpp - sources/View/BaseView.hpp) + sources/View/View.hpp) target_include_directories(wal PUBLIC sources) \ No newline at end of file diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp index e233debc..c3b000b6 100644 --- a/lib/wal/sources/Scene/Scene.hpp +++ b/lib/wal/sources/Scene/Scene.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include "Entity/Entity.hpp" namespace WAL diff --git a/lib/wal/sources/View/BaseView.hpp b/lib/wal/sources/View/BaseView.hpp deleted file mode 100644 index 4a8502d4..00000000 --- a/lib/wal/sources/View/BaseView.hpp +++ /dev/null @@ -1,50 +0,0 @@ -// -// Created by Zoe Roux on 2021-06-03. -// - - -#pragma once - -#include -#include -#include -#include -#include "Entity/Entity.hpp" - -namespace WAL -{ - class BaseView - { - public: - std::vector> entities = {}; - - std::vector types = {}; - - size_t size() - { - return entities.size(); - } - -// std::vector> &view(const Components &...index) requires(std::is_same_v) -// { -// static std::vector> view; -// -// std::copy_if(this->_entities.begin(), this->_entities.end(), std::back_inserter(view), [&index...](Entity &entity) { -// return (entity.hasComponent(index) && ...); -// }); -// return view; -// } - }; - - template - class View : public BaseView - { - public: - explicit View(std::vector &scene) - { - std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { - return (entity.hasComponent() && ...); - }); - } - }; -} \ No newline at end of file diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp new file mode 100644 index 00000000..9df63f55 --- /dev/null +++ b/lib/wal/sources/View/View.hpp @@ -0,0 +1,65 @@ +// +// Created by Zoe Roux on 2021-06-03. +// + + +#pragma once + +#include +#include +#include +#include +#include "Entity/Entity.hpp" + +namespace WAL +{ + //! @brief A basic view used to manipulate view without knowing their type at compile time. + class BaseView + { + public: + //! @brief The list of entities in the view. + std::vector> entities = {}; + + //! @brief The list of types that every entity of the view has. + std::vector types = {}; + + size_t size() + { + return entities.size(); + } + + //! @brief A default destructor + ~BaseView() = default; + protected: + //! @brief A basic view can't be constructed, you should use the View templated class. + BaseView() = default; + //! @brief A basic view can't be constructed, you should use the View templated class. + BaseView(const BaseView &) = default; + //! @brief A basic view can't be assigned. See the View template class. + BaseView &operator=(const BaseView &) = default; + }; + + //! @brief A view allowing one to easily access entities containing a set list of component. + //! A view is always updated and only references to entities are kept. + template + class View : public BaseView + { + public: + //! @brief Construct a view from a list of entities. + //! Those entities are never copied but references to them are kept internally. + explicit View(std::vector &scene) + { + this->types = {typeid(Components)...}; + std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { + return (entity.hasComponent() && ...); + }); + } + + //! @brief Copying a view is not possible since a view must be managed by a scene. + View(const View &) = delete; + //! @brief A default destructor + ~View() = default; + //! @brief A view is not assignable. + View &operator=(const View &) = delete; + }; +} \ No newline at end of file diff --git a/tests/ViewTest.cpp b/tests/ViewTest.cpp index 21315f2b..8e0c1aef 100644 --- a/tests/ViewTest.cpp +++ b/tests/ViewTest.cpp @@ -4,15 +4,10 @@ #include "Entity/Entity.hpp" #include "Component/Position/PositionComponent.hpp" -#include "System/Movable/MovableSystem.hpp" -#include "System/Controllable/ControllableSystem.hpp" #include #include #include -#define private public -#include - using namespace WAL; using namespace BBM; @@ -29,4 +24,45 @@ TEST_CASE("View creation", "[View]") Entity &entity = *scene.getEntities().begin(); Entity &firstView = *scene.view().entities.begin(); REQUIRE(&entity == &firstView); -} \ No newline at end of file +} + +TEST_CASE("View update", "[View]") +{ + Scene scene; + scene.addEntity("player") + .addComponent() + .addComponent(); + auto &view = scene.view(); + auto &entity = scene.addEntity("Box") + .addComponent(); + REQUIRE(view.size() == 2); + entity.removeComponent(); + REQUIRE(view.size() == 1); +} + +TEST_CASE("View cache", "[View]") +{ + Scene scene; + scene.addEntity("player") + .addComponent() + .addComponent(); + auto &view = scene.view(); + REQUIRE(&view == &scene.view()); +} + +//TEST_CASE("View iteration", "[View]") +//{ +// Scene scene; +// scene.addEntity("player") +// .addComponent() +// .addComponent(); +// scene.addEntity("Box") +// .addComponent(); +// int i = 0; +// for (auto &entity : scene.view()) { +// if (i == 0) +// REQUIRE(entity.getName() == "player"); +// else +// REQUIRE(entity.getName() == "Box"); +// } +//} \ No newline at end of file From 306f07972da2466130dd13e6639fe5e783be1cf7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 4 Jun 2021 00:50:38 +0200 Subject: [PATCH 06/22] Reworking systems --- lib/wal/CMakeLists.txt | 4 +- lib/wal/sources/System/ISystem.hpp | 37 ++++++++++++++ lib/wal/sources/System/System.cpp | 28 ----------- lib/wal/sources/System/System.hpp | 34 ++++++++----- lib/wal/sources/View/View.hpp | 2 +- lib/wal/sources/Wal.cpp | 49 ------------------- lib/wal/sources/Wal.hpp | 32 ++++++------ sources/System/Collision/CollisionSystem.cpp | 6 +-- sources/System/Collision/CollisionSystem.hpp | 11 ++--- .../Controllable/ControllableSystem.cpp | 7 +-- .../Controllable/ControllableSystem.hpp | 10 ++-- sources/System/Gamepad/GamepadSystem.cpp | 7 +-- sources/System/Gamepad/GamepadSystem.hpp | 10 ++-- .../GridCentered/GridCenteredSystem.cpp | 8 +-- .../GridCentered/GridCenteredSystem.hpp | 9 ++-- sources/System/Health/HealthSystem.cpp | 6 +-- sources/System/Health/HealthSystem.hpp | 9 ++-- sources/System/Keyboard/KeyboardSystem.cpp | 7 +-- sources/System/Keyboard/KeyboardSystem.hpp | 10 ++-- sources/System/Movable/MovableSystem.cpp | 7 +-- sources/System/Movable/MovableSystem.hpp | 9 ++-- tests/CollisionTest.cpp | 4 +- 22 files changed, 129 insertions(+), 177 deletions(-) create mode 100644 lib/wal/sources/System/ISystem.hpp delete mode 100644 lib/wal/sources/System/System.cpp delete mode 100644 lib/wal/sources/Wal.cpp diff --git a/lib/wal/CMakeLists.txt b/lib/wal/CMakeLists.txt index ba2a60a0..c0aac922 100644 --- a/lib/wal/CMakeLists.txt +++ b/lib/wal/CMakeLists.txt @@ -7,7 +7,6 @@ add_library(wal sources/Entity/Entity.hpp sources/Component/Component.hpp sources/System/System.hpp - sources/Wal.cpp sources/Wal.hpp sources/Scene/Scene.cpp sources/Scene/Scene.hpp @@ -15,8 +14,7 @@ add_library(wal sources/Exception/WalError.hpp sources/Entity/Entity.cpp sources/Component/Component.cpp - sources/System/System.cpp sources/Models/Callback.hpp - sources/View/View.hpp) + sources/View/View.hpp sources/System/ISystem.hpp) target_include_directories(wal PUBLIC sources) \ No newline at end of file diff --git a/lib/wal/sources/System/ISystem.hpp b/lib/wal/sources/System/ISystem.hpp new file mode 100644 index 00000000..be755d65 --- /dev/null +++ b/lib/wal/sources/System/ISystem.hpp @@ -0,0 +1,37 @@ +// +// Created by Zoe Roux on 2021-06-04. +// + + +#pragma once + +#include "Entity/Entity.hpp" +#include "View/View.hpp" +#include + +namespace WAL +{ + //! @brief A base class that represent a system. + class ISystem + { + public: + //! @brief Update the corresponding component of the given entity + //! @param entity The entity to update. + //! @param dtime The delta time. + virtual void onUpdate(Entity &entity, std::chrono::nanoseconds dtime) = 0; + + //! @brief An alternative of onUpdate that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. + //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. + //! @param entity The entity to update. + virtual void onFixedUpdate(Entity &entity) = 0; + + //! @brief A method called after all entities that this system manage has been updated. + virtual void onSelfUpdate() = 0; + + //! @brief Get a view containing every entity this system should update. + virtual BaseView &getView() = 0; + + //! @brief A virtual default destructor. + virtual ~ISystem() = default; + }; +} \ No newline at end of file diff --git a/lib/wal/sources/System/System.cpp b/lib/wal/sources/System/System.cpp deleted file mode 100644 index 0dd69f30..00000000 --- a/lib/wal/sources/System/System.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by Zoe Roux on 5/17/21. -// - -#include "System.hpp" -#include -#include - -namespace WAL -{ - System::System(std::vector dependencies) - : _dependencies(std::move(dependencies)) - {} - - void System::onUpdate(Entity &entity, std::chrono::nanoseconds dtime) - {} - - void System::onFixedUpdate(Entity &entity) - {} - - void System::onSelfUpdate() - {} - - const std::vector &System::getDependencies() const - { - return this->_dependencies; - } -} // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/System/System.hpp b/lib/wal/sources/System/System.hpp index 8dc164a7..56a5404d 100644 --- a/lib/wal/sources/System/System.hpp +++ b/lib/wal/sources/System/System.hpp @@ -9,39 +9,49 @@ #include #include #include "Entity/Entity.hpp" +#include "Wal.hpp" +#include "View/View.hpp" +#include "ISystem.hpp" namespace WAL { //! @brief A base system of WAL - class System + //! @tparam Dependencies The list of dependencies this system has. + template + class System : public ISystem { - private: - //! @brief The list of dependencies of this system - std::vector _dependencies = {}; public: //! @brief A virtual, default, destructor - virtual ~System() = default; + ~System() override = default; //! @brief A system can be moved - System(System &&) = default; + System(System &&) noexcept = default; - //! @brief Get the name of the component corresponding to this system. - const std::vector &getDependencies() const; + //! @brief Get a view of all entities containing every dependencies of this system. + View &getView() override + { + return this->_wal.scene->template view(); + } //! @brief Update the corresponding component of the given entity //! @param entity The entity to update. //! @param dtime The delta time. - virtual void onUpdate(Entity &entity, std::chrono::nanoseconds dtime); + void onUpdate(Entity &entity, std::chrono::nanoseconds dtime) override {} //! @brief An alternative of onUpdate that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. //! @param entity The entity to update. - virtual void onFixedUpdate(Entity &entity); + void onFixedUpdate(Entity &entity) override {} //! @brief A method called after all entities that this system manage has been updated. - virtual void onSelfUpdate(); + void onSelfUpdate() override {} protected: + //! @brief A reference to the ECS. + Wal &_wal; + //! @brief A system can't be instantiated, it should be derived. - explicit System(std::vector dependencies); + explicit System(Wal &wal) + : _wal(wal) + {} //! @brief A system can't be instantiated, it should be derived. System(const System &) = default; //! @brief A system can't be instantiated, it should be derived. diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp index 9df63f55..2db9e2ff 100644 --- a/lib/wal/sources/View/View.hpp +++ b/lib/wal/sources/View/View.hpp @@ -23,7 +23,7 @@ namespace WAL //! @brief The list of types that every entity of the view has. std::vector types = {}; - size_t size() + size_t size() const { return entities.size(); } diff --git a/lib/wal/sources/Wal.cpp b/lib/wal/sources/Wal.cpp deleted file mode 100644 index d4262b73..00000000 --- a/lib/wal/sources/Wal.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Created by Zoe Roux on 2021-05-14. -// - -#include -#include -#include "Wal.hpp" -#include "Scene/Scene.hpp" - -namespace WAL -{ - std::chrono::nanoseconds Wal::timestep = std::chrono::milliseconds(8); - - void Wal::_update(std::chrono::nanoseconds dtime) - { - auto &entities = this->scene->getEntities(); - - for (auto &system : this->_systems) { - for (auto &entity : entities) { - if (!Wal::_hasDependencies(entity, *system)) - continue; - system->onUpdate(entity, dtime); - } - system->onSelfUpdate(); - } - } - - void Wal::_fixedUpdate() - { - auto &entities = this->scene->getEntities(); - - for (auto &system : this->_systems) { - for (auto &entity : entities) { - if (!Wal::_hasDependencies(entity, *system)) - continue; - system->onFixedUpdate(entity); - } - } - } - - bool Wal::_hasDependencies(const Entity &entity, const System &system) - { - // TODO use an hashmap to cache results. - const auto &dependency = system.getDependencies(); - return std::ranges::all_of(dependency.begin(), dependency.end(), [&entity](const auto &dependency) { - return entity.hasComponent(dependency); - }); - } -} // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index 7e361904..4bb9500a 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -10,7 +10,7 @@ #include #include #include "Exception/WalError.hpp" -#include "System/System.hpp" +#include "System/ISystem.hpp" #include "Models/Callback.hpp" #include "Scene/Scene.hpp" @@ -19,31 +19,22 @@ namespace WAL class Entity; class Scene; + template + class System; + //! @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 Call the onUpdate of every system with every component - void _update(std::chrono::nanoseconds dtime); - - //! @brief Call the onFixedUpdate of every system with every component - void _fixedUpdate(); - - //! @brief Check if an entity met a system's dependencies. - //! @param entity The entity to check - //! @param system The system that will list dependencies - //! @return True if all dependencies are met, false otherwise. - static bool _hasDependencies(const Entity &entity, const System &system); + std::vector> _systems = {}; 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 std::chrono::nanoseconds timestep; + 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. @@ -132,9 +123,16 @@ namespace WAL while (fBehind > Wal::timestep) { fBehind -= Wal::timestep; - this->_fixedUpdate(); + for (auto &system : this->_systems) { + for (auto &entity : system->getView().entities) + system->onFixedUpdate(entity); + } + } + for (auto &system : this->_systems) { + for (auto &entity : system->getView().entities) + system->onUpdate(entity, dtime); + system->onSelfUpdate(); } - this->_update(dtime); callback(*this, state); } } diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index 6bb58d51..7561b984 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -11,10 +11,8 @@ namespace BBM { CollisionSystem::CollisionSystem(WAL::Wal &wal) - : WAL::System({typeid(PositionComponent), typeid(CollisionComponent)}), _wal(wal) - { - - } + : System(wal) + { } bool CollisionSystem::collide(Vector3f minA, Vector3f maxA, Vector3f minB, Vector3f maxB) { diff --git a/sources/System/Collision/CollisionSystem.hpp b/sources/System/Collision/CollisionSystem.hpp index c4c2de25..4e3961d9 100644 --- a/sources/System/Collision/CollisionSystem.hpp +++ b/sources/System/Collision/CollisionSystem.hpp @@ -12,23 +12,20 @@ namespace BBM { //! @brief A system to handle collisions. - class CollisionSystem : public WAL::System + class CollisionSystem : public WAL::System { - private: - //! @brief reference to the ECS engine to get other entities - WAL::Wal &_wal; public: //! @inherit void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - CollisionSystem(WAL::Wal &wal); + explicit CollisionSystem(WAL::Wal &wal); //! @brief A Collision system is copy constructable CollisionSystem(const CollisionSystem &) = default; //! @brief A default destructor ~CollisionSystem() override = default; - //! @brief A Collision system is assignable. - CollisionSystem &operator=(const CollisionSystem &) = default; + //! @brief A system is not assignable. + CollisionSystem &operator=(const CollisionSystem &) = delete; //! @brief check AABB collision static bool collide(Vector3f minA, Vector3f maxA, Vector3f minB, Vector3f maxB); diff --git a/sources/System/Controllable/ControllableSystem.cpp b/sources/System/Controllable/ControllableSystem.cpp index 0a9c789c..941319cf 100644 --- a/sources/System/Controllable/ControllableSystem.cpp +++ b/sources/System/Controllable/ControllableSystem.cpp @@ -12,11 +12,8 @@ namespace BBM { float ControllableSystem::speed = .25f; - ControllableSystem::ControllableSystem() - : WAL::System({ - typeid(ControllableComponent), - typeid(MovableComponent) - }) + ControllableSystem::ControllableSystem(WAL::Wal &wal) + : System(wal) {} void ControllableSystem::onFixedUpdate(WAL::Entity &entity) diff --git a/sources/System/Controllable/ControllableSystem.hpp b/sources/System/Controllable/ControllableSystem.hpp index fac4db08..45e7513a 100644 --- a/sources/System/Controllable/ControllableSystem.hpp +++ b/sources/System/Controllable/ControllableSystem.hpp @@ -5,12 +5,14 @@ #pragma once +#include "Component/Movable/MovableComponent.hpp" +#include "Component/Controllable/ControllableComponent.hpp" #include "System/System.hpp" namespace BBM { //! @brief A system to handle Controllable entities. - class ControllableSystem : public WAL::System + class ControllableSystem : public WAL::System { public: //! @brief The speed applied to every controllable entities. @@ -20,12 +22,12 @@ namespace BBM void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - ControllableSystem(); + explicit ControllableSystem(WAL::Wal &wal); //! @brief A Controllable system is copy constructable ControllableSystem(const ControllableSystem &) = default; //! @brief A default destructor ~ControllableSystem() override = default; - //! @brief A Controllable system is assignable. - ControllableSystem &operator=(const ControllableSystem &) = default; + //! @brief A system is not assignable. + ControllableSystem &operator=(const ControllableSystem &) = delete; }; } diff --git a/sources/System/Gamepad/GamepadSystem.cpp b/sources/System/Gamepad/GamepadSystem.cpp index 5df6bd96..9392dc02 100644 --- a/sources/System/Gamepad/GamepadSystem.cpp +++ b/sources/System/Gamepad/GamepadSystem.cpp @@ -13,11 +13,8 @@ using Gamepad = RAY::Controller::GamePad; namespace BBM { - GamepadSystem::GamepadSystem() - : WAL::System({ - typeid(GamepadComponent), - typeid(ControllableComponent) - }) + GamepadSystem::GamepadSystem(WAL::Wal &wal) + : System(wal) {} void GamepadSystem::onFixedUpdate(WAL::Entity &entity) diff --git a/sources/System/Gamepad/GamepadSystem.hpp b/sources/System/Gamepad/GamepadSystem.hpp index 9c8c705f..8daa5e06 100644 --- a/sources/System/Gamepad/GamepadSystem.hpp +++ b/sources/System/Gamepad/GamepadSystem.hpp @@ -6,23 +6,25 @@ #include "System/System.hpp" #include +#include "Component/Gamepad/GamepadComponent.hpp" +#include "Component/Controllable/ControllableComponent.hpp" namespace BBM { //! @brief A system to handle Gamepad entities. - class GamepadSystem : public WAL::System + class GamepadSystem : public WAL::System { public: //! @inherit void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - GamepadSystem(); + explicit GamepadSystem(WAL::Wal &wal); //! @brief A Gamepad system is copy constructable GamepadSystem(const GamepadSystem &) = default; //! @brief A default destructor ~GamepadSystem() override = default; - //! @brief A Gamepad system is assignable. - GamepadSystem &operator=(const GamepadSystem &) = default; + //! @brief A system is not assignable. + GamepadSystem &operator=(const GamepadSystem &) = delete; }; } diff --git a/sources/System/GridCentered/GridCenteredSystem.cpp b/sources/System/GridCentered/GridCenteredSystem.cpp index 5dcca1a9..d6337c3a 100644 --- a/sources/System/GridCentered/GridCenteredSystem.cpp +++ b/sources/System/GridCentered/GridCenteredSystem.cpp @@ -8,12 +8,8 @@ namespace BBM { - GridCenteredSystem::GridCenteredSystem() - : WAL::System({ - typeid(GridCenteredComponent), - typeid(MovableComponent), -// typeid(PositionComponent) - }) + GridCenteredSystem::GridCenteredSystem(WAL::Wal &wal) + : System(wal) {} void GridCenteredSystem::onFixedUpdate(WAL::Entity &entity) diff --git a/sources/System/GridCentered/GridCenteredSystem.hpp b/sources/System/GridCentered/GridCenteredSystem.hpp index e84e65fb..3af49ea1 100644 --- a/sources/System/GridCentered/GridCenteredSystem.hpp +++ b/sources/System/GridCentered/GridCenteredSystem.hpp @@ -5,22 +5,23 @@ #pragma once #include +#include "Component/Position/PositionComponent.hpp" namespace BBM { //! @brief The system handling GridCenteredComponent - class GridCenteredSystem : public WAL::System + class GridCenteredSystem : public WAL::System { public: void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - GridCenteredSystem(); + explicit GridCenteredSystem(WAL::Wal &wal); //! @brief A GridCenteredSystem is copyable. GridCenteredSystem(const GridCenteredSystem &) = default; //! @brief A default destructor ~GridCenteredSystem() override = default; - //! @brief A GridCenteredSystem is assignable - GridCenteredSystem &operator=(const GridCenteredSystem &) = default; + //! @brief A system is not assignable + GridCenteredSystem &operator=(const GridCenteredSystem &) = delete; }; } diff --git a/sources/System/Health/HealthSystem.cpp b/sources/System/Health/HealthSystem.cpp index 90bbfbb9..e9aaa155 100644 --- a/sources/System/Health/HealthSystem.cpp +++ b/sources/System/Health/HealthSystem.cpp @@ -10,10 +10,8 @@ namespace BBM { - HealthSystem::HealthSystem() - : WAL::System({ - typeid(HealthComponent) - }) + HealthSystem::HealthSystem(WAL::Wal &wal) + : System(wal) {} void HealthSystem::onFixedUpdate(WAL::Entity &entity) diff --git a/sources/System/Health/HealthSystem.hpp b/sources/System/Health/HealthSystem.hpp index 15ca2062..fe8bb1ab 100644 --- a/sources/System/Health/HealthSystem.hpp +++ b/sources/System/Health/HealthSystem.hpp @@ -5,24 +5,25 @@ #pragma once +#include "Component/Health/HealthComponent.hpp" #include "System/System.hpp" namespace BBM { //! @brief A system to handle Health entities. - class HealthSystem : public WAL::System + class HealthSystem : public WAL::System { public: //! @inherit void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - HealthSystem(); + explicit HealthSystem(WAL::Wal &wal); //! @brief A Health system is copy constructable HealthSystem(const HealthSystem &) = default; //! @brief A default destructor ~HealthSystem() override = default; - //! @brief A Health system is assignable. - HealthSystem &operator=(const HealthSystem &) = default; + //! @brief A system is not assignable. + HealthSystem &operator=(const HealthSystem &) = delete; }; } diff --git a/sources/System/Keyboard/KeyboardSystem.cpp b/sources/System/Keyboard/KeyboardSystem.cpp index 13ad9804..044373b4 100644 --- a/sources/System/Keyboard/KeyboardSystem.cpp +++ b/sources/System/Keyboard/KeyboardSystem.cpp @@ -14,11 +14,8 @@ using Keyboard = RAY::Controller::Keyboard; namespace BBM { - KeyboardSystem::KeyboardSystem() - : WAL::System({ - typeid(KeyboardComponent), - typeid(ControllableComponent) - }) + KeyboardSystem::KeyboardSystem(WAL::Wal &wal) + : System(wal) {} void KeyboardSystem::onFixedUpdate(WAL::Entity &entity) diff --git a/sources/System/Keyboard/KeyboardSystem.hpp b/sources/System/Keyboard/KeyboardSystem.hpp index f17c5f15..56977386 100644 --- a/sources/System/Keyboard/KeyboardSystem.hpp +++ b/sources/System/Keyboard/KeyboardSystem.hpp @@ -7,23 +7,25 @@ #include "System/System.hpp" #include +#include "Component/Keyboard/KeyboardComponent.hpp" +#include "Component/Controllable/ControllableComponent.hpp" namespace BBM { //! @brief A system to handle keyboard entities. - class KeyboardSystem : public WAL::System + class KeyboardSystem : public WAL::System { public: //! @inherit void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - KeyboardSystem(); + explicit KeyboardSystem(WAL::Wal &wal); //! @brief A keyboard system is copy constructable KeyboardSystem(const KeyboardSystem &) = default; //! @brief A default destructor ~KeyboardSystem() override = default; - //! @brief A keyboard system is assignable. - KeyboardSystem &operator=(const KeyboardSystem &) = default; + //! @brief A system is not assignable. + KeyboardSystem &operator=(const KeyboardSystem &) = delete; }; } diff --git a/sources/System/Movable/MovableSystem.cpp b/sources/System/Movable/MovableSystem.cpp index 1c439345..c6d86457 100644 --- a/sources/System/Movable/MovableSystem.cpp +++ b/sources/System/Movable/MovableSystem.cpp @@ -8,11 +8,8 @@ namespace BBM { - MovableSystem::MovableSystem() - : WAL::System({ - typeid(MovableComponent), - typeid(PositionComponent) - }) + MovableSystem::MovableSystem(WAL::Wal &wal) + : System(wal) {} void MovableSystem::onFixedUpdate(WAL::Entity &entity) diff --git a/sources/System/Movable/MovableSystem.hpp b/sources/System/Movable/MovableSystem.hpp index 51a56ea9..173bc5a8 100644 --- a/sources/System/Movable/MovableSystem.hpp +++ b/sources/System/Movable/MovableSystem.hpp @@ -5,25 +5,26 @@ #pragma once +#include "Component/Movable/MovableComponent.hpp" #include "System/System.hpp" #include "Entity/Entity.hpp" namespace BBM { //! @brief A system to handle movable entities. This system update velocity based on accelerations and positions based on velocity. - class MovableSystem : public WAL::System + class MovableSystem : public WAL::System { public: //! @inherit void onFixedUpdate(WAL::Entity &entity) override; //! @brief A default constructor - MovableSystem(); + explicit MovableSystem(WAL::Wal &wal); //! @brief A movable system is copy constructable MovableSystem(const MovableSystem &) = default; //! @brief A default destructor ~MovableSystem() override = default; - //! @brief A movable system is assignable. - MovableSystem &operator=(const MovableSystem &) = default; + //! @brief A system is not assignable. + MovableSystem &operator=(const MovableSystem &) = delete; }; } // namespace WAL diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp index 447a8dbb..f869bb3e 100644 --- a/tests/CollisionTest.cpp +++ b/tests/CollisionTest.cpp @@ -20,7 +20,7 @@ using namespace BBM; TEST_CASE("Collision test", "[Component][System]") { Wal wal; - CollisionSystem collision(wal); + CollisionSystem collision(<#initializer#>, wal); wal.scene = std::make_shared(); wal.scene->addEntity("player") .addComponent() @@ -63,7 +63,7 @@ TEST_CASE("Collision test", "[Component][System]") TEST_CASE("Collsion test with movable", "[Component][System]") { Wal wal; - CollisionSystem collision(wal); + CollisionSystem collision(<#initializer#>, wal); MovableSystem movable; wal.scene = std::make_shared(); wal.scene->addEntity("player") From 0bd22502eefe79ba51dd62aed2569ad08d87e3d1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 4 Jun 2021 01:22:55 +0200 Subject: [PATCH 07/22] Cleaning up --- lib/wal/sources/Wal.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index d5630c8d..2456296a 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -21,10 +21,6 @@ namespace WAL { class Entity; - class Scene; - - template - class System; //! @brief The main WAL class, it is used to setup and run the ECS. class Wal From 97add1b6d99d1d4108c9463b1a8a6edf668c44a1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 4 Jun 2021 12:56:26 +0200 Subject: [PATCH 08/22] Fixing view caches --- lib/wal/sources/Scene/Scene.cpp | 4 +++- lib/wal/sources/Scene/Scene.hpp | 16 +++++++++++++--- lib/wal/sources/View/View.hpp | 4 ++-- tests/CollisionTest.cpp | 6 +++--- tests/MoveTests.cpp | 2 +- tests/ViewTest.cpp | 16 ++++++++++++++++ 6 files changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/wal/sources/Scene/Scene.cpp b/lib/wal/sources/Scene/Scene.cpp index db5c9d6a..fe9fbb19 100644 --- a/lib/wal/sources/Scene/Scene.cpp +++ b/lib/wal/sources/Scene/Scene.cpp @@ -6,7 +6,9 @@ namespace WAL { - std::vector &Scene::getEntities() + int Scene::_nextID = 0; + + std::list &Scene::getEntities() { return this->_entities; } diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp index c3b000b6..be5e160b 100644 --- a/lib/wal/sources/Scene/Scene.hpp +++ b/lib/wal/sources/Scene/Scene.hpp @@ -6,6 +6,7 @@ #pragma once #include +#include #include #include #include "Entity/Entity.hpp" @@ -16,8 +17,12 @@ namespace WAL class Scene { private: + static int _nextID; + //! @brief An ID representing this scene. + int _id = _nextID++; + //! @brief The list of registered entities - std::vector _entities = {}; + std::list _entities = {}; //! @brief The list of cached views to update. std::vector> _views = {}; @@ -31,7 +36,7 @@ namespace WAL void _componentRemoved(const Entity &entity, const std::type_index &type); public: //! @brief Get the list of entities. - std::vector &getEntities(); + std::list &getEntities(); //! @brief Add a new entity to the scene. //! @param name The name of the created entity. @@ -41,8 +46,13 @@ namespace WAL template View &view() { - static auto view = std::make_shared>(this->_entities); + static std::unordered_map>> cache; + auto existing = cache.find(this->_id); + if (existing != cache.end() && !existing->second.expired()) + return *existing->second.lock(); + auto view = std::make_shared>(this->_entities); this->_views.emplace_back(view); + cache.emplace(this->_id, view); return *view; } diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp index 2db9e2ff..52a46294 100644 --- a/lib/wal/sources/View/View.hpp +++ b/lib/wal/sources/View/View.hpp @@ -5,7 +5,7 @@ #pragma once -#include +#include #include #include #include @@ -47,7 +47,7 @@ namespace WAL public: //! @brief Construct a view from a list of entities. //! Those entities are never copied but references to them are kept internally. - explicit View(std::vector &scene) + explicit View(std::list &scene) { this->types = {typeid(Components)...}; std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp index d0ed5f78..77188df1 100644 --- a/tests/CollisionTest.cpp +++ b/tests/CollisionTest.cpp @@ -32,7 +32,7 @@ TEST_CASE("Collision test", "[Component][System]") pos.position.z = 1; } catch (std::exception &e) {}; }, [](Entity &, const Entity &){}, 5.0); - Entity &entity = wal.scene->getEntities()[0]; + Entity &entity = wal.scene->getEntities().front(); REQUIRE(entity.getComponent().position == Vector3f()); entity.getComponent().bound.x = 5; @@ -48,7 +48,7 @@ TEST_CASE("Collision test", "[Component][System]") wal.scene->addEntity("block") .addComponent(2,2,2) .addComponent(1); - Entity &player = wal.scene->getEntities()[0]; + Entity &player = wal.scene->getEntities().front(); collision.onUpdate(entity, std::chrono::nanoseconds(1)); REQUIRE(player.hasComponent(typeid(PositionComponent))); collision.onFixedUpdate(player); @@ -79,7 +79,7 @@ TEST_CASE("Collsion test with movable", "[Component][System]") mov.resetVelocity(); } catch (std::exception &e) {}; }, 1); - Entity &entity = wal.scene->getEntities()[0]; + Entity &entity = wal.scene->getEntities().front(); REQUIRE(entity.getComponent().position == Vector3f()); entity.getComponent().bound.x = 5; diff --git a/tests/MoveTests.cpp b/tests/MoveTests.cpp index ba46a55d..ac035c86 100644 --- a/tests/MoveTests.cpp +++ b/tests/MoveTests.cpp @@ -25,7 +25,7 @@ TEST_CASE("Move test", "[Component][System]") .addComponent() .addComponent() .addComponent(); - Entity &entity = scene.getEntities()[0]; + Entity &entity = *scene.getEntities().begin(); REQUIRE(entity.getComponent().position == Vector3f()); diff --git a/tests/ViewTest.cpp b/tests/ViewTest.cpp index 8e0c1aef..194e04a6 100644 --- a/tests/ViewTest.cpp +++ b/tests/ViewTest.cpp @@ -50,6 +50,22 @@ TEST_CASE("View cache", "[View]") REQUIRE(&view == &scene.view()); } +TEST_CASE("View cache switch", "[View]") +{ + Scene scene; + scene.addEntity("player") + .addComponent() + .addComponent(); + auto &view = scene.view(); + Scene scene2; + scene2.addEntity("box") + .addComponent(); + + REQUIRE(&view == &scene.view()); + REQUIRE(view.entities.begin()->get().getName() == "player"); + REQUIRE(scene2.view().entities.begin()->get().getName() == "box"); +} + //TEST_CASE("View iteration", "[View]") //{ // Scene scene; From 181ccb7934d02082cda3212497b2161215e37b9f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 4 Jun 2021 13:02:27 +0200 Subject: [PATCH 09/22] Using views for the collision system --- sources/System/Collision/CollisionSystem.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index 7561b984..de4ef82b 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -32,12 +32,9 @@ namespace BBM position += entity.getComponent().getVelocity(); Vector3f minA = Vector3f::min(position, position + col.bound); Vector3f maxA = Vector3f::max(position, position + col.bound); - for (auto &other : _wal.scene->getEntities()) { + for (WAL::Entity &other : _wal.scene->view().entities) { if (&other == &entity) continue; - if (!other.hasComponent() || - !other.hasComponent()) - continue; auto colB = other.getComponent(); auto posB = other.getComponent().position; Vector3f minB = Vector3f::min(posB, posB + colB.bound); From eac5c2c847a210d1418da44812c76b1825b3b60b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 4 Jun 2021 13:16:14 +0200 Subject: [PATCH 10/22] Oups --- sources/System/Collision/CollisionSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index de4ef82b..4c91fc45 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -32,7 +32,7 @@ namespace BBM position += entity.getComponent().getVelocity(); Vector3f minA = Vector3f::min(position, position + col.bound); Vector3f maxA = Vector3f::max(position, position + col.bound); - for (WAL::Entity &other : _wal.scene->view().entities) { + for (WAL::Entity &other : this->getView().entities) { if (&other == &entity) continue; auto colB = other.getComponent(); From a40b61845a7bc94d065aaddcae4608b9312ceaaf Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 4 Jun 2021 18:59:42 +0200 Subject: [PATCH 11/22] Adding an iterator --- CMakeLists.txt | 36 ++--- lib/wal/sources/Entity/Entity.hpp | 10 ++ lib/wal/sources/Scene/Scene.cpp | 13 +- lib/wal/sources/Scene/Scene.hpp | 2 +- lib/wal/sources/System/ISystem.hpp | 17 +-- lib/wal/sources/System/System.hpp | 24 ++- lib/wal/sources/View/View.hpp | 150 ++++++++++++++++--- lib/wal/sources/Wal.hpp | 13 +- sources/Map/Map.hpp | 1 - sources/Runner/Runner.cpp | 31 ++-- sources/System/Collision/CollisionSystem.cpp | 18 +-- sources/System/Collision/CollisionSystem.hpp | 3 +- 12 files changed, 220 insertions(+), 98 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fd96ef8e..70d6a98e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,34 +35,34 @@ set(SOURCES sources/Component/Keyboard/KeyboardComponent.hpp sources/Component/Health/HealthComponent.cpp sources/Component/Health/HealthComponent.hpp - sources/System/Movable/MovableSystem.hpp - sources/System/Movable/MovableSystem.cpp - sources/System/Controllable/ControllableSystem.cpp - sources/System/Controllable/ControllableSystem.hpp - sources/System/Gamepad/GamepadSystem.cpp - sources/System/Gamepad/GamepadSystem.hpp - sources/System/Health/HealthSystem.cpp - sources/System/Health/HealthSystem.hpp - sources/System/Keyboard/KeyboardSystem.cpp - sources/System/Keyboard/KeyboardSystem.hpp - sources/System/Movable/MovableSystem.cpp - sources/System/Movable/MovableSystem.hpp +# sources/System/Movable/MovableSystem.hpp +# sources/System/Movable/MovableSystem.cpp +# sources/System/Controllable/ControllableSystem.cpp +# sources/System/Controllable/ControllableSystem.hpp +# sources/System/Gamepad/GamepadSystem.cpp +# sources/System/Gamepad/GamepadSystem.hpp +# sources/System/Health/HealthSystem.cpp +# sources/System/Health/HealthSystem.hpp +# sources/System/Keyboard/KeyboardSystem.cpp +# sources/System/Keyboard/KeyboardSystem.hpp +# sources/System/Movable/MovableSystem.cpp +# sources/System/Movable/MovableSystem.hpp sources/Models/Vector3.hpp sources/Component/GridCentered/GridCenteredComponent.cpp sources/Component/GridCentered/GridCenteredComponent.hpp - sources/System/GridCentered/GridCenteredSystem.cpp - sources/System/GridCentered/GridCenteredSystem.hpp +# sources/System/GridCentered/GridCenteredSystem.cpp +# sources/System/GridCentered/GridCenteredSystem.hpp sources/Models/Vector2.hpp sources/Component/Renderer/Drawable2DComponent.hpp sources/Component/Renderer/Drawable3DComponent.hpp - sources/System/Renderer/RenderSystem.hpp - sources/System/Renderer/RenderSystem.cpp +# sources/System/Renderer/RenderSystem.hpp +# sources/System/Renderer/RenderSystem.cpp sources/Component/Renderer/CameraComponent.cpp sources/Component/Renderer/CameraComponent.hpp sources/Component/Animation/AnimationsComponent.cpp sources/Component/Animation/AnimationsComponent.hpp - sources/System/Animation/AnimationsSystem.cpp - sources/System/Animation/AnimationsSystem.hpp +# sources/System/Animation/AnimationsSystem.cpp +# sources/System/Animation/AnimationsSystem.hpp sources/Component/Collision/CollisionComponent.cpp sources/Component/Collision/CollisionComponent.hpp sources/System/Collision/CollisionSystem.hpp diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp index d8948f87..c4a4e9cf 100644 --- a/lib/wal/sources/Entity/Entity.hpp +++ b/lib/wal/sources/Entity/Entity.hpp @@ -65,6 +65,16 @@ namespace WAL return *static_cast(existing->second.get()); } + template + T *getComponentOrDefault() + { + const std::type_index &type = typeid(T); + auto existing = this->_components.find(type); + if (existing == this->_components.end()) + return nullptr; + return *static_cast(existing->second.get()); + } + //! @brief Check if this entity has a component. //! @tparam T The type of the component template diff --git a/lib/wal/sources/Scene/Scene.cpp b/lib/wal/sources/Scene/Scene.cpp index fe9fbb19..22ed03ff 100644 --- a/lib/wal/sources/Scene/Scene.cpp +++ b/lib/wal/sources/Scene/Scene.cpp @@ -26,25 +26,22 @@ namespace WAL void Scene::_componentAdded(Entity &entity, const std::type_index &type) { for (auto &view : this->_views) { - if (std::find(view->types.begin(), view->types.end(), type) == view->types.end()) + if (std::find(view->getTypes().begin(), view->getTypes().end(), type) == view->getTypes().end()) continue; - bool valid = std::all_of(view->types.begin(), view->types.end(), [&entity](const auto &type){ + bool valid = std::all_of(view->getTypes().begin(), view->getTypes().end(), [&entity](const auto &type){ return entity.hasComponent(type); }); if (valid) - view->entities.emplace_back(entity); + view->emplace_back(entity); } } void Scene::_componentRemoved(const Entity &entity, const std::type_index &type) { for (auto &view : this->_views) { - if (std::find(view->types.begin(), view->types.end(), type) == view->types.end()) + if (std::find(view->getTypes().begin(), view->getTypes().end(), type) == view->getTypes().end()) continue; - view->entities.erase(std::remove_if(view->entities.begin(), view->entities.end(), [&entity](const auto &ref) - { - return &ref.get() == &entity; - }), view->entities.end()); + view->erase(entity); } } } // namespace WAL \ No newline at end of file diff --git a/lib/wal/sources/Scene/Scene.hpp b/lib/wal/sources/Scene/Scene.hpp index be5e160b..aab47566 100644 --- a/lib/wal/sources/Scene/Scene.hpp +++ b/lib/wal/sources/Scene/Scene.hpp @@ -24,7 +24,7 @@ namespace WAL //! @brief The list of registered entities std::list _entities = {}; //! @brief The list of cached views to update. - std::vector> _views = {}; + std::vector> _views = {}; //! @brief Notify this scene that a component has been added to the given entity. //! @param entity The entity with the new component diff --git a/lib/wal/sources/System/ISystem.hpp b/lib/wal/sources/System/ISystem.hpp index be755d65..ab8dc3f3 100644 --- a/lib/wal/sources/System/ISystem.hpp +++ b/lib/wal/sources/System/ISystem.hpp @@ -15,21 +15,16 @@ namespace WAL class ISystem { public: - //! @brief Update the corresponding component of the given entity - //! @param entity The entity to update. - //! @param dtime The delta time. - virtual void onUpdate(Entity &entity, std::chrono::nanoseconds dtime) = 0; + //! @brief Update the whole system (every entities that this system is responsible can be updated. + //! @param dtime The delta time since the last call to this method. + virtual void update(std::chrono::nanoseconds dtime) = 0; - //! @brief An alternative of onUpdate that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. + //! @brief An alternative of update that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. - //! @param entity The entity to update. - virtual void onFixedUpdate(Entity &entity) = 0; - - //! @brief A method called after all entities that this system manage has been updated. - virtual void onSelfUpdate() = 0; + virtual void fixedUpdate() = 0; //! @brief Get a view containing every entity this system should update. - virtual BaseView &getView() = 0; + virtual IView &getView() = 0; //! @brief A virtual default destructor. virtual ~ISystem() = default; diff --git a/lib/wal/sources/System/System.hpp b/lib/wal/sources/System/System.hpp index 56a5404d..c21de9d2 100644 --- a/lib/wal/sources/System/System.hpp +++ b/lib/wal/sources/System/System.hpp @@ -35,15 +35,33 @@ namespace WAL //! @brief Update the corresponding component of the given entity //! @param entity The entity to update. //! @param dtime The delta time. - void onUpdate(Entity &entity, std::chrono::nanoseconds dtime) override {} + virtual void onUpdate(ViewEntity &entity, std::chrono::nanoseconds dtime) {} //! @brief An alternative of onUpdate that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. //! @param entity The entity to update. - void onFixedUpdate(Entity &entity) override {} + virtual void onFixedUpdate(ViewEntity &entity) {} //! @brief A method called after all entities that this system manage has been updated. - void onSelfUpdate() override {} + virtual void onSelfUpdate() {} + + + //! @brief Update the whole system (every entities that this system is responsible can be updated. + //! @param dtime The delta time since the last call to this method. + void update(std::chrono::nanoseconds dtime) final + { + for (auto entity : this->getView()) + this->onUpdate(entity, dtime); + this->onSelfUpdate(); + } + + //! @brief An alternative of update that is called every 8ms (120 times per seconds). If the system slow down, it will try to catch up. + //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. + void fixedUpdate() final + { + for (auto entity : this->getView()) + this->onFixedUpdate(entity); + } protected: //! @brief A reference to the ECS. Wal &_wal; diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp index 52a46294..22c02b60 100644 --- a/lib/wal/sources/View/View.hpp +++ b/lib/wal/sources/View/View.hpp @@ -13,46 +13,152 @@ namespace WAL { - //! @brief A basic view used to manipulate view without knowing their type at compile time. - class BaseView + template + class ViewEntity { + private: + std::tuple, std::reference_wrapper...> _value; public: - //! @brief The list of entities in the view. - std::vector> entities = {}; + explicit ViewEntity(std::tuple, std::reference_wrapper...> value) + : _value(value) + {} - //! @brief The list of types that every entity of the view has. - std::vector types = {}; - - size_t size() const + Entity *operator->() { - return entities.size(); + return &(std::get<0>(this->_value).get()); } + Entity &operator*() + { + return std::get<0>(this->_value); + } + + operator Entity &() + { + return std::get<0>(this->_value); + } + + template + T &get() + { + return std::get>(this->_value); + } + }; + + template + class ViewIterator + { + private: + It _it; + + public: + ViewEntity operator*() + { + ViewEntity entity(*this->_it); + return entity; + } + + ViewEntity operator->() + { + ViewEntity entity(*this->_it); + return entity; + } + + ViewIterator &operator++() + { + this->_it++; + return *this; + } + + ViewIterator operator++(int) + { + ViewIterator copy = *this; + this->_it++; + return *this; + } + + bool operator==(const ViewIterator &other) const + { + return this->_it == other._it; + } + + bool operator!=(const ViewIterator &other) const + { + return !this->operator==(other); + } + + explicit ViewIterator(It current) + : _it(current) + {} + }; + + //! @brief A basic view used to manipulate view without knowing their type at compile time. + class IView + { + public: + //! @brief The list of types that every entity of the view has. + virtual const std::vector &getTypes() const = 0; + + virtual void emplace_back(Entity &) = 0; + + virtual void erase(const Entity &) = 0; + //! @brief A default destructor - ~BaseView() = default; - protected: - //! @brief A basic view can't be constructed, you should use the View templated class. - BaseView() = default; - //! @brief A basic view can't be constructed, you should use the View templated class. - BaseView(const BaseView &) = default; - //! @brief A basic view can't be assigned. See the View template class. - BaseView &operator=(const BaseView &) = default; + virtual ~IView() = default; }; //! @brief A view allowing one to easily access entities containing a set list of component. //! A view is always updated and only references to entities are kept. template - class View : public BaseView + class View : public IView { + private: + using entity_type = std::tuple, std::reference_wrapper...>; + + //! @brief The list of entities in the view. + std::vector _entities = {}; + //! @brief The list of types that every entity of the view has. + std::vector _types = {}; public: + using iterator = ViewIterator::iterator, Components...>; + + iterator begin() + { + return iterator(this->_entities.begin()); + } + + iterator end() + { + return iterator(this->_entities.begin()); + } + + const std::vector &getTypes() const override + { + return this->_types; + } + + void emplace_back(Entity &) override + { + + } + + void erase(const Entity &) override + { + + } + //! @brief Construct a view from a list of entities. //! Those entities are never copied but references to them are kept internally. explicit View(std::list &scene) { - this->types = {typeid(Components)...}; - std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { - return (entity.hasComponent() && ...); - }); + this->_types = {typeid(Components)...}; + for (auto &entity : scene) { + auto tuple = std::make_tuple(entity, entity.getComponentOrDefault()...); + std::apply(&this->_entities.emplace_back, tuple); + } + // std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { +// return (entity.hasComponent() && ...); +// }); } //! @brief Copying a view is not possible since a view must be managed by a scene. diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index 2456296a..ef138871 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -129,16 +129,11 @@ namespace WAL while (fBehind > Wal::timestep) { fBehind -= Wal::timestep; - for (auto &system : this->_systems) { - for (auto &entity : system->getView().entities) - system->onFixedUpdate(entity); - } - } - for (auto &system : this->_systems) { - for (auto &entity : system->getView().entities) - system->onUpdate(entity, dtime); - system->onSelfUpdate(); + for (auto &system : this->_systems) + system->fixedUpdate(); } + for (auto &system : this->_systems) + system->update(dtime); callback(*this, state); } } diff --git a/sources/Map/Map.hpp b/sources/Map/Map.hpp index 177d836f..7f396b31 100644 --- a/sources/Map/Map.hpp +++ b/sources/Map/Map.hpp @@ -14,7 +14,6 @@ #include #include #include "Component/Renderer/Drawable3DComponent.hpp" -#include "System/Renderer/RenderSystem.hpp" #include "Scene/Scene.hpp" #include "Model/Model.hpp" #include "Component/Component.hpp" diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index b64a61c1..ff933fff 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -4,30 +4,30 @@ #include #include -#include "System/Movable/MovableSystem.hpp" -#include "System/Renderer/RenderSystem.hpp" +//#include "System/Movable/MovableSystem.hpp" +//#include "System/Renderer/RenderSystem.hpp" #include #include #include -#include +//#include #include -#include -#include +//#include +//#include #include #include #include #include #include -#include -#include "Models/Vector2.hpp" +//#include +//#include "Models/Vector2.hpp" #include "Component/Renderer/CameraComponent.hpp" -#include "Component/Renderer/Drawable2DComponent.hpp" +//#include "Component/Renderer/Drawable2DComponent.hpp" #include "Component/Renderer/Drawable3DComponent.hpp" #include "Runner.hpp" #include "Models/GameState.hpp" #include #include "Component/Animation/AnimationsComponent.hpp" -#include "System/Animation/AnimationsSystem.hpp" +//#include "System/Animation/AnimationsSystem.hpp" #include "Map/Map.hpp" namespace RAY2D = RAY::Drawables::Drawables2D; @@ -47,18 +47,19 @@ namespace BBM void addSystems(WAL::Wal &wal) { - wal.addSystem() - .addSystem() - .addSystem() - .addSystem() - .addSystem(); + wal.addSystem(); + // wal.addSystem() +// .addSystem() +// .addSystem() +// .addSystem() +// .addSystem(); } void enableRaylib(WAL::Wal &wal) { RAY::TraceLog::setLevel(LOG_WARNING); RAY::Window &window = RAY::Window::getInstance(600, 400, "Bomberman", FLAG_WINDOW_RESIZABLE); - wal.addSystem(window); +// wal.addSystem(window); } std::shared_ptr loadGameScene(WAL::Wal &wal) diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index 4c91fc45..79351729 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -23,20 +23,20 @@ namespace BBM return (overlapX && overlapY && overlapZ); } - void CollisionSystem::onFixedUpdate(WAL::Entity &entity) + void CollisionSystem::onFixedUpdate(WAL::ViewEntity &entity) { - auto &posA = entity.getComponent(); - auto &col = entity.getComponent(); + auto &posA = entity.get(); + auto &col = entity.get(); Vector3f position = posA.position; - if (entity.hasComponent(typeid(MovableComponent))) - position += entity.getComponent().getVelocity(); +// if (entity.hasComponent(typeid(MovableComponent))) +// position += entity.getComponent().getVelocity(); Vector3f minA = Vector3f::min(position, position + col.bound); Vector3f maxA = Vector3f::max(position, position + col.bound); - for (WAL::Entity &other : this->getView().entities) { - if (&other == &entity) + for (auto other : this->getView()) { + if (other->getUid() == entity->getUid()) continue; - auto colB = other.getComponent(); - auto posB = other.getComponent().position; + auto colB = other.get(); + auto posB = other.get().position; Vector3f minB = Vector3f::min(posB, posB + colB.bound); Vector3f maxB = Vector3f::max(posB, posB + colB.bound); if (collide(minA, maxA, minB, maxB)) { diff --git a/sources/System/Collision/CollisionSystem.hpp b/sources/System/Collision/CollisionSystem.hpp index 45ee9816..2c0f5491 100644 --- a/sources/System/Collision/CollisionSystem.hpp +++ b/sources/System/Collision/CollisionSystem.hpp @@ -10,6 +10,7 @@ #include "System/System.hpp" #include "Models/Vector3.hpp" #include "Component/Collision/CollisionComponent.hpp" +#include "Component/Position/PositionComponent.hpp" namespace BBM { @@ -18,7 +19,7 @@ namespace BBM { public: //! @inherit - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit CollisionSystem(WAL::Wal &wal); From acb3935c7c6ee98584d0f4a8a62e9c9b6649bd84 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Jun 2021 16:42:53 +0200 Subject: [PATCH 12/22] Finishing views --- lib/wal/sources/Entity/Entity.hpp | 18 +++++++++++------- lib/wal/sources/View/View.hpp | 13 +++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp index c4a4e9cf..50770d18 100644 --- a/lib/wal/sources/Entity/Entity.hpp +++ b/lib/wal/sources/Entity/Entity.hpp @@ -54,25 +54,29 @@ namespace WAL void setDisable(bool disabled); //! @brief Get a component of a specific type + //! @tparam The type of the component //! @throw NotFoundError if the component could not be found + //! @return The component of the requested type. template T &getComponent() { - const std::type_index &type = typeid(T); - auto existing = this->_components.find(type); - if (existing == this->_components.end()) - throw NotFoundError("No component could be found with the type \"" + std::string(type.name()) + "\"."); - return *static_cast(existing->second.get()); + T *ret = this->tryGetComponent(); + if (ret == nullptr) + throw NotFoundError("No component could be found with the type \"" + std::string(typeid(T).name()) + "\"."); + return *ret; } + //! @brief Get a component of a specific type or null if not found. + //! @tparam The type of the component + //! @return The component or nullptr if not found. template - T *getComponentOrDefault() + T *tryGetComponent() { const std::type_index &type = typeid(T); auto existing = this->_components.find(type); if (existing == this->_components.end()) return nullptr; - return *static_cast(existing->second.get()); + return static_cast(existing->second.get()); } //! @brief Check if this entity has a component. diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp index 22c02b60..1c3ba2d9 100644 --- a/lib/wal/sources/View/View.hpp +++ b/lib/wal/sources/View/View.hpp @@ -153,18 +153,19 @@ namespace WAL { this->_types = {typeid(Components)...}; for (auto &entity : scene) { - auto tuple = std::make_tuple(entity, entity.getComponentOrDefault()...); - std::apply(&this->_entities.emplace_back, tuple); + auto tuple = std::make_tuple(entity.tryGetComponent()...); + if (std::apply([](const auto *...component) {return ((component == nullptr) || ...);}, tuple)) + continue; + std::apply([&](auto *...component) { + this->_entities.emplace_back(entity, *component...); + }, tuple); } - // std::copy_if(scene.begin(), scene.end(), std::back_inserter(this->entities), [](Entity &entity) { -// return (entity.hasComponent() && ...); -// }); } //! @brief Copying a view is not possible since a view must be managed by a scene. View(const View &) = delete; //! @brief A default destructor - ~View() = default; + ~View() override = default; //! @brief A view is not assignable. View &operator=(const View &) = delete; }; From f20445cdc824b8e5bdf2de95b5eb2734757bbf94 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Jun 2021 18:42:10 +0200 Subject: [PATCH 13/22] Update systems to the new view --- CMakeLists.txt | 36 ++++---- lib/wal/sources/System/System.hpp | 5 +- lib/wal/sources/View/View.hpp | 86 ++++++++++++++----- sources/Runner/Runner.cpp | 35 ++++---- sources/System/Animation/AnimationsSystem.cpp | 6 +- sources/System/Animation/AnimationsSystem.hpp | 2 +- sources/System/Collision/CollisionSystem.cpp | 4 +- .../Controllable/ControllableSystem.cpp | 6 +- .../Controllable/ControllableSystem.hpp | 2 +- sources/System/Gamepad/GamepadSystem.cpp | 6 +- sources/System/Gamepad/GamepadSystem.hpp | 2 +- .../GridCentered/GridCenteredSystem.cpp | 6 +- .../GridCentered/GridCenteredSystem.hpp | 2 +- sources/System/Health/HealthSystem.cpp | 4 +- sources/System/Health/HealthSystem.hpp | 2 +- sources/System/Keyboard/KeyboardSystem.cpp | 8 +- sources/System/Keyboard/KeyboardSystem.hpp | 2 +- sources/System/Movable/MovableSystem.cpp | 6 +- sources/System/Movable/MovableSystem.hpp | 2 +- sources/System/Renderer/RenderSystem.cpp | 6 +- sources/System/Renderer/RenderSystem.hpp | 2 +- tests/CollisionTest.cpp | 16 ++-- tests/MoveTests.cpp | 18 ++-- tests/ViewTest.cpp | 80 +++++++++++++---- 24 files changed, 213 insertions(+), 131 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70d6a98e..fd96ef8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,34 +35,34 @@ set(SOURCES sources/Component/Keyboard/KeyboardComponent.hpp sources/Component/Health/HealthComponent.cpp sources/Component/Health/HealthComponent.hpp -# sources/System/Movable/MovableSystem.hpp -# sources/System/Movable/MovableSystem.cpp -# sources/System/Controllable/ControllableSystem.cpp -# sources/System/Controllable/ControllableSystem.hpp -# sources/System/Gamepad/GamepadSystem.cpp -# sources/System/Gamepad/GamepadSystem.hpp -# sources/System/Health/HealthSystem.cpp -# sources/System/Health/HealthSystem.hpp -# sources/System/Keyboard/KeyboardSystem.cpp -# sources/System/Keyboard/KeyboardSystem.hpp -# sources/System/Movable/MovableSystem.cpp -# sources/System/Movable/MovableSystem.hpp + sources/System/Movable/MovableSystem.hpp + sources/System/Movable/MovableSystem.cpp + sources/System/Controllable/ControllableSystem.cpp + sources/System/Controllable/ControllableSystem.hpp + sources/System/Gamepad/GamepadSystem.cpp + sources/System/Gamepad/GamepadSystem.hpp + sources/System/Health/HealthSystem.cpp + sources/System/Health/HealthSystem.hpp + sources/System/Keyboard/KeyboardSystem.cpp + sources/System/Keyboard/KeyboardSystem.hpp + sources/System/Movable/MovableSystem.cpp + sources/System/Movable/MovableSystem.hpp sources/Models/Vector3.hpp sources/Component/GridCentered/GridCenteredComponent.cpp sources/Component/GridCentered/GridCenteredComponent.hpp -# sources/System/GridCentered/GridCenteredSystem.cpp -# sources/System/GridCentered/GridCenteredSystem.hpp + sources/System/GridCentered/GridCenteredSystem.cpp + sources/System/GridCentered/GridCenteredSystem.hpp sources/Models/Vector2.hpp sources/Component/Renderer/Drawable2DComponent.hpp sources/Component/Renderer/Drawable3DComponent.hpp -# sources/System/Renderer/RenderSystem.hpp -# sources/System/Renderer/RenderSystem.cpp + sources/System/Renderer/RenderSystem.hpp + sources/System/Renderer/RenderSystem.cpp sources/Component/Renderer/CameraComponent.cpp sources/Component/Renderer/CameraComponent.hpp sources/Component/Animation/AnimationsComponent.cpp sources/Component/Animation/AnimationsComponent.hpp -# sources/System/Animation/AnimationsSystem.cpp -# sources/System/Animation/AnimationsSystem.hpp + sources/System/Animation/AnimationsSystem.cpp + sources/System/Animation/AnimationsSystem.hpp sources/Component/Collision/CollisionComponent.cpp sources/Component/Collision/CollisionComponent.hpp sources/System/Collision/CollisionSystem.hpp diff --git a/lib/wal/sources/System/System.hpp b/lib/wal/sources/System/System.hpp index c21de9d2..58f49970 100644 --- a/lib/wal/sources/System/System.hpp +++ b/lib/wal/sources/System/System.hpp @@ -12,6 +12,7 @@ #include "Wal.hpp" #include "View/View.hpp" #include "ISystem.hpp" +#include namespace WAL { @@ -50,7 +51,7 @@ namespace WAL //! @param dtime The delta time since the last call to this method. void update(std::chrono::nanoseconds dtime) final { - for (auto entity : this->getView()) + for (auto &entity : this->getView()) this->onUpdate(entity, dtime); this->onSelfUpdate(); } @@ -59,7 +60,7 @@ namespace WAL //! @remark This should be used for Physics, AI and everything that could be imprecise due to float rounding. void fixedUpdate() final { - for (auto entity : this->getView()) + for (auto &entity : this->getView()) this->onFixedUpdate(entity); } protected: diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp index 1c3ba2d9..cbee789e 100644 --- a/lib/wal/sources/View/View.hpp +++ b/lib/wal/sources/View/View.hpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "Entity/Entity.hpp" namespace WAL @@ -43,6 +45,12 @@ namespace WAL { return std::get>(this->_value); } + + template + auto &get() + { + return std::get(this->_value); + } }; template @@ -50,18 +58,19 @@ namespace WAL { private: It _it; + std::optional> _entity; public: - ViewEntity operator*() + ViewEntity &operator*() { - ViewEntity entity(*this->_it); - return entity; + this->_entity.emplace(*this->_it); + return this->_entity.value(); } - ViewEntity operator->() + ViewEntity *operator->() { - ViewEntity entity(*this->_it); - return entity; + this->_entity.emplace(*this->_it); + return &this->_entity; } ViewIterator &operator++() @@ -88,7 +97,8 @@ namespace WAL } explicit ViewIterator(It current) - : _it(current) + : _it(current), + _entity(std::nullopt) {} }; @@ -129,7 +139,22 @@ namespace WAL iterator end() { - return iterator(this->_entities.begin()); + return iterator(this->_entities.end()); + } + + std::size_t size() const + { + return this->_entities.size(); + } + + ViewEntity front() + { + return *iterator(this->_entities.begin()); + } + + ViewEntity back() + { + return *iterator(--this->_entities.end()); } const std::vector &getTypes() const override @@ -137,14 +162,21 @@ namespace WAL return this->_types; } - void emplace_back(Entity &) override + void emplace_back(Entity &entity) override { - + auto tuple = std::make_tuple(entity.tryGetComponent()...); + if (std::apply([](const auto *...component) {return ((component == nullptr) || ...);}, tuple)) + return; + std::apply([&](auto *...component) { + this->_entities.emplace_back(entity, *component...); + }, tuple); } - void erase(const Entity &) override + void erase(const Entity &entity) override { - + this->_entities.erase(std::remove_if(this->_entities.begin(), this->_entities.end(), [&entity](const auto &ref){ + return &std::get<0>(ref).get() == &entity; + })); } //! @brief Construct a view from a list of entities. @@ -152,14 +184,8 @@ namespace WAL explicit View(std::list &scene) { this->_types = {typeid(Components)...}; - for (auto &entity : scene) { - auto tuple = std::make_tuple(entity.tryGetComponent()...); - if (std::apply([](const auto *...component) {return ((component == nullptr) || ...);}, tuple)) - continue; - std::apply([&](auto *...component) { - this->_entities.emplace_back(entity, *component...); - }, tuple); - } + for (auto &entity : scene) + this->emplace_back(entity); } //! @brief Copying a view is not possible since a view must be managed by a scene. @@ -169,4 +195,24 @@ namespace WAL //! @brief A view is not assignable. View &operator=(const View &) = delete; }; +} + +namespace std +{ + template + struct tuple_size<::WAL::ViewEntity> + : public std::integral_constant + {}; + + template + struct tuple_element<0, ::WAL::ViewEntity> + { + using type = WAL::Entity &; + }; + + template + struct tuple_element> + { + using type = typename std::tuple_element>::type; + }; } \ No newline at end of file diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index ff933fff..6dbce849 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -4,33 +4,28 @@ #include #include -//#include "System/Movable/MovableSystem.hpp" -//#include "System/Renderer/RenderSystem.hpp" +#include "System/Movable/MovableSystem.hpp" +#include "System/Renderer/RenderSystem.hpp" #include #include -#include -//#include #include -//#include -//#include +#include +#include #include #include #include #include #include -//#include -//#include "Models/Vector2.hpp" +#include #include "Component/Renderer/CameraComponent.hpp" -//#include "Component/Renderer/Drawable2DComponent.hpp" #include "Component/Renderer/Drawable3DComponent.hpp" #include "Runner.hpp" #include "Models/GameState.hpp" #include #include "Component/Animation/AnimationsComponent.hpp" -//#include "System/Animation/AnimationsSystem.hpp" +#include "System/Animation/AnimationsSystem.hpp" #include "Map/Map.hpp" -namespace RAY2D = RAY::Drawables::Drawables2D; namespace RAY3D = RAY::Drawables::Drawables3D; namespace BBM @@ -47,22 +42,22 @@ namespace BBM void addSystems(WAL::Wal &wal) { - wal.addSystem(); - // wal.addSystem() -// .addSystem() -// .addSystem() -// .addSystem() -// .addSystem(); + wal.addSystem() + .addSystem() + .addSystem() + .addSystem() + .addSystem(); } void enableRaylib(WAL::Wal &wal) { RAY::TraceLog::setLevel(LOG_WARNING); RAY::Window &window = RAY::Window::getInstance(600, 400, "Bomberman", FLAG_WINDOW_RESIZABLE); -// wal.addSystem(window); + wal.addSystem() + .addSystem(window); } - std::shared_ptr loadGameScene(WAL::Wal &wal) + std::shared_ptr loadGameScene() { auto scene = std::make_shared(); scene->addEntity("player") @@ -99,7 +94,7 @@ namespace BBM WAL::Wal wal; addSystems(wal); enableRaylib(wal); - wal.scene = loadGameScene(wal); + wal.scene = loadGameScene(); try { wal.run(updateState); diff --git a/sources/System/Animation/AnimationsSystem.cpp b/sources/System/Animation/AnimationsSystem.cpp index afc22e31..5aaa4597 100644 --- a/sources/System/Animation/AnimationsSystem.cpp +++ b/sources/System/Animation/AnimationsSystem.cpp @@ -15,10 +15,10 @@ namespace BBM : System(wal) {} - void AnimationsSystem::onUpdate(WAL::Entity &entity, std::chrono::nanoseconds) + void AnimationsSystem::onUpdate(WAL::ViewEntity &entity, std::chrono::nanoseconds) { - auto &model = entity.getComponent(); - auto &anim = entity.getComponent(); + auto &model = entity.get(); + auto &anim = entity.get(); if (anim.isDisabled()) return; diff --git a/sources/System/Animation/AnimationsSystem.hpp b/sources/System/Animation/AnimationsSystem.hpp index b17b1935..6ace28d8 100644 --- a/sources/System/Animation/AnimationsSystem.hpp +++ b/sources/System/Animation/AnimationsSystem.hpp @@ -14,7 +14,7 @@ namespace BBM { public: //! @inherit - void onUpdate(WAL::Entity &entity, std::chrono::nanoseconds) override; + void onUpdate(WAL::ViewEntity &entity, std::chrono::nanoseconds) override; //! @brief A default constructor explicit AnimationsSystem(WAL::Wal &wal); diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index 79351729..4e992bda 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -28,8 +28,8 @@ namespace BBM auto &posA = entity.get(); auto &col = entity.get(); Vector3f position = posA.position; -// if (entity.hasComponent(typeid(MovableComponent))) -// position += entity.getComponent().getVelocity(); + if (auto *movable = entity->tryGetComponent()) + position += movable->getVelocity(); Vector3f minA = Vector3f::min(position, position + col.bound); Vector3f maxA = Vector3f::max(position, position + col.bound); for (auto other : this->getView()) { diff --git a/sources/System/Controllable/ControllableSystem.cpp b/sources/System/Controllable/ControllableSystem.cpp index b028a2c7..f14c46b3 100644 --- a/sources/System/Controllable/ControllableSystem.cpp +++ b/sources/System/Controllable/ControllableSystem.cpp @@ -14,10 +14,10 @@ namespace BBM : System(wal) {} - void ControllableSystem::onFixedUpdate(WAL::Entity &entity) + void ControllableSystem::onFixedUpdate(WAL::ViewEntity &entity) { - auto &controllable = entity.getComponent(); - auto &movable = entity.getComponent(); + auto &controllable = entity.get(); + auto &movable = entity.get(); Vector2f move = controllable.move.normalized() * ControllableSystem::speed; movable.addForce(Vector3f(move.x, controllable.jump, move.y)); diff --git a/sources/System/Controllable/ControllableSystem.hpp b/sources/System/Controllable/ControllableSystem.hpp index 042b17df..25a9e837 100644 --- a/sources/System/Controllable/ControllableSystem.hpp +++ b/sources/System/Controllable/ControllableSystem.hpp @@ -19,7 +19,7 @@ namespace BBM static constexpr const float speed = .25f; //! @inherit - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit ControllableSystem(WAL::Wal &wal); diff --git a/sources/System/Gamepad/GamepadSystem.cpp b/sources/System/Gamepad/GamepadSystem.cpp index 9392dc02..f6d67d99 100644 --- a/sources/System/Gamepad/GamepadSystem.cpp +++ b/sources/System/Gamepad/GamepadSystem.cpp @@ -17,10 +17,10 @@ namespace BBM : System(wal) {} - void GamepadSystem::onFixedUpdate(WAL::Entity &entity) + void GamepadSystem::onFixedUpdate(WAL::ViewEntity &entity) { - const auto &gamepadComponent = entity.getComponent(); - auto &controllable = entity.getComponent(); + const auto &gamepadComponent = entity.get(); + auto &controllable = entity.get(); Gamepad gamepad(gamepadComponent.getID()); const std::map keyPressedMap = { diff --git a/sources/System/Gamepad/GamepadSystem.hpp b/sources/System/Gamepad/GamepadSystem.hpp index 8daa5e06..61efdd5e 100644 --- a/sources/System/Gamepad/GamepadSystem.hpp +++ b/sources/System/Gamepad/GamepadSystem.hpp @@ -16,7 +16,7 @@ namespace BBM { public: //! @inherit - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit GamepadSystem(WAL::Wal &wal); diff --git a/sources/System/GridCentered/GridCenteredSystem.cpp b/sources/System/GridCentered/GridCenteredSystem.cpp index d6337c3a..3c86175b 100644 --- a/sources/System/GridCentered/GridCenteredSystem.cpp +++ b/sources/System/GridCentered/GridCenteredSystem.cpp @@ -12,10 +12,10 @@ namespace BBM : System(wal) {} - void GridCenteredSystem::onFixedUpdate(WAL::Entity &entity) + void GridCenteredSystem::onFixedUpdate(WAL::ViewEntity &entity) { - auto &grid = entity.getComponent(); - auto &movement = entity.getComponent(); + auto &grid = entity.get(); + auto &movement = entity.get(); // movement.addForce(grid.force * ) } } \ No newline at end of file diff --git a/sources/System/GridCentered/GridCenteredSystem.hpp b/sources/System/GridCentered/GridCenteredSystem.hpp index 3af49ea1..6ab97339 100644 --- a/sources/System/GridCentered/GridCenteredSystem.hpp +++ b/sources/System/GridCentered/GridCenteredSystem.hpp @@ -13,7 +13,7 @@ namespace BBM class GridCenteredSystem : public WAL::System { public: - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit GridCenteredSystem(WAL::Wal &wal); diff --git a/sources/System/Health/HealthSystem.cpp b/sources/System/Health/HealthSystem.cpp index e9aaa155..29e0db1f 100644 --- a/sources/System/Health/HealthSystem.cpp +++ b/sources/System/Health/HealthSystem.cpp @@ -14,9 +14,9 @@ namespace BBM : System(wal) {} - void HealthSystem::onFixedUpdate(WAL::Entity &entity) + void HealthSystem::onFixedUpdate(WAL::ViewEntity &entity) { - auto &health = entity.getComponent(); + auto &health = entity.get(); if (health.getHealthPoint() == 0) health.onDeath(entity); diff --git a/sources/System/Health/HealthSystem.hpp b/sources/System/Health/HealthSystem.hpp index fe8bb1ab..f1cd2397 100644 --- a/sources/System/Health/HealthSystem.hpp +++ b/sources/System/Health/HealthSystem.hpp @@ -15,7 +15,7 @@ namespace BBM { public: //! @inherit - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit HealthSystem(WAL::Wal &wal); diff --git a/sources/System/Keyboard/KeyboardSystem.cpp b/sources/System/Keyboard/KeyboardSystem.cpp index 044373b4..ccc207ab 100644 --- a/sources/System/Keyboard/KeyboardSystem.cpp +++ b/sources/System/Keyboard/KeyboardSystem.cpp @@ -3,11 +3,9 @@ // Edited by Benjamin Henry on 2021-05-20. // -#include #include "KeyboardSystem.hpp" #include "Component/Keyboard/KeyboardComponent.hpp" #include "Component/Controllable/ControllableComponent.hpp" -#include "Entity/Entity.hpp" #include "Controllers/Keyboard.hpp" using Keyboard = RAY::Controller::Keyboard; @@ -18,10 +16,10 @@ namespace BBM : System(wal) {} - void KeyboardSystem::onFixedUpdate(WAL::Entity &entity) + void KeyboardSystem::onFixedUpdate(WAL::ViewEntity &entity) { - const auto &keyboard = entity.getComponent(); - auto &controllable = entity.getComponent(); + const auto &keyboard = entity.get(); + auto &controllable = entity.get(); const std::map keyPressedMap = { {keyboard.keyJump, controllable.jump}, diff --git a/sources/System/Keyboard/KeyboardSystem.hpp b/sources/System/Keyboard/KeyboardSystem.hpp index 56977386..cf7309e1 100644 --- a/sources/System/Keyboard/KeyboardSystem.hpp +++ b/sources/System/Keyboard/KeyboardSystem.hpp @@ -17,7 +17,7 @@ namespace BBM { public: //! @inherit - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit KeyboardSystem(WAL::Wal &wal); diff --git a/sources/System/Movable/MovableSystem.cpp b/sources/System/Movable/MovableSystem.cpp index c6d86457..eff5a79a 100644 --- a/sources/System/Movable/MovableSystem.cpp +++ b/sources/System/Movable/MovableSystem.cpp @@ -12,10 +12,10 @@ namespace BBM : System(wal) {} - void MovableSystem::onFixedUpdate(WAL::Entity &entity) + void MovableSystem::onFixedUpdate(WAL::ViewEntity &entity) { - auto &movable = entity.getComponent(); - auto &position = entity.getComponent(); + auto &movable = entity.get(); + auto &position = entity.get(); position.position += movable._velocity; movable._velocity = movable._acceleration; diff --git a/sources/System/Movable/MovableSystem.hpp b/sources/System/Movable/MovableSystem.hpp index 7032d97e..e109da13 100644 --- a/sources/System/Movable/MovableSystem.hpp +++ b/sources/System/Movable/MovableSystem.hpp @@ -17,7 +17,7 @@ namespace BBM { public: //! @inherit - void onFixedUpdate(WAL::Entity &entity) override; + void onFixedUpdate(WAL::ViewEntity &entity) override; //! @brief A default constructor explicit MovableSystem(WAL::Wal &wal); diff --git a/sources/System/Renderer/RenderSystem.cpp b/sources/System/Renderer/RenderSystem.cpp index 16190445..1607183e 100644 --- a/sources/System/Renderer/RenderSystem.cpp +++ b/sources/System/Renderer/RenderSystem.cpp @@ -52,10 +52,10 @@ namespace BBM this->_window.endDrawing(); } - void RenderSystem::onUpdate(WAL::Entity &entity, std::chrono::nanoseconds dtime) + void RenderSystem::onUpdate(WAL::ViewEntity &entity, std::chrono::nanoseconds dtime) { - const auto &pos = entity.getComponent(); - const auto &cam = entity.getComponent(); + const auto &pos = entity.get(); + const auto &cam = entity.get(); _camera.setPosition(pos.position); _camera.setTarget(cam.target); } diff --git a/sources/System/Renderer/RenderSystem.hpp b/sources/System/Renderer/RenderSystem.hpp index 0ba6c4d3..9c5c4c27 100644 --- a/sources/System/Renderer/RenderSystem.hpp +++ b/sources/System/Renderer/RenderSystem.hpp @@ -27,7 +27,7 @@ namespace BBM void onSelfUpdate() override; //! @inherit - void onUpdate(WAL::Entity &entity, std::chrono::nanoseconds dtime) override; + void onUpdate(WAL::ViewEntity &entity, std::chrono::nanoseconds dtime) override; //! @brief ctor RenderSystem(WAL::Wal &wal, RAY::Window &window); diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp index 77188df1..ce153238 100644 --- a/tests/CollisionTest.cpp +++ b/tests/CollisionTest.cpp @@ -39,8 +39,8 @@ TEST_CASE("Collision test", "[Component][System]") entity.getComponent().bound.y = 5; entity.getComponent().bound.z = 5; - collision.onUpdate(entity, std::chrono::nanoseconds(1)); - collision.onFixedUpdate(entity); + collision.update(std::chrono::nanoseconds(1)); + collision.fixedUpdate(); REQUIRE(entity.getComponent().position.x == 0.0); REQUIRE(entity.getComponent().position.y == 0.0); REQUIRE(entity.getComponent().position.z == 0.0); @@ -49,9 +49,9 @@ TEST_CASE("Collision test", "[Component][System]") .addComponent(2,2,2) .addComponent(1); Entity &player = wal.scene->getEntities().front(); - collision.onUpdate(entity, std::chrono::nanoseconds(1)); + collision.update(std::chrono::nanoseconds(1)); REQUIRE(player.hasComponent(typeid(PositionComponent))); - collision.onFixedUpdate(player); + collision.fixedUpdate(); REQUIRE(wal.scene->getEntities().size() == 2); REQUIRE(player.hasComponent(typeid(PositionComponent))); REQUIRE(player.getComponent().position.x == 1.0); @@ -87,10 +87,10 @@ TEST_CASE("Collsion test with movable", "[Component][System]") entity.getComponent().bound.z = 5; entity.getComponent().addForce({1, 1, 1}); - collision.onUpdate(entity, std::chrono::nanoseconds(1)); - collision.onFixedUpdate(entity); - movable.onUpdate(entity, std::chrono::nanoseconds(1)); - movable.onFixedUpdate(entity); + collision.update(std::chrono::nanoseconds(1)); + collision.fixedUpdate(); + movable.update(std::chrono::nanoseconds(1)); + movable.fixedUpdate(); REQUIRE(entity.getComponent().position.x == 0.0); REQUIRE(entity.getComponent().position.y == 0.0); REQUIRE(entity.getComponent().position.z == 0.0); diff --git a/tests/MoveTests.cpp b/tests/MoveTests.cpp index ac035c86..b19a68fc 100644 --- a/tests/MoveTests.cpp +++ b/tests/MoveTests.cpp @@ -20,32 +20,32 @@ using namespace BBM; TEST_CASE("Move test", "[Component][System]") { Wal wal; - Scene scene; - scene.addEntity("player") + wal.scene = std::make_shared(); + wal.scene->addEntity("player") .addComponent() .addComponent() .addComponent(); - Entity &entity = *scene.getEntities().begin(); + Entity &entity = wal.scene->getEntities().front(); REQUIRE(entity.getComponent().position == Vector3f()); entity.getComponent().move = Vector2f(1, 1); ControllableSystem controllable(wal); - controllable.onUpdate(entity, std::chrono::nanoseconds(1)); - controllable.onFixedUpdate(entity); + controllable.update(std::chrono::nanoseconds(1)); + controllable.fixedUpdate(); REQUIRE(entity.getComponent()._acceleration.x > 0); REQUIRE(entity.getComponent()._acceleration.z > 0); MovableSystem movable(wal); - movable.onUpdate(entity, std::chrono::nanoseconds(1)); - movable.onFixedUpdate(entity); + movable.update(std::chrono::nanoseconds(1)); + movable.fixedUpdate(); REQUIRE(entity.getComponent()._velocity.x > 0); REQUIRE(entity.getComponent()._velocity.z > 0); REQUIRE(entity.getComponent()._acceleration.x == 0); REQUIRE(entity.getComponent()._acceleration.z == 0); - movable.onUpdate(entity, std::chrono::nanoseconds(1)); - movable.onFixedUpdate(entity); + movable.update(std::chrono::nanoseconds(1)); + movable.fixedUpdate(); REQUIRE(entity.getComponent().position.x > 0); REQUIRE(entity.getComponent().position.z > 0); diff --git a/tests/ViewTest.cpp b/tests/ViewTest.cpp index 194e04a6..569d42ad 100644 --- a/tests/ViewTest.cpp +++ b/tests/ViewTest.cpp @@ -22,7 +22,7 @@ TEST_CASE("View creation", "[View]") REQUIRE(scene.view().size() == 2); REQUIRE(scene.view().size() == 1); Entity &entity = *scene.getEntities().begin(); - Entity &firstView = *scene.view().entities.begin(); + Entity &firstView = scene.view().front(); REQUIRE(&entity == &firstView); } @@ -62,23 +62,65 @@ TEST_CASE("View cache switch", "[View]") .addComponent(); REQUIRE(&view == &scene.view()); - REQUIRE(view.entities.begin()->get().getName() == "player"); - REQUIRE(scene2.view().entities.begin()->get().getName() == "box"); + REQUIRE(view.front()->getName() == "player"); + REQUIRE(scene2.view().front()->getName() == "box"); } -//TEST_CASE("View iteration", "[View]") -//{ -// Scene scene; -// scene.addEntity("player") -// .addComponent() -// .addComponent(); -// scene.addEntity("Box") -// .addComponent(); -// int i = 0; -// for (auto &entity : scene.view()) { -// if (i == 0) -// REQUIRE(entity.getName() == "player"); -// else -// REQUIRE(entity.getName() == "Box"); -// } -//} \ No newline at end of file +TEST_CASE("View entity iteration", "[View]") +{ + Scene scene; + scene.addEntity("player") + .addComponent() + .addComponent(); + scene.addEntity("Box") + .addComponent(); + int i = 0; + for (Entity &entity : scene.view()) { + if (i == 0) + REQUIRE(entity.getName() == "player"); + else + REQUIRE(entity.getName() == "Box"); + i++; + } + REQUIRE(i == 2); +} + +TEST_CASE("ViewEntity<> iteration", "[View]") +{ + Scene scene; + scene.addEntity("player") + .addComponent(1, 1, 1) + .addComponent(); + scene.addEntity("Box") + .addComponent(1, 1, 1); + int i = 0; + for (auto entity : scene.view()) { + if (i == 0) + REQUIRE(entity->getName() == "player"); + else + REQUIRE(entity->getName() == "Box"); + REQUIRE(entity.get().position == Vector3f(1, 1, 1)); + i++; + } + REQUIRE(i == 2); +} + +TEST_CASE("View [entity, component] iteration", "[View]") +{ + Scene scene; + scene.addEntity("player") + .addComponent(1, 1, 1) + .addComponent(); + scene.addEntity("Box") + .addComponent(1, 1, 1); + int i = 0; + for (auto &[entity, position] : scene.view()) { + if (i == 0) + REQUIRE(entity.getName() == "player"); + else + REQUIRE(entity.getName() == "Box"); + REQUIRE(position.position == Vector3f(1, 1, 1)); + i++; + } + REQUIRE(i == 2); +} \ No newline at end of file From f3ce14caca373e1ba5296808b5d3edd85d054f0c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Jun 2021 19:11:26 +0200 Subject: [PATCH 14/22] Cleaning up callbacks --- lib/wal/sources/Models/Callback.hpp | 13 +- lib/wal/sources/Wal.hpp | 115 +++++++++--------- .../Collision/CollisionComponent.cpp | 8 -- .../Collision/CollisionComponent.hpp | 6 - sources/System/Renderer/RenderSystem.cpp | 16 +-- 5 files changed, 67 insertions(+), 91 deletions(-) diff --git a/lib/wal/sources/Models/Callback.hpp b/lib/wal/sources/Models/Callback.hpp index be2c092f..dc12a18b 100644 --- a/lib/wal/sources/Models/Callback.hpp +++ b/lib/wal/sources/Models/Callback.hpp @@ -24,10 +24,14 @@ namespace WAL //! @brief Add a method to be called when this callback is invoked. //! @param callback The list of arguments of the callback method //! @return A unique ID for this callback. That can be used to remove the callback later. - int addCallback(std::function callback) + template + int addCallback(Func callback) { int id = this->_nextID++; - this->_functions[id] = std::move(callback); + if constexpr(std::is_same_v>) + this->_functions[id] = std::move(callback); + else + this->_functions[id] = std::function(callback); return id; } @@ -53,8 +57,9 @@ namespace WAL //! @brief A default assignment operator Callback &operator=(const Callback &) = default; - //! @brief Implicitly transform a function into a callback. - Callback(std::function callback) // NOLINT(google-explicit-constructor) + //! @brief Implicitly transform a callable into a callback. + template + Callback(Func callback) // NOLINT(google-explicit-constructor) { this->addCallback(callback); } diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index ef138871..f0c8dd50 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -28,6 +28,56 @@ namespace 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; @@ -94,23 +144,6 @@ namespace WAL 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 std::function &callback, T state = T()) - { - Callback update(callback); - - #if defined(PLATFORM_WEB) - std::tuple iterationParams(this, &update, &state); - return emscripten_set_main_loop_arg((em_arg_callback_func)runIteration, (void *)&iterationParams, 0, 1); - #else - return this->run(update, state); - #endif - } - //! @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. @@ -118,50 +151,14 @@ namespace WAL 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) + 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 } - #if defined(PLATFORM_WEB) - template - static void runIteration(void *param) - { - static auto iterationParams = reinterpret_cast *, T *> *>(param); - static const Callback callback = *((Callback *)std::get<1>(*iterationParams)); - static T *state = (T *)std::get<2>(*iterationParams); - static Wal *wal = (Wal *)std::get<0>(*iterationParams); - 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; - wal->_fixedUpdate(); - } - wal->_update(dtime); - callback(*wal, *state); - } - #endif - //! @brief A default constructor Wal() = default; //! @brief A WAL can't be copy constructed diff --git a/sources/Component/Collision/CollisionComponent.cpp b/sources/Component/Collision/CollisionComponent.cpp index 22cbdef8..df727b2d 100644 --- a/sources/Component/Collision/CollisionComponent.cpp +++ b/sources/Component/Collision/CollisionComponent.cpp @@ -16,14 +16,6 @@ namespace BBM return new CollisionComponent(entity); } - CollisionComponent::CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, Vector3f bound) - : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound(bound) - { } - - CollisionComponent::CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, float boundSize) - : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound({boundSize, boundSize, boundSize}) - { } - CollisionComponent::CollisionComponent(WAL::Entity &entity, WAL::Callback onCollide, WAL::Callback onCollided, Vector3f bound) : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound(bound) { } diff --git a/sources/Component/Collision/CollisionComponent.hpp b/sources/Component/Collision/CollisionComponent.hpp index 4636c79a..5bfe7e16 100644 --- a/sources/Component/Collision/CollisionComponent.hpp +++ b/sources/Component/Collision/CollisionComponent.hpp @@ -27,12 +27,6 @@ namespace BBM //! @brief A component can't be instantiated, it should be derived. explicit CollisionComponent(WAL::Entity &entity); - //! @brief Constructor with a callback function - CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, Vector3f bound); - - //! @brief Constructor with a callback function, same boundSize for all axis - CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, float boundSize = 0); - //! @brief Constructor with a WAL::Callback CollisionComponent(WAL::Entity &entity, WAL::Callback onCollide, WAL::Callback onCollided,Vector3f bound); diff --git a/sources/System/Renderer/RenderSystem.cpp b/sources/System/Renderer/RenderSystem.cpp index 1607183e..bd6a3adc 100644 --- a/sources/System/Renderer/RenderSystem.cpp +++ b/sources/System/Renderer/RenderSystem.cpp @@ -26,26 +26,14 @@ namespace BBM this->_window.clear(); this->_window.useCamera(this->_camera); - for (auto &entity : this->_wal.scene->getEntities()) { - if (!entity.hasComponent() - || !entity.hasComponent()) - continue; - auto &drawable = entity.getComponent(); - auto &pos = entity.getComponent(); - + for (auto &[_, pos, drawable] : this->_wal.scene->view()) { drawable.drawable->setPosition(pos.position); drawable.drawable->drawOn(this->_window); } this->_window.unuseCamera(); // TODO sort entities based on the Z axis - for (auto &entity : this->_wal.scene->getEntities()) { - if (!entity.hasComponent() - || !entity.hasComponent()) - continue; - auto &drawable = entity.getComponent(); - auto &pos = entity.getComponent(); - + for (auto &[_, pos, drawable] : this->_wal.scene->view()) { drawable.drawable->setPosition(Vector2f(pos.position.x, pos.position.y)); drawable.drawable->drawOn(this->_window); } From 1b4e8d2151832be6cac3eee6864e21e35c4aa601 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Jun 2021 19:55:38 +0200 Subject: [PATCH 15/22] Optimizing view iterators --- lib/wal/sources/View/View.hpp | 14 +++++++++----- lib/wal/sources/Wal.hpp | 2 +- sources/System/Collision/CollisionSystem.cpp | 10 ++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/wal/sources/View/View.hpp b/lib/wal/sources/View/View.hpp index cbee789e..15b31e09 100644 --- a/lib/wal/sources/View/View.hpp +++ b/lib/wal/sources/View/View.hpp @@ -19,9 +19,9 @@ namespace WAL class ViewEntity { private: - std::tuple, std::reference_wrapper...> _value; + std::tuple, std::reference_wrapper...> &_value; public: - explicit ViewEntity(std::tuple, std::reference_wrapper...> value) + explicit ViewEntity(std::tuple, std::reference_wrapper...> &value) : _value(value) {} @@ -63,19 +63,22 @@ namespace WAL public: ViewEntity &operator*() { - this->_entity.emplace(*this->_it); - return this->_entity.value(); + if (!this->_entity) + this->_entity.emplace(*this->_it); + return *this->_entity; } ViewEntity *operator->() { - this->_entity.emplace(*this->_it); + if (!this->_entity) + this->_entity =(*this->_it); return &this->_entity; } ViewIterator &operator++() { this->_it++; + this->_entity = std::nullopt; return *this; } @@ -83,6 +86,7 @@ namespace WAL { ViewIterator copy = *this; this->_it++; + this->_entity = std::nullopt; return *this; } diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index f0c8dd50..85a7804e 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -84,7 +84,7 @@ namespace WAL //! @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); + static constexpr std::chrono::nanoseconds timestep = std::chrono::milliseconds(16); //! @brief Create a new system in place. //! @return The wal instance used to call this function is returned. This allow method chaining. diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp index 4e992bda..6a3e36cd 100644 --- a/sources/System/Collision/CollisionSystem.cpp +++ b/sources/System/Collision/CollisionSystem.cpp @@ -32,13 +32,11 @@ namespace BBM position += movable->getVelocity(); Vector3f minA = Vector3f::min(position, position + col.bound); Vector3f maxA = Vector3f::max(position, position + col.bound); - for (auto other : this->getView()) { - if (other->getUid() == entity->getUid()) + for (auto &[other, posB, colB] : this->getView()) { + if (other.getUid() == entity->getUid()) continue; - auto colB = other.get(); - auto posB = other.get().position; - Vector3f minB = Vector3f::min(posB, posB + colB.bound); - Vector3f maxB = Vector3f::max(posB, posB + colB.bound); + Vector3f minB = Vector3f::min(posB.position, posB.position + colB.bound); + Vector3f maxB = Vector3f::max(posB.position, posB.position + colB.bound); if (collide(minA, maxA, minB, maxB)) { col.onCollide(entity, other); colB.onCollided(entity, other); From 0ebfa77e1a910c0dc05227ea823e1c82e95ee860 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Jun 2021 20:54:17 +0200 Subject: [PATCH 16/22] Fixing include for windowsw --- lib/wal/sources/Scene/Scene.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/wal/sources/Scene/Scene.cpp b/lib/wal/sources/Scene/Scene.cpp index 22ed03ff..77526662 100644 --- a/lib/wal/sources/Scene/Scene.cpp +++ b/lib/wal/sources/Scene/Scene.cpp @@ -3,6 +3,7 @@ // #include "Scene.hpp" +#include namespace WAL { From 01ea9053fab81c9c2303b949abbba4b6529aff9d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 6 Jun 2021 16:54:27 +0200 Subject: [PATCH 17/22] Making block solids --- lib/wal/sources/Entity/Entity.hpp | 26 +++++++++++++ lib/wal/sources/Models/Callback.hpp | 3 ++ .../Component/Movable/MovableComponent.cpp | 5 --- .../Component/Movable/MovableComponent.hpp | 4 +- sources/Map/Map.cpp | 38 ++++++++++++++----- sources/Map/Map.hpp | 2 + sources/Models/Vector3.hpp | 10 ++++- sources/Runner/Runner.cpp | 14 +------ 8 files changed, 70 insertions(+), 32 deletions(-) diff --git a/lib/wal/sources/Entity/Entity.hpp b/lib/wal/sources/Entity/Entity.hpp index 50770d18..25234f31 100644 --- a/lib/wal/sources/Entity/Entity.hpp +++ b/lib/wal/sources/Entity/Entity.hpp @@ -79,6 +79,32 @@ namespace WAL return static_cast(existing->second.get()); } + //! @brief Get a component of a specific type + //! @tparam The type of the component + //! @throw NotFoundError if the component could not be found + //! @return The component of the requested type. + template + const T &getComponent() const + { + const T *ret = this->tryGetComponent(); + if (ret == nullptr) + throw NotFoundError("No component could be found with the type \"" + std::string(typeid(T).name()) + "\"."); + return *ret; + } + + //! @brief Get a component of a specific type or null if not found. + //! @tparam The type of the component + //! @return The component or nullptr if not found. + template + const T *tryGetComponent() const + { + const std::type_index &type = typeid(T); + auto existing = this->_components.find(type); + if (existing == this->_components.end()) + return nullptr; + return static_cast(existing->second.get()); + } + //! @brief Check if this entity has a component. //! @tparam T The type of the component template diff --git a/lib/wal/sources/Models/Callback.hpp b/lib/wal/sources/Models/Callback.hpp index dc12a18b..2c988db4 100644 --- a/lib/wal/sources/Models/Callback.hpp +++ b/lib/wal/sources/Models/Callback.hpp @@ -64,4 +64,7 @@ namespace WAL this->addCallback(callback); } }; + + template + static constexpr Callback EmptyCallback; } // namespace WAL \ No newline at end of file diff --git a/sources/Component/Movable/MovableComponent.cpp b/sources/Component/Movable/MovableComponent.cpp index 51f0707d..26a8fd11 100644 --- a/sources/Component/Movable/MovableComponent.cpp +++ b/sources/Component/Movable/MovableComponent.cpp @@ -20,11 +20,6 @@ namespace BBM this->_acceleration += force; } - void MovableComponent::resetVelocity(void) - { - this->_velocity = {0, 0, 0}; - } - const Vector3f &MovableComponent::getVelocity(void) const { return _velocity; diff --git a/sources/Component/Movable/MovableComponent.hpp b/sources/Component/Movable/MovableComponent.hpp index 13c8bf90..6fecf89e 100644 --- a/sources/Component/Movable/MovableComponent.hpp +++ b/sources/Component/Movable/MovableComponent.hpp @@ -23,9 +23,6 @@ namespace BBM //! @param force The force to add to this entity's acceleration. The force is added instantly and in one go. void addForce(Vector3f force); - //! @brief Set velocity to 0 - void resetVelocity(void); - //! @brief Get velocity const Vector3f &getVelocity(void) const; @@ -42,5 +39,6 @@ namespace BBM MovableComponent &operator=(const MovableComponent &) = delete; friend class MovableSystem; + friend class MapGenerator; }; } // namespace WAL \ No newline at end of file diff --git a/sources/Map/Map.cpp b/sources/Map/Map.cpp index 258dcd9d..722f0433 100644 --- a/sources/Map/Map.cpp +++ b/sources/Map/Map.cpp @@ -5,11 +5,31 @@ #include #include "Map.hpp" +#include namespace RAY3D = RAY::Drawables::Drawables3D; namespace BBM -{ +{ + void MapGenerator::wallCollide(WAL::Entity &entity, const WAL::Entity &wall) + { + auto *mov = entity.tryGetComponent(); + if (!mov) + return; + auto &pos = entity.getComponent(); + const auto &wallPos = wall.getComponent(); + auto diff = pos.position + mov->getVelocity() - wallPos.position; + std::cout << diff << std::endl; + if (diff.x <= 0 && mov->_velocity.x < 0) + mov->_velocity.x = 0; + if (diff.x >= 0 && mov->_velocity.x > 0) + mov->_velocity.x = 0; + if (diff.z <= 0 && mov->_velocity.z < 0) + mov->_velocity.z = 0; + if (diff.z >= 0 && mov->_velocity.z > 0) + mov->_velocity.z = 0; + } + void MapGenerator::generateUnbreakableBlock(int width, int height, std::shared_ptr scene) { std::string unbreakableObj = "assets/wall/unbreakable_wall.obj"; @@ -20,7 +40,7 @@ namespace BBM if (!(i % 2) && !(j % 2)) { scene->addEntity("Unbreakable Wall") .addComponent(i, 0, j) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj)); } } @@ -34,19 +54,19 @@ namespace BBM scene->addEntity("Bottom Wall") .addComponent(Vector3f((width + 1) / 2, 0, -1)) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(width + 3, 1, 1)); scene->addEntity("Upper Wall") .addComponent(Vector3f((width + 1) / 2, 0, height + 1)) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(width + 3, 1, 1)); scene->addEntity("Left Wall") .addComponent(Vector3f(width + 1, 0, (height + 1) / 2)) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(1, 1, height + 3)); scene->addEntity("Right Wall") .addComponent(Vector3f(-1, 0, (height + 1) / 2)) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent(unbreakableObj, std::make_pair(MAP_DIFFUSE, unbreakablePnj), RAY::Vector3(1, 1, height + 3)); } @@ -81,7 +101,7 @@ namespace BBM scene->addEntity("Breakable Block") .addComponent(coords) .addComponent(1) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent("assets/wall/breakable_wall.obj", std::make_pair(MAP_DIFFUSE, "assets/wall/breakable_wall.png")); } @@ -96,7 +116,7 @@ namespace BBM { scene->addEntity("Unbreakable Block") .addComponent(coords) - .addComponent(1) + .addComponent(WAL::Callback(), &MapGenerator::wallCollide, .75) .addComponent("assets/wall/unbreakable_wall.obj", std::make_pair(MAP_DIFFUSE, "assets/wall/unbreakable_wall.png")); } @@ -155,7 +175,7 @@ namespace BBM MapGenerator::MapBlock MapGenerator::createHeight(MapBlock map, int width, int height) { - double rnd = static_cast(std::rand())/RAND_MAX; + double rnd = static_cast(std::rand()) / RAND_MAX; if (rnd > 0.60) { for (int i = 0; i < width; i++) { diff --git a/sources/Map/Map.hpp b/sources/Map/Map.hpp index 7f396b31..4059925b 100644 --- a/sources/Map/Map.hpp +++ b/sources/Map/Map.hpp @@ -119,6 +119,8 @@ namespace BBM public: + static void wallCollide(WAL::Entity &entity, const WAL::Entity &wall); + //! @param width Width of the map //! @param height Height of the map diff --git a/sources/Models/Vector3.hpp b/sources/Models/Vector3.hpp index dc1752f1..d4ba7337 100644 --- a/sources/Models/Vector3.hpp +++ b/sources/Models/Vector3.hpp @@ -71,7 +71,13 @@ namespace BBM } template - Vector3 &operator*=(T2 d) + Vector3 operator-(const Vector3 &vec) const + { + return Vector3(this->x - vec.x, this->y - vec.y, this->z - vec.z); + } + + template + Vector3 &operator*=(const T2 d) { this->x *= d; this->y *= d; @@ -80,7 +86,7 @@ namespace BBM } template - Vector3 operator*(T2 d) const + Vector3 operator*(const T2 d) const { return Vector3(this->x * d, this->y * d, this->z * d); } diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index 6dbce849..bfcddef7 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -66,20 +66,8 @@ namespace BBM .addComponent() .addComponent() .addComponent(RAY::ModelAnimations("assets/player/player.iqm"), 1) - .addComponent(2) + .addComponent(1) .addComponent(); - scene->addEntity("cube") - .addComponent(-5, 0, -5) - .addComponent(Vector3f(-5, 0, -5), Vector3f(3, 3, 3), RED) - .addComponent() - .addComponent() - .addComponent([](WAL::Entity &, const WAL::Entity &){}, - [](WAL::Entity &actual, const WAL::Entity &) { - try { - auto &mov = actual.getComponent(); - mov.resetVelocity(); - } catch (std::exception &e) { } - }, 3); scene->addEntity("camera") .addComponent(8, 20, 7) From e624a2f7fecf54886919d3649c67ab35679391fc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 6 Jun 2021 17:26:05 +0200 Subject: [PATCH 18/22] Fixing unit test compilation --- tests/CollisionTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp index ce153238..394b1ccc 100644 --- a/tests/CollisionTest.cpp +++ b/tests/CollisionTest.cpp @@ -5,12 +5,12 @@ #include #include "Entity/Entity.hpp" #include "Component/Position/PositionComponent.hpp" -#include "Component/Movable/MovableComponent.hpp" -#include "System/Movable/MovableSystem.hpp" -#include "System/Collision/CollisionSystem.hpp" #include "Wal.hpp" #define private public +#include "System/Collision/CollisionSystem.hpp" +#include "System/Movable/MovableSystem.hpp" +#include "Component/Movable/MovableComponent.hpp" #include "Component/Collision/CollisionComponent.hpp" using namespace WAL; @@ -76,7 +76,7 @@ TEST_CASE("Collsion test with movable", "[Component][System]") .addComponent([](Entity &actual, const Entity &){}, [](Entity &actual, const Entity &) { try { auto &mov = actual.getComponent(); - mov.resetVelocity(); + mov._velocity = Vector3f(); } catch (std::exception &e) {}; }, 1); Entity &entity = wal.scene->getEntities().front(); From c7554c6b97a8ec8aacfe7ede587fffe3e8ff5b38 Mon Sep 17 00:00:00 2001 From: "arthur.jamet" Date: Mon, 7 Jun 2021 09:39:00 +0200 Subject: [PATCH 19/22] assets for hole on second flor --- assets/map/upper_floor_hole.png | Bin 0 -> 136291 bytes build_web.sh | 2 +- sources/Map/Map.cpp | 4 +++- sources/Map/Map.hpp | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 assets/map/upper_floor_hole.png diff --git a/assets/map/upper_floor_hole.png b/assets/map/upper_floor_hole.png new file mode 100644 index 0000000000000000000000000000000000000000..43da7cd687ba9d0b22361dce415eeaa626b02ace GIT binary patch literal 136291 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 Z|}xur!s50zMqIkuD!3hBasoCf_C^$eBhSKjUn0 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 VvO>HMQzPI scene) { @@ -158,6 +159,7 @@ namespace BBM { static const std::string holeObj = holePath + objExtension; static const std::string holePng = holePath + imageExtension; + static const std::string secondFloor = secondFloorHolePath + imageExtension; scene->addEntity("Hole Block") .addComponent(Vector3f(coords.x, coords.y - 1, coords.z)) diff --git a/sources/Map/Map.hpp b/sources/Map/Map.hpp index f08f25bd..eaf4415d 100644 --- a/sources/Map/Map.hpp +++ b/sources/Map/Map.hpp @@ -154,6 +154,8 @@ namespace BBM static const std::string holePath; + static const std::string MapGenerator::secondFloorHolePath; + public: //! @param width Width of the map From 95761b75b7cf11a5dc0406b701481f34cc6ca59c Mon Sep 17 00:00:00 2001 From: "arthur.jamet" Date: Mon, 7 Jun 2021 10:17:53 +0200 Subject: [PATCH 20/22] second floor hole asset --- assets/map/upper_floor_hole.mtl | 13 ++++++++ assets/map/upper_floor_hole.obj | 54 +++++++++++++++++++++++++++++++++ sources/Map/Map.cpp | 18 +++++++---- sources/Map/Map.hpp | 2 +- 4 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 assets/map/upper_floor_hole.mtl create mode 100644 assets/map/upper_floor_hole.obj diff --git a/assets/map/upper_floor_hole.mtl b/assets/map/upper_floor_hole.mtl new file mode 100644 index 00000000..829daba3 --- /dev/null +++ b/assets/map/upper_floor_hole.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl floor +Ns 225.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd upper_floor_hole.png diff --git a/assets/map/upper_floor_hole.obj b/assets/map/upper_floor_hole.obj new file mode 100644 index 00000000..2042783c --- /dev/null +++ b/assets/map/upper_floor_hole.obj @@ -0,0 +1,54 @@ +# Blender v2.92.0 OBJ File: '' +# www.blender.org +mtllib upper_floor_hole.mtl +o floor +v -0.500000 -0.499144 0.500854 +v -0.500000 -0.500854 -0.499144 +v 0.500000 -0.499144 0.500855 +v 0.500000 -0.500855 -0.499144 +v 0.500000 0.500854 0.499144 +v 0.500000 0.499144 -0.500854 +v -0.500000 0.500855 0.499144 +v -0.500000 0.499144 -0.500855 +vt 0.000977 0.667790 +vt 0.000977 0.333151 +vt 0.333659 0.667790 +vt 0.333659 0.333151 +vt 0.666341 0.667790 +vt 0.666341 0.333151 +vt 0.333659 0.667790 +vt 0.333659 1.002429 +vt 0.000977 0.667790 +vt 0.000977 1.002429 +vt 0.666341 0.667790 +vt 0.666341 1.002429 +vt 0.333659 0.667790 +vt 0.333659 1.002429 +vt 0.666341 0.333151 +vt 0.999024 0.333151 +vt 0.666341 0.667790 +vt 0.999024 0.667790 +vt 0.999024 1.002429 +vt 0.666341 1.002429 +vt 0.999024 0.667790 +vt 0.666341 0.667790 +vn -0.0000 -1.0000 0.0017 +vn 1.0000 -0.0000 0.0000 +vn 0.0000 1.0000 -0.0017 +vn -1.0000 0.0000 -0.0000 +vn 0.0000 -0.0017 -1.0000 +vn -0.0000 0.0017 1.0000 +usemtl floor +s 1 +f 1/1/1 2/2/1 3/3/1 +f 3/3/1 2/2/1 4/4/1 +f 3/3/2 4/4/2 5/5/2 +f 5/5/2 4/4/2 6/6/2 +f 5/7/3 6/8/3 7/9/3 +f 7/9/3 6/8/3 8/10/3 +f 7/11/4 8/12/4 1/13/4 +f 1/13/4 8/12/4 2/14/4 +f 2/15/5 8/16/5 4/17/5 +f 4/17/5 8/16/5 6/18/5 +f 7/19/6 1/20/6 5/21/6 +f 5/21/6 1/20/6 3/22/6 diff --git a/sources/Map/Map.cpp b/sources/Map/Map.cpp index 8fdb1a37..34eeada7 100644 --- a/sources/Map/Map.cpp +++ b/sources/Map/Map.cpp @@ -19,8 +19,8 @@ namespace BBM const std::string MapGenerator::secondFloorPath = MapGenerator::wallAssetsPath + "upper_floor"; const std::string MapGenerator::stairsPath = MapGenerator::wallAssetsPath + "stairs"; const std::string MapGenerator::bumperPath = MapGenerator::wallAssetsPath + "bumper"; - const std::string MapGenerator::secondFloorPath = MapGenerator::wallAssetsPath + "hole"; - const std::string MapGenerator::secondFloorHolePath = secondFloorPath + "_hole"; + const std::string MapGenerator::holePath = MapGenerator::wallAssetsPath + "hole"; + const std::string MapGenerator::secondFloorHolePath = MapGenerator::secondFloorPath + "_hole"; void MapGenerator::generateUnbreakableBlock(int width, int height, std::shared_ptr scene) { @@ -159,11 +159,17 @@ namespace BBM { static const std::string holeObj = holePath + objExtension; static const std::string holePng = holePath + imageExtension; - static const std::string secondFloor = secondFloorHolePath + imageExtension; + static const std::string secondFloorObj = secondFloorHolePath + objExtension; + static const std::string secondFloorPng = secondFloorHolePath + imageExtension; - scene->addEntity("Hole Block") - .addComponent(Vector3f(coords.x, coords.y - 1, coords.z)) - .addComponent(holeObj, std::make_pair(MAP_DIFFUSE, holePng)); + WAL::Entity &holeEntity = scene->addEntity("Hole Block"); + + holeEntity.addComponent(Vector3f(coords.x, coords.y - 1, coords.z)); + + if (coords.y == 0) + holeEntity.addComponent(holeObj, std::make_pair(MAP_DIFFUSE, holePng)); + else + holeEntity.addComponent(secondFloorObj, std::make_pair(MAP_DIFFUSE, secondFloorPng)); /*.addComponent([](WAL::Entity &other, const WAL::Entity &entity) { if (other.hasComponent()) { auto &health = other.getComponent(); diff --git a/sources/Map/Map.hpp b/sources/Map/Map.hpp index eaf4415d..061a3a69 100644 --- a/sources/Map/Map.hpp +++ b/sources/Map/Map.hpp @@ -154,7 +154,7 @@ namespace BBM static const std::string holePath; - static const std::string MapGenerator::secondFloorHolePath; + static const std::string secondFloorHolePath; public: From f47dd669f12795c9fd13ace9ce8324c50bb4dbe5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Jun 2021 11:37:16 +0200 Subject: [PATCH 21/22] Collisions are still broke --- lib/wal/sources/Wal.hpp | 2 +- sources/Map/Map.cpp | 2 +- sources/Runner/Runner.cpp | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/wal/sources/Wal.hpp b/lib/wal/sources/Wal.hpp index 85a7804e..de6bb394 100644 --- a/lib/wal/sources/Wal.hpp +++ b/lib/wal/sources/Wal.hpp @@ -84,7 +84,7 @@ namespace WAL //! @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(16); + static constexpr std::chrono::nanoseconds timestep = std::chrono::milliseconds(32); //! @brief Create a new system in place. //! @return The wal instance used to call this function is returned. This allow method chaining. diff --git a/sources/Map/Map.cpp b/sources/Map/Map.cpp index 722f0433..c6b237ff 100644 --- a/sources/Map/Map.cpp +++ b/sources/Map/Map.cpp @@ -19,7 +19,7 @@ namespace BBM auto &pos = entity.getComponent(); const auto &wallPos = wall.getComponent(); auto diff = pos.position + mov->getVelocity() - wallPos.position; - std::cout << diff << std::endl; +// mov->_velocity = Vector3f(); if (diff.x <= 0 && mov->_velocity.x < 0) mov->_velocity.x = 0; if (diff.x >= 0 && mov->_velocity.x > 0) diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index bfcddef7..16c2a1a7 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -72,6 +72,12 @@ namespace BBM scene->addEntity("camera") .addComponent(8, 20, 7) .addComponent(Vector3f(8, 0, 8)); +// scene->addEntity("cube") +// .addComponent(5, 0, 5) +// .addComponent(Vector3f(-5, 0, -5), Vector3f(3, 3, 3), RED) +// .addComponent() +// .addComponent() +// .addComponent(WAL::Callback(), &MapGenerator::wallCollide, 3); std::srand(std::time(nullptr)); MapGenerator::loadMap(16, 16, MapGenerator::createMap(16, 16), scene); return scene; From d8bb3be7df1607f19b650f1f478a14cd64a61c16 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Jun 2021 14:51:25 +0200 Subject: [PATCH 22/22] Removing usless empty callback --- lib/wal/sources/Models/Callback.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/wal/sources/Models/Callback.hpp b/lib/wal/sources/Models/Callback.hpp index 2c988db4..dc12a18b 100644 --- a/lib/wal/sources/Models/Callback.hpp +++ b/lib/wal/sources/Models/Callback.hpp @@ -64,7 +64,4 @@ namespace WAL this->addCallback(callback); } }; - - template - static constexpr Callback EmptyCallback; } // namespace WAL \ No newline at end of file