From 7f1850cfd22be03d40507e9ed5f49640027b73f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9au?= <theau.baton@etu.univ-amu.fr> Date: Tue, 14 Jan 2025 16:48:50 +0100 Subject: [PATCH] Update Physic and Graphic Resolvers --- .../engine/graphics/front/engine/Engine.cpp | 10 +++++ .../engine/graphics/front/engine/Engine.hpp | 1 + source/engine/graphics/front/engine/Layer.hpp | 8 ++++ source/engine/physic/front/engine/Engine.cpp | 20 ++++++++++ source/engine/physic/front/engine/Engine.hpp | 3 ++ source/kernel/back/engine/Engine.hpp | 3 +- source/kernel/back/engine/GraphicEngine.cpp | 12 ++++-- source/kernel/back/engine/GraphicEngine.hpp | 9 ++--- source/kernel/back/engine/PhysicEngine.cpp | 9 +++++ source/kernel/back/engine/PhysicEngine.hpp | 6 ++- source/kernel/back/props/Component.hpp | 3 +- source/kernel/back/props/Physical.hpp | 4 +- source/kernel/front/Kernel.cpp | 12 +++--- source/kernel/front/Kernel.hpp | 3 ++ .../kernel/front/component/physic/Fixed.cpp | 4 +- .../kernel/front/component/physic/Fixed.hpp | 2 +- .../front/component/physic/FixedArray.cpp | 37 +++++++++++++++++++ .../front/component/physic/FixedArray.hpp | 27 ++++++++++++++ .../kernel/front/component/physic/Movable.cpp | 4 +- .../kernel/front/component/physic/Movable.hpp | 2 +- .../kernel/front/resolver/GraphicResolver.cpp | 9 ++--- .../kernel/front/resolver/GraphicResolver.hpp | 4 +- .../kernel/front/resolver/PhysicResolver.cpp | 19 +++------- .../kernel/front/resolver/PhysicResolver.hpp | 4 +- source/kernel/front/resolver/Resolver.hpp | 18 ++++++++- source/kernel/front/resolver/Resolver.tpp | 23 ++++++++++++ 26 files changed, 205 insertions(+), 51 deletions(-) create mode 100644 source/kernel/front/component/physic/FixedArray.cpp create mode 100644 source/kernel/front/component/physic/FixedArray.hpp create mode 100644 source/kernel/front/resolver/Resolver.tpp diff --git a/source/engine/graphics/front/engine/Engine.cpp b/source/engine/graphics/front/engine/Engine.cpp index a47862c..9adda5a 100644 --- a/source/engine/graphics/front/engine/Engine.cpp +++ b/source/engine/graphics/front/engine/Engine.cpp @@ -32,6 +32,16 @@ namespace megu { return {}; } + std::optional<std::reference_wrapper<const Renderable>> GraphicEngine::get(const Identifiable & id) const { + for(const auto & [p, layer] : this->_layers) { + auto renderable = layer->get(id); + if(renderable.has_value()) { + return renderable; + } + } + return {}; + } + void GraphicEngine::step() { if(this->_window.isOpen()) { // Draw Layers diff --git a/source/engine/graphics/front/engine/Engine.hpp b/source/engine/graphics/front/engine/Engine.hpp index 7d83972..f85a336 100644 --- a/source/engine/graphics/front/engine/Engine.hpp +++ b/source/engine/graphics/front/engine/Engine.hpp @@ -30,6 +30,7 @@ namespace megu { void remove(Priority); std::optional<std::reference_wrapper<const Layer>> get(Priority) const; + std::optional<std::reference_wrapper<const Renderable>> get(const Identifiable &) const; inline bool empty() const {return this->_layers.empty();} inline const std::map<Priority, std::unique_ptr<Layer>> & layers() const {return this->_layers;} diff --git a/source/engine/graphics/front/engine/Layer.hpp b/source/engine/graphics/front/engine/Layer.hpp index a1dbcce..5b6feba 100644 --- a/source/engine/graphics/front/engine/Layer.hpp +++ b/source/engine/graphics/front/engine/Layer.hpp @@ -37,6 +37,14 @@ namespace megu { this->_objects[priority] = renderable; }*/ + std::optional<std::reference_wrapper<const Renderable>> get(const Identifiable & id) const { + for(const auto & [priority, object] : this->_objects) { + if(object.id() == id.id()) { + return object; + } + } + } + const Texture & draw(const Window & w, const TextureArray & a) { this->_frameBuffer.bind(); this->_renderer.clear(); diff --git a/source/engine/physic/front/engine/Engine.cpp b/source/engine/physic/front/engine/Engine.cpp index 5b21c12..6aa664b 100644 --- a/source/engine/physic/front/engine/Engine.cpp +++ b/source/engine/physic/front/engine/Engine.cpp @@ -43,4 +43,24 @@ namespace megu { } } } + + std::optional<std::reference_wrapper<const Tangible>> PhysicEngine::get(const Identifiable & id) const { + for(auto & [priotiy, objects] : this->_statics) { + for(auto & object : objects) { + if(object.get().id() == id.id()) { + return object.get(); + } + } + } + + for(auto & [priotiy, objects] : this->_dynamic) { + for(auto & object : objects) { + if(object.get().id() == id.id()) { + return object.get(); + } + } + } + + return {}; + } } \ No newline at end of file diff --git a/source/engine/physic/front/engine/Engine.hpp b/source/engine/physic/front/engine/Engine.hpp index 79bbabd..b1db159 100644 --- a/source/engine/physic/front/engine/Engine.hpp +++ b/source/engine/physic/front/engine/Engine.hpp @@ -3,6 +3,7 @@ #include <vector> #include <map> #include <set> +#include <optional> #include <engine/physic/front/object/Tangible.hpp> #include <engine/physic/front/object/TangibleStatic.hpp> @@ -24,6 +25,8 @@ namespace megu { void step(double); void step(double, Priority); + std::optional<std::reference_wrapper<const Tangible>> get(const Identifiable &) const; + inline void clearCollision() {this->_collisions.clear();} const std::set<Collision, reference_sorter<Collision>> & collision() const {return this->_collisions;} diff --git a/source/kernel/back/engine/Engine.hpp b/source/kernel/back/engine/Engine.hpp index 6a8111d..b69f67f 100644 --- a/source/kernel/back/engine/Engine.hpp +++ b/source/kernel/back/engine/Engine.hpp @@ -4,7 +4,7 @@ namespace megu::kernel { class Props; class Kernel; - template <class T, class C> + template <class T, class C, class O> class Engine { public: virtual void boot(Kernel &) = 0; @@ -12,6 +12,7 @@ namespace megu::kernel { virtual void step(Kernel &, double) = 0; virtual T & get() = 0; + virtual const O & get(const C &) const = 0; virtual void add(Kernel &, C &) = 0; }; diff --git a/source/kernel/back/engine/GraphicEngine.cpp b/source/kernel/back/engine/GraphicEngine.cpp index 314f19b..2ee0f70 100644 --- a/source/kernel/back/engine/GraphicEngine.cpp +++ b/source/kernel/back/engine/GraphicEngine.cpp @@ -4,12 +4,10 @@ namespace megu::kernel { GraphicEngine::GraphicEngine(Window & window) - : _engine(window), _renderer(360, 360), _tmp_ta("assets/textures/Tile_Test_3.png", 1, 1, 32.f) {} + : _engine(window), _renderer(360, 360) {} void GraphicEngine::boot(Kernel &) { this->_engine.push(0, this->_renderer); - - //this->_engine.push(0, 0, this->_tmp_ta, &this->_tmp_mod); } void GraphicEngine::stop(Kernel &) { @@ -23,4 +21,12 @@ namespace megu::kernel { void GraphicEngine::add(Kernel & kernel, Graphical<GraphicEngine> & graphical) { graphical.apply(kernel, *this); } + + const Renderable & GraphicEngine::get(const Graphical<GraphicEngine> & graphical) const { + auto object = this->_engine.get(graphical); + if(object.has_value()) { + return object.value(); + } + throw std::runtime_error("Cannot find in physic object : " + graphical.id()); + } } \ No newline at end of file diff --git a/source/kernel/back/engine/GraphicEngine.hpp b/source/kernel/back/engine/GraphicEngine.hpp index 885d7a5..41d8546 100644 --- a/source/kernel/back/engine/GraphicEngine.hpp +++ b/source/kernel/back/engine/GraphicEngine.hpp @@ -13,17 +13,19 @@ #include <kernel/back/props/Graphical.hpp> namespace megu::kernel { - class GraphicEngine : public Engine<megu::GraphicEngine, Graphical<GraphicEngine>> { + class GraphicEngine : public Engine<megu::GraphicEngine, Graphical<GraphicEngine>, Renderable> { public: GraphicEngine(Window &); + inline megu::GraphicEngine & get() {return this->_engine;} + void boot(Kernel &) override; void stop(Kernel &) override; void step(Kernel &, double) override; void add(Kernel &, Graphical<GraphicEngine> &) override; - inline megu::GraphicEngine & get() {return this->_engine;} + const megu::Renderable & get(const Graphical<GraphicEngine> &) const override; inline const megu::GraphicEngine & engine() const {return this->_engine;} inline const megu::Renderer & renderer() const {return this->_renderer;} @@ -31,8 +33,5 @@ namespace megu::kernel { private: megu::GraphicEngine _engine; megu::Renderer _renderer; - - megu::TileArray _tmp_ta; - megu::TileArray_Module _tmp_mod; }; } \ No newline at end of file diff --git a/source/kernel/back/engine/PhysicEngine.cpp b/source/kernel/back/engine/PhysicEngine.cpp index f679407..25e1c3a 100644 --- a/source/kernel/back/engine/PhysicEngine.cpp +++ b/source/kernel/back/engine/PhysicEngine.cpp @@ -21,4 +21,13 @@ namespace megu::kernel { void PhysicEngine::add(Kernel & kernel, Physical<PhysicEngine> & props) { props.apply(kernel, *this); } + + const megu::Tangible & PhysicEngine::get(const Physical<PhysicEngine> & props) const { + auto tangible = this->_engine.get(props); + if(tangible.has_value()) { + return tangible.value(); + } + throw std::runtime_error("Cannot find in physic object : " + props.id()); + + } } \ No newline at end of file diff --git a/source/kernel/back/engine/PhysicEngine.hpp b/source/kernel/back/engine/PhysicEngine.hpp index d1c100f..38919d9 100644 --- a/source/kernel/back/engine/PhysicEngine.hpp +++ b/source/kernel/back/engine/PhysicEngine.hpp @@ -6,17 +6,19 @@ #include <kernel/back/props/Physical.hpp> namespace megu::kernel { - class PhysicEngine : public Engine<megu::PhysicEngine, Physical<PhysicEngine>> { + class PhysicEngine : public Engine<megu::PhysicEngine, Physical<PhysicEngine>, Tangible> { public: PhysicEngine(); + inline megu::PhysicEngine & get() override {return this->_engine;} + void boot(Kernel &) override; void stop(Kernel &) override; void step(Kernel &, double) override; void add(Kernel &, Physical<PhysicEngine> &) override; - inline megu::PhysicEngine & get() override {return this->_engine;} + const megu::Tangible & get(const Physical<PhysicEngine> &) const override; private: megu::PhysicEngine _engine; diff --git a/source/kernel/back/props/Component.hpp b/source/kernel/back/props/Component.hpp index 229e22b..2a3538a 100644 --- a/source/kernel/back/props/Component.hpp +++ b/source/kernel/back/props/Component.hpp @@ -1,12 +1,13 @@ #pragma once #include <kernel/back/engine/Engine.hpp> +#include <utility/Identifiable.hpp> namespace megu::kernel { class Kernel; template <class E> - class Component { + class Component : public virtual Identifiable { public: virtual void apply(Kernel & k, E &) = 0; }; diff --git a/source/kernel/back/props/Physical.hpp b/source/kernel/back/props/Physical.hpp index df85056..92d17ce 100644 --- a/source/kernel/back/props/Physical.hpp +++ b/source/kernel/back/props/Physical.hpp @@ -9,8 +9,8 @@ namespace megu::kernel { template <class Pe> class Physical : public Component<Pe> { public: - virtual void on_collide(double, const Identifiable &, Physical &) = 0; + virtual void on_collide(const Kernel &, const Pe &, Physical &, double) = 0; - using CollideLambda = std::function<void(double, const Identifiable &, Physical &)>; + using CollideLambda = std::function<void(const Kernel &, const Pe &, const Physical &, double)>; }; } \ No newline at end of file diff --git a/source/kernel/front/Kernel.cpp b/source/kernel/front/Kernel.cpp index e5ab814..84cd925 100644 --- a/source/kernel/front/Kernel.cpp +++ b/source/kernel/front/Kernel.cpp @@ -13,15 +13,15 @@ namespace megu::kernel { } void Kernel::step() { - double delta = Window::Time(); + double time = Window::Time(); if(this->_window.isOpen()) { - this->_gEngine.step(*this, delta); - this->_gResolver.resolve(delta, this->_gEngine, this->_props); + this->_gEngine.step(*this, time); + this->_gResolver.resolve(*this, this->_gEngine, time); } - this->_pEngine.step(*this, delta); - this->_pResolver.resolve(delta, this->_pEngine, this->_props); + this->_pEngine.step(*this, time); + this->_pResolver.resolve(*this, this->_pEngine, time); } void Kernel::add(Props * props) { @@ -29,11 +29,13 @@ namespace megu::kernel { auto * pComponent = props->getPhysicComponent(); if(pComponent != nullptr) { this->_pEngine.add(*this, *pComponent); + this->_pResolver.add(*pComponent); } auto * gComponent = props->getGraphicComponent(); if(gComponent != nullptr) { this->_gEngine.add(*this, *gComponent); + this->_gResolver.add(*gComponent); } } } \ No newline at end of file diff --git a/source/kernel/front/Kernel.hpp b/source/kernel/front/Kernel.hpp index 7ba3eac..6a89a50 100644 --- a/source/kernel/front/Kernel.hpp +++ b/source/kernel/front/Kernel.hpp @@ -22,6 +22,9 @@ namespace megu::kernel { inline Identifiable_Map<Props> & props() {return this->_props;} + inline PhysicEngine & getPhysicEngine() {return this->_pEngine;} + inline GraphicEngine & getGraphicEngine() {return this->_gEngine;} + private: Window & _window; diff --git a/source/kernel/front/component/physic/Fixed.cpp b/source/kernel/front/component/physic/Fixed.cpp index f967059..e981a42 100644 --- a/source/kernel/front/component/physic/Fixed.cpp +++ b/source/kernel/front/component/physic/Fixed.cpp @@ -12,9 +12,9 @@ namespace megu::kernel { } } - void Fixed::on_collide(double time, const Identifiable & identifiable, Physical<PhysicEngine> & physical) { + void Fixed::on_collide(const Kernel & kernel, const PhysicEngine & engine, Physical & physical, double time) { if(this->_collide != nullptr) { - this->_collide(time, identifiable, physical); + this->_collide(kernel, engine, physical, time); } } diff --git a/source/kernel/front/component/physic/Fixed.hpp b/source/kernel/front/component/physic/Fixed.hpp index e40d35e..9439be8 100644 --- a/source/kernel/front/component/physic/Fixed.hpp +++ b/source/kernel/front/component/physic/Fixed.hpp @@ -13,7 +13,7 @@ namespace megu::kernel { Fixed(float x, float y, float w, float h); void update_physic(double) const override; - void on_collide(double, const Identifiable &, Physical<PhysicEngine> &) override; + void on_collide(const Kernel &, const PhysicEngine &, Physical &, double) override; void apply(Kernel & k, PhysicEngine &) override; void setCollideLambda(const CollideLambda &); diff --git a/source/kernel/front/component/physic/FixedArray.cpp b/source/kernel/front/component/physic/FixedArray.cpp new file mode 100644 index 0000000..7482b8d --- /dev/null +++ b/source/kernel/front/component/physic/FixedArray.cpp @@ -0,0 +1,37 @@ +#include "FixedArray.hpp" + +#include <kernel/front/Kernel.hpp> + +namespace megu::kernel { + std::optional<std::reference_wrapper<const TangibleStatic>> FixedArray::at(const Position & position) const { + if(this->_tangibles.contains(position)) { + return this->_tangibles.at(position); + } + return {}; + } + + void FixedArray::push(const Fixed & tangible) { + this->_tangibles.insert({tangible.getPosition(), tangible}); + } + + void FixedArray::erase(const Fixed & tangible) { + this->_tangibles.erase(tangible.getPosition()); + } + + void FixedArray::erase(const Position & position) { + this->_tangibles.erase(position); + } + + void FixedArray::on_collide(const Kernel & kernel, const PhysicEngine & engine, Physical & physical, double time) { + auto & tangible = engine.get(physical); + for(auto & [position, fixed] : this->_tangibles) { + if(fixed.isColliding(tangible)) { + physical.on_collide(kernel, engine, fixed, time); + } + } + } + + void FixedArray::apply(Kernel & kernel, PhysicEngine & engine) { + engine.get().push(0, *this); + } +} \ No newline at end of file diff --git a/source/kernel/front/component/physic/FixedArray.hpp b/source/kernel/front/component/physic/FixedArray.hpp new file mode 100644 index 0000000..473b06b --- /dev/null +++ b/source/kernel/front/component/physic/FixedArray.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include <kernel/back/props/Physical.hpp> +#include <kernel/back/engine/PhysicEngine.hpp> +#include <vector> +#include <functional> + +#include "Fixed.hpp" + +namespace megu::kernel { + class FixedArray : public Physical<PhysicEngine>, public TangibleStatic { + public: + std::optional<std::reference_wrapper<const TangibleStatic>> at(const Position &) const; + + void push(const Fixed &); + void erase(const Fixed &); + void erase(const Position &); + + void on_collide(const Kernel &, const PhysicEngine &, Physical &, double) override; + void apply(Kernel & k, PhysicEngine &) override; + + private: + std::map<Position, Fixed> _tangibles; + CollideLambda _collide; + UpdateLambda _update; + }; +} \ No newline at end of file diff --git a/source/kernel/front/component/physic/Movable.cpp b/source/kernel/front/component/physic/Movable.cpp index 01292d3..0fd7504 100644 --- a/source/kernel/front/component/physic/Movable.cpp +++ b/source/kernel/front/component/physic/Movable.cpp @@ -10,9 +10,9 @@ namespace megu::kernel { } } - void Movable::on_collide(double time, const Identifiable & identifiable, Physical<PhysicEngine> & physical) { + void Movable::on_collide(const Kernel & kernel, const PhysicEngine & engine, Physical & physical, double time) { if(this->_collide != nullptr) { - this->_collide(time, identifiable, physical); + this->_collide(kernel, engine, physical, time); } } diff --git a/source/kernel/front/component/physic/Movable.hpp b/source/kernel/front/component/physic/Movable.hpp index 48fd907..f74c85b 100644 --- a/source/kernel/front/component/physic/Movable.hpp +++ b/source/kernel/front/component/physic/Movable.hpp @@ -13,7 +13,7 @@ namespace megu::kernel { Movable(float x, float y, float w, float h); void update_physic(double) override; - void on_collide(double, const Identifiable &, Physical<PhysicEngine> &) override; + void on_collide(const Kernel &, const PhysicEngine &, Physical &, double) override; void apply(Kernel & k, PhysicEngine &) override; void setCollideLambda(CollideLambda &); diff --git a/source/kernel/front/resolver/GraphicResolver.cpp b/source/kernel/front/resolver/GraphicResolver.cpp index a436473..b0c2f45 100644 --- a/source/kernel/front/resolver/GraphicResolver.cpp +++ b/source/kernel/front/resolver/GraphicResolver.cpp @@ -1,12 +1,9 @@ #include "GraphicResolver.hpp" namespace megu::kernel { - void GraphicResolver::resolve(double time, GraphicEngine &, const Identifiable_Map<Props> & props) { - for(auto & [id, object] : props) { - auto * component = object->getGraphicComponent(); - if(component != nullptr) { - component->update(time); - } + void GraphicResolver::resolve(Kernel & kernel, GraphicEngine & engine, double time) { + for(auto & component : this->components()) { + component.get().update(time); } } } \ No newline at end of file diff --git a/source/kernel/front/resolver/GraphicResolver.hpp b/source/kernel/front/resolver/GraphicResolver.hpp index e1fe0ea..7e2b541 100644 --- a/source/kernel/front/resolver/GraphicResolver.hpp +++ b/source/kernel/front/resolver/GraphicResolver.hpp @@ -5,8 +5,8 @@ #include <kernel/back/props/Props.hpp> namespace megu::kernel { - class GraphicResolver : public Resolver<GraphicEngine, Props> { + class GraphicResolver : public Resolver<GraphicEngine, Graphical<GraphicEngine>> { public: - void resolve(double, GraphicEngine &, const Identifiable_Map<Props> &) override; + void resolve(Kernel &, GraphicEngine &, double) override; }; } \ No newline at end of file diff --git a/source/kernel/front/resolver/PhysicResolver.cpp b/source/kernel/front/resolver/PhysicResolver.cpp index 81ed40e..f2b5431 100644 --- a/source/kernel/front/resolver/PhysicResolver.cpp +++ b/source/kernel/front/resolver/PhysicResolver.cpp @@ -1,24 +1,15 @@ #include "PhysicResolver.hpp" namespace megu::kernel { - void PhysicResolver::resolve(double time, PhysicEngine & engine, const Identifiable_Map<Props> & props) { + void PhysicResolver::resolve(Kernel & kernel, PhysicEngine & engine, double time) { auto & collisions = engine.get().collision(); for(auto & collision : collisions) { - const Tangible & source = collision.source(); - const Tangible & target = collision.target(); + auto source_comp = this->get(collision.source()); + auto target_comp = this->get(collision.target()); - if(props.contains(source.id()) && props.contains(target.id())) { - Props * props_source = props.at(source.id()); - Props * props_target = props.at(target.id()); - - auto * sComponent = props_source->getPhysicComponent(); - auto * tComponent = props_target->getPhysicComponent(); - - if(sComponent != nullptr && tComponent != nullptr) { - sComponent->on_collide(time, target, *tComponent); - } - + if(source_comp.has_value() && target_comp.has_value()) { + source_comp.value().get().on_collide(kernel, engine, target_comp.value().get(), time); } } diff --git a/source/kernel/front/resolver/PhysicResolver.hpp b/source/kernel/front/resolver/PhysicResolver.hpp index 0cd194d..349c6de 100644 --- a/source/kernel/front/resolver/PhysicResolver.hpp +++ b/source/kernel/front/resolver/PhysicResolver.hpp @@ -5,8 +5,8 @@ #include <kernel/back/props/Props.hpp> namespace megu::kernel { - class PhysicResolver : public Resolver<PhysicEngine, Props> { + class PhysicResolver : public Resolver<PhysicEngine, Physical<PhysicEngine>> { public: - void resolve(double, PhysicEngine &, const Identifiable_Map<Props> &) override; + void resolve(Kernel &, PhysicEngine &, double) override; }; } \ No newline at end of file diff --git a/source/kernel/front/resolver/Resolver.hpp b/source/kernel/front/resolver/Resolver.hpp index d7484af..35b4732 100644 --- a/source/kernel/front/resolver/Resolver.hpp +++ b/source/kernel/front/resolver/Resolver.hpp @@ -1,11 +1,25 @@ #pragma once #include <utility/Identifiable.hpp> +#include <kernel/back/props/Component.hpp> +#include <engine/utility/ref_set.hpp> +#include <optional> namespace megu::kernel { template <class E, class O> class Resolver { public: - virtual void resolve(double, E &, const Identifiable_Map<O> &) = 0; + inline ref_set<O> & components() {return this->_components;} + + void add(O &); + void erase(const Identifiable &); + std::optional<std::reference_wrapper<O>> get(const Identifiable &); + + virtual void resolve(Kernel &, E &, double) = 0; + + private: + ref_set<O> _components; }; -} \ No newline at end of file +} + +#include "Resolver.tpp" \ No newline at end of file diff --git a/source/kernel/front/resolver/Resolver.tpp b/source/kernel/front/resolver/Resolver.tpp new file mode 100644 index 0000000..0a4beb0 --- /dev/null +++ b/source/kernel/front/resolver/Resolver.tpp @@ -0,0 +1,23 @@ +#include "Resolver.hpp" + +namespace megu::kernel { + template <class E, class O> + void Resolver<E, O>::add(O & c) { + this->_components.insert(c); + } + + template <class E, class O> + void Resolver<E, O>::erase(const Identifiable & c) { + this->_components.erase(c); + } + + template <class E, class O> + std::optional<std::reference_wrapper<O>> Resolver<E, O>::get(const Identifiable & c) { + for(auto & comp : this->_components) { + if(comp.get().id() == c.id()) { + return comp; + } + } + return {}; + } +} \ No newline at end of file -- GitLab