diff options
109 files changed, 3516 insertions, 1027 deletions
diff --git a/.editorconfig b/.editorconfig index 191871f..65f5034 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,5 +10,5 @@ max_line_length = 95  indent_style = space  indent_size = 2 -[*.md] +[*.{md,dox}]  max_line_length = 80 @@ -14,12 +14,22 @@ RECURSIVE = YES  GENERATE_LATEX = NO +LAYOUT_FILE = src/doc/layout.xml +TAB_SIZE = 2 + +HTML_INDEX_NUM_ENTRIES = 2 +HTML_EXTRA_STYLESHEET = src/doc/style.css +SHOW_HEADERFILE = NO +  USE_MDFILE_AS_MAINPAGE = ./readme.md -HTML_INDEX_NUM_ENTRIES = 1 # collapse trees by default  REPEAT_BRIEF = NO -INTERNAL_DOCS = YES  EXTRACT_STATIC = YES +HIDE_UNDOC_NAMESPACES = YES +HIDE_UNDOC_CLASSES = YES  QUIET = YES +# set these to NO for user-only docs +INTERNAL_DOCS = YES +EXTRACT_PRIVATE = YES diff --git a/asset/texture/ERROR.png b/asset/texture/ERROR.png Binary files differnew file mode 100644 index 0000000..2af3548 --- /dev/null +++ b/asset/texture/ERROR.png diff --git a/contributing.md b/contributing.md index e4f075f..77a2908 100644 --- a/contributing.md +++ b/contributing.md @@ -24,7 +24,7 @@ that you can click on to open them.  # Code style  - Formatting nitty-gritty is handled by clang-format/clang-tidy (run `make -  format` in the root folder of this repository to format all sources files) +  format` or `make lint`)  - <details><summary>    ASCII only    </summary><table><tr><th>Good</th><th>Bad</th></tr><tr><td> @@ -803,6 +803,27 @@ that you can click on to open them.    resolving merge conflicts when multiple sources were added by different    people to the same CMakeLists.txt easier. +## GoogleTest-specific + +- Unit tests are not *required* to follow all code standards +- <details><summary> +  Private/protected members may be accessed using preprocessor tricks +  </summary> + +  ```cpp +  // include unrelated headers before + +  #define private public +  #define protected public + +  // headers included after *will* be affected +  ``` +  </details> +- Each test source file defines tests within a single test suite (first +  parameter of `TEST()` / `TEST_F()` macro) +- Test source files match their suite name (or test fixture name in the case of +  tests that use a fixture) +  # Structure  - Files are placed in the appropriate directory: @@ -6,5 +6,6 @@ doxygen: Doxyfile FORCE  FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp')  format: FORCE  	clang-format -i $(FMT) -	$(MAKE) -C src $@ +lint: FORCE +	$(MAKE) -C src $@ @@ -32,6 +32,7 @@ This project uses the following libraries  |`SoLoud`|(latest git `master` version)|  |Google Test (`GTest`)|1.15.2|  |Berkeley DB (`libdb`)|5.3.21| +|Where Am I?|(latest git `master` version)  > [!NOTE]  > Most of these libraries are likely available from your package manager if you @@ -49,6 +50,11 @@ $ git submodule update --init --recursive --depth 1  Then, follow these steps for each library you want to install: +> [!IMPORTANT] +> A dollar sign prompt (`$`) indicates commands to be run as a regular user, +> while a hashtag (`#`) is used to denote commands that must be run with +> privileges (e.g. as root or using `sudo`). +  1. Change into the library folder (run **one** of these):     ```     $ cd lib/googletest @@ -56,6 +62,7 @@ Then, follow these steps for each library you want to install:     $ cd lib/soloud/contrib     $ cd lib/sdl_image     $ cd lib/sdl_ttf +   $ cd lib/whereami     ```  2. Use CMake to configure the build, run the build and install (run **all** of     these): diff --git a/src/crepe/Component.h b/src/crepe/Component.h index 2e4ef7d..67bcc68 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -32,6 +32,11 @@ protected:  	//! Only ComponentManager can create components  	friend class ComponentManager; +	Component(const Component &) = delete; +	Component(Component &&) = delete; +	virtual Component & operator=(const Component &) = delete; +	virtual Component & operator=(Component &&) = delete; +  public:  	virtual ~Component() = default; diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp index e310577..e4de027 100644 --- a/src/crepe/ComponentManager.cpp +++ b/src/crepe/ComponentManager.cpp @@ -26,8 +26,7 @@ void ComponentManager::delete_all_components() {  }  GameObject ComponentManager::new_object(const string & name, const string & tag, -										const Vector2 & position, double rotation, -										double scale) { +										const vec2 & position, double rotation, double scale) {  	GameObject object{*this, this->next_id, name, tag, position, rotation, scale};  	this->next_id++;  	return object; diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h index 2107453..1cb0b5f 100644 --- a/src/crepe/ComponentManager.h +++ b/src/crepe/ComponentManager.h @@ -5,9 +5,8 @@  #include <unordered_map>  #include <vector> -#include "api/Vector2.h" -  #include "Component.h" +#include "types.h"  namespace crepe { @@ -44,7 +43,7 @@ public:  	 * \note This method automatically assigns a new entity ID  	 */  	GameObject new_object(const std::string & name, const std::string & tag = "", -						  const Vector2 & position = {0, 0}, double rotation = 0, +						  const vec2 & position = {0, 0}, double rotation = 0,  						  double scale = 1);  protected: @@ -112,7 +111,7 @@ public:  	 * \return A vector of all components of the specific type and id  	 */  	template <typename T> -	std::vector<std::reference_wrapper<T>> get_components_by_id(game_object_id_t id) const; +	RefVector<T> get_components_by_id(game_object_id_t id) const;  	/**  	 * \brief Get all components of a specific type  	 *  @@ -122,7 +121,7 @@ public:  	 * \return A vector of all components of the specific type  	 */  	template <typename T> -	std::vector<std::reference_wrapper<T>> get_components_by_type() const; +	RefVector<T> get_components_by_type() const;  private:  	/** diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/ComponentManager.hpp index be99cac..4d5eaf4 100644 --- a/src/crepe/ComponentManager.hpp +++ b/src/crepe/ComponentManager.hpp @@ -81,15 +81,14 @@ void ComponentManager::delete_components() {  }  template <typename T> -std::vector<std::reference_wrapper<T>> -ComponentManager::get_components_by_id(game_object_id_t id) const { +RefVector<T> ComponentManager::get_components_by_id(game_object_id_t id) const {  	using namespace std;  	// Determine the type of T (this is used as the key of the unordered_map<>)  	type_index type = typeid(T);  	// Create an empty vector<> -	vector<reference_wrapper<T>> component_vector; +	RefVector<T> component_vector;  	if (this->components.find(type) == this->components.end()) return component_vector; @@ -114,14 +113,14 @@ ComponentManager::get_components_by_id(game_object_id_t id) const {  }  template <typename T> -std::vector<std::reference_wrapper<T>> ComponentManager::get_components_by_type() const { +RefVector<T> ComponentManager::get_components_by_type() const {  	using namespace std;  	// Determine the type of T (this is used as the key of the unordered_map<>)  	type_index type = typeid(T);  	// Create an empty vector<> -	vector<reference_wrapper<T>> component_vector; +	RefVector<T> component_vector;  	// Find the type (in the unordered_map<>)  	if (this->components.find(type) == this->components.end()) return component_vector; diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 1068cbf..485a0d4 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,7 +2,7 @@  using namespace crepe; -void Particle::reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity, +void Particle::reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity,  					 double angle) {  	// Initialize the particle state  	this->time_in_life = 0; diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 19859fe..d0397c9 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -2,7 +2,7 @@  #include <cstdint> -#include "api/Vector2.h" +#include "types.h"  namespace crepe { @@ -18,11 +18,11 @@ class Particle {  public:  	//! Position of the particle in 2D space. -	Vector2 position; +	vec2 position;  	//! Velocity vector indicating the speed and direction of the particle. -	Vector2 velocity; +	vec2 velocity;  	//! Accumulated force affecting the particle over time. -	Vector2 force_over_time; +	vec2 force_over_time;  	//! Total lifespan of the particle in milliseconds.  	uint32_t lifespan;  	//! Active state of the particle; true if it is in use, false otherwise. @@ -43,8 +43,7 @@ public:  	 * \param velocity  The initial velocity of the particle.  	 * \param angle     The angle of the particle's trajectory or orientation.  	 */ -	void reset(uint32_t lifespan, const Vector2 & position, const Vector2 & velocity, -			   double angle); +	void reset(uint32_t lifespan, const vec2 & position, const vec2 & velocity, double angle);  	/**  	 * \brief Updates the particle's state.  	 * diff --git a/src/crepe/api/Asset.cpp b/src/crepe/api/Asset.cpp index 5271cf7..e148367 100644 --- a/src/crepe/api/Asset.cpp +++ b/src/crepe/api/Asset.cpp @@ -2,34 +2,33 @@  #include <stdexcept>  #include <whereami.h> -#include "Asset.h"  #include "api/Config.h" +#include "Asset.h" +  using namespace crepe;  using namespace std; -Asset::Asset(const string & src) : src(find_asset(src)) { } -Asset::Asset(const char * src) : src(find_asset(src)) { } +Asset::Asset(const string & src) : src(find_asset(src)) {} +Asset::Asset(const char * src) : src(find_asset(src)) {}  const string & Asset::get_path() const noexcept { return this->src; }  string Asset::find_asset(const string & src) const {  	auto & cfg = Config::get_instance(); -	auto & root_pattern = cfg.asset.root_pattern; +	string & root_pattern = cfg.asset.root_pattern;  	// if root_pattern is empty, find_asset must return all paths as-is  	if (root_pattern.empty()) return src;  	// absolute paths do not need to be resolved, only canonicalized  	filesystem::path path = src; -	if (path.is_absolute()) -		return filesystem::canonical(path); +	if (path.is_absolute()) return filesystem::canonical(path);  	// find directory matching root_pattern  	filesystem::path root = this->whereami();  	while (1) { -		if (filesystem::exists(root / root_pattern)) -			break; +		if (filesystem::exists(root / root_pattern)) break;  		if (!root.has_parent_path())  			throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern));  		root = root.parent_path(); @@ -48,11 +47,8 @@ string Asset::whereami() const noexcept {  	return path;  } -bool Asset::operator==(const Asset & other) const noexcept { -	return this->src == other.src; -} +bool Asset::operator==(const Asset & other) const noexcept { return this->src == other.src; }  size_t std::hash<const Asset>::operator()(const Asset & asset) const noexcept {  	return std::hash<string>{}(asset.get_path());  }; - diff --git a/src/crepe/api/Asset.h b/src/crepe/api/Asset.h index 685dd3a..bfd0ac7 100644 --- a/src/crepe/api/Asset.h +++ b/src/crepe/api/Asset.h @@ -33,13 +33,29 @@ public:  	 * \param other Possibly different instance of \c Asset to test equality against  	 * \return True if \c this and \c other are equal  	 */ -	bool operator == (const Asset & other) const noexcept; +	bool operator==(const Asset & other) const noexcept;  private:  	//! path to asset  	const std::string src;  private: +	/** +	 * \brief Locate asset path, or throw exception if it cannot be found +	 * +	 * This function resolves asset locations relative to crepe::Config::root_pattern if it is +	 * set and \p src is a relative path. If \p src is an absolute path, it is canonicalized. +	 * This function only returns if the file can be found. +	 * +	 * \param src Arbitrary path to resource file +	 * +	 * \returns \p src if crepe::Config::root_pattern is empty +	 * \returns Canonical path to \p src +	 * +	 * \throws std::runtime_error if root_pattern cannot be found +	 * \throws std::filesystem::filesystem_error if the resolved path does not exist +	 * \throws std::filesystem::filesystem_error if the path cannot be canonicalized +	 */  	std::string find_asset(const std::string & src) const;  	/**  	 * \returns The path to the current executable @@ -52,7 +68,8 @@ private:  namespace std {  //! Hash helper struct -template<> struct hash<const crepe::Asset> { +template <> +struct hash<const crepe::Asset> {  	/**  	 * \brief Hash operator for crepe::Asset  	 * @@ -64,5 +81,4 @@ template<> struct hash<const crepe::Asset> {  	size_t operator()(const crepe::Asset & asset) const noexcept;  }; -} - +} // namespace std diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h index 9d85d4c..d556fe5 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -39,11 +39,14 @@ public:  	 * \brief Set the concrete script of this component  	 *  	 * \tparam T Concrete script type (derived from \c crepe::Script) +	 * \tparam Args Arguments for concrete script constructor +	 * +	 * \param args Arguments for concrete script constructor (forwarded using perfect forwarding)  	 *  	 * \returns Reference to BehaviorScript component (`*this`)  	 */ -	template <class T> -	BehaviorScript & set_script(); +	template <class T, typename... Args> +	BehaviorScript & set_script(Args &&... args);  protected:  	//! Script instance diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index d80321d..bd59337 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -9,13 +9,17 @@  namespace crepe { -template <class T> -BehaviorScript & BehaviorScript::set_script() { +template <class T, typename... Args> +BehaviorScript & BehaviorScript::set_script(Args &&... args) {  	dbg_trace();  	static_assert(std::is_base_of<Script, T>::value); -	Script * s = new T(); +	Script * s = new T(std::forward<Args>(args)...); +  	s->game_object_id = this->game_object_id; -	s->component_manager_ref = &this->component_manager; +	s->active = this->active; +	s->component_manager = this->component_manager; +	s->event_manager = EventManager::get_instance(); +  	this->script = std::unique_ptr<Script>(s);  	return *this;  } diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index cca0e8b..7a3ab48 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -14,12 +14,16 @@ target_sources(crepe PUBLIC  	Metadata.cpp  	Scene.cpp  	SceneManager.cpp -	Vector2.cpp  	Camera.cpp  	Animator.cpp +	EventManager.cpp +	IKeyListener.cpp +	IMouseListener.cpp  	LoopManager.cpp  	LoopTimer.cpp  	Asset.cpp +	EventHandler.cpp +	Script.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -33,6 +37,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Rigidbody.h  	Sprite.h  	Vector2.h +	Vector2.hpp  	Color.h  	Texture.h   	ResourceManager.h  @@ -43,6 +48,13 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	SceneManager.hpp  	Camera.h  	Animator.h +	EventManager.h +	EventManager.hpp +	EventHandler.h +	EventHandler.hpp +	Event.h +	IKeyListener.h +	IMouseListener.h  	LoopManager.h  	LoopTimer.h  	Asset.h diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp index 9e5f187..29bd77a 100644 --- a/src/crepe/api/Color.cpp +++ b/src/crepe/api/Color.cpp @@ -2,32 +2,11 @@  using namespace crepe; -Color Color::white = Color(255, 255, 255, 0); -Color Color::red = Color(255, 0, 0, 0); -Color Color::green = Color(0, 255, 0, 0); -Color Color::blue = Color(0, 0, 255, 0); -Color Color::black = Color(0, 0, 0, 0); -Color Color::cyan = Color(0, 255, 255, 0); -Color Color::yellow = Color(255, 255, 0, 0); -Color Color::magenta = Color(255, 0, 255, 0); - -Color::Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { -	this->a = alpha; -	this->r = red; -	this->g = green; -	this->b = blue; -}; - -const Color & Color::get_white() { return Color::white; }; - -const Color & Color::get_red() { return Color::red; }; -const Color & Color::get_green() { return Color::green; }; -const Color & Color::get_blue() { return Color::blue; }; - -const Color & Color::get_black() { return Color::black; }; - -const Color & Color::get_cyan() { return Color::cyan; }; - -const Color & Color::get_yellow() { return Color::yellow; }; - -const Color & Color::get_magenta() { return Color::magenta; }; +const Color Color::WHITE{0xff, 0xff, 0xff}; +const Color Color::RED{0xff, 0x00, 0x00}; +const Color Color::GREEN{0x00, 0xff, 0x00}; +const Color Color::BLUE{0x00, 0x00, 0xff}; +const Color Color::BLACK{0x00, 0x00, 0x00}; +const Color Color::CYAN{0x00, 0xff, 0xff}; +const Color Color::YELLOW{0xff, 0xff, 0x00}; +const Color Color::MAGENTA{0xff, 0x00, 0xff}; diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h index aa47bf4..84edb5c 100644 --- a/src/crepe/api/Color.h +++ b/src/crepe/api/Color.h @@ -4,41 +4,20 @@  namespace crepe { -// TODO: make Color a struct w/o constructors/destructors -class Color { - -	// FIXME: can't these colors be defined as a `static constexpr const Color` -	// instead? - -public: -	Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); -	static const Color & get_white(); -	static const Color & get_red(); -	static const Color & get_green(); -	static const Color & get_blue(); -	static const Color & get_cyan(); -	static const Color & get_magenta(); -	static const Color & get_yellow(); -	static const Color & get_black(); - -private: -	// TODO: why are these private!? -	uint8_t r; -	uint8_t g; -	uint8_t b; -	uint8_t a; - -	static Color white; -	static Color red; -	static Color green; -	static Color blue; -	static Color cyan; -	static Color magenta; -	static Color yellow; -	static Color black; - -private: -	friend class SDLContext; +struct Color { +	uint8_t r = 0x00; +	uint8_t g = 0x00; +	uint8_t b = 0x00; +	uint8_t a = 0xff; + +	static const Color WHITE; +	static const Color RED; +	static const Color GREEN; +	static const Color BLUE; +	static const Color CYAN; +	static const Color MAGENTA; +	static const Color YELLOW; +	static const Color BLACK;  };  } // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h new file mode 100644 index 0000000..b267e3e --- /dev/null +++ b/src/crepe/api/Event.h @@ -0,0 +1,112 @@ +#pragma once +// TODO discussing the location of these events + +#include <string> + +#include "KeyCodes.h" + +namespace crepe { + +/** + * \brief Base class for all event types in the system. + */ +class Event {}; + +/** + * \brief Event triggered when a key is pressed. + */ +class KeyPressEvent : public Event { +public: +	//! false if first time press, true if key is repeated +	bool repeat = false; + +	//! The key that was pressed. +	Keycode key = Keycode::NONE; +}; + +/** + * \brief Event triggered when a key is released. + */ +class KeyReleaseEvent : public Event { +public: +	//! The key that was released. +	Keycode key = Keycode::NONE; +}; + +/** + * \brief Event triggered when a mouse button is pressed. + */ +class MousePressEvent : public Event { +public: +	//! X-coordinate of the mouse position at the time of the event. +	int mouse_x = 0; + +	//! Y-coordinate of the mouse position at the time of the event. +	int mouse_y = 0; + +	//! The mouse button that was pressed. +	MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when a mouse button is clicked (press and release). + */ +class MouseClickEvent : public Event { +public: +	//! X-coordinate of the mouse position at the time of the event. +	int mouse_x = 0; + +	//! Y-coordinate of the mouse position at the time of the event. +	int mouse_y = 0; + +	//! The mouse button that was clicked. +	MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when a mouse button is released. + */ +class MouseReleaseEvent : public Event { +public: +	//! X-coordinate of the mouse position at the time of the event. +	int mouse_x = 0; + +	//! Y-coordinate of the mouse position at the time of the event. +	int mouse_y = 0; + +	//! The mouse button that was released. +	MouseButton button = MouseButton::NONE; +}; + +/** + * \brief Event triggered when the mouse is moved. + */ +class MouseMoveEvent : public Event { +public: +	//! X-coordinate of the mouse position at the time of the event. +	int mouse_x = 0; + +	//! Y-coordinate of the mouse position at the time of the event. +	int mouse_y = 0; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event {}; + +/** + * \brief Event triggered when text is submitted, e.g., from a text input. + */ +class TextSubmitEvent : public Event { +public: +	//! The submitted text. +	std::string text = ""; +}; + +/** + * \brief Event triggered to indicate the application is shutting down. + */ +class ShutDownEvent : public Event {}; + +} // namespace crepe diff --git a/src/crepe/api/EventHandler.cpp b/src/crepe/api/EventHandler.cpp new file mode 100644 index 0000000..4dc232f --- /dev/null +++ b/src/crepe/api/EventHandler.cpp @@ -0,0 +1,5 @@ +#include "EventHandler.h" + +using namespace crepe; + +bool IEventHandlerWrapper::exec(const Event & e) { return this->call(e); } diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h new file mode 100644 index 0000000..ef659fd --- /dev/null +++ b/src/crepe/api/EventHandler.h @@ -0,0 +1,96 @@ +#pragma once + +#include <functional> +#include <string> + +#include "Event.h" + +namespace crepe { +/** + * \brief A type alias for an event handler function. + *  + * The EventHandler is a std::function that takes an EventType reference and returns a boolean value  + * indicating whether the event is handled. + *  + * \tparam EventType The type of event this handler will handle. + *  + * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether. + */ +template <typename EventType> +using EventHandler = std::function<bool(const EventType & e)>; + +/** + * \class IEventHandlerWrapper + * \brief An abstract base class for event handler wrappers. + *  + * This class provides the interface for handling events. Derived classes must implement the + * `call()` method to process events + */ +class IEventHandlerWrapper { +public: +	/** +     * \brief Virtual destructor for IEventHandlerWrapper. +     */ +	virtual ~IEventHandlerWrapper() = default; + +	/** +     * \brief Executes the handler with the given event. +     *  +     * This method calls the `call()` method of the derived class, passing the event to the handler. +     *  +     * \param e The event to be processed. +     * \return A boolean value indicating whether the event is handled. +     */ +	bool exec(const Event & e); + +private: +	/** +     * \brief The method responsible for handling the event. +     *  +     * This method is implemented by derived classes to process the event. +     *  +     * \param e The event to be processed. +     * \return A boolean value indicating whether the event is handled. +     */ +	virtual bool call(const Event & e) = 0; +}; + +/** + * \class EventHandlerWrapper + * \brief A wrapper for event handler functions. + *  + * This class wraps an event handler function of a specific event type. It implements the  + * `call()` and `get_type()` methods to allow the handler to be executed and its type to be  + * queried. + *  + * \tparam EventType The type of event this handler will handle. + */ +template <typename EventType> +class EventHandlerWrapper : public IEventHandlerWrapper { +public: +	/** +     * \brief Constructs an EventHandlerWrapper with a given handler. +     *  +     * The constructor takes an event handler function and stores it in the wrapper. +     *  +     * \param handler The event handler function. +     */ +	explicit EventHandlerWrapper(const EventHandler<EventType> & handler); + +private: +	/** +     * \brief Calls the stored event handler with the event. +     *  +     * This method casts the event to the appropriate type and calls the handler. +     *  +     * \param e The event to be handled. +     * \return A boolean value indicating whether the event is handled. +     */ +	bool call(const Event & e) override; +	//! The event handler function. +	EventHandler<EventType> handler; +}; + +} // namespace crepe + +#include "EventHandler.hpp" diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp new file mode 100644 index 0000000..050e57e --- /dev/null +++ b/src/crepe/api/EventHandler.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include <typeindex> + +#include "EventHandler.h" + +namespace crepe { + +// Implementation of EventHandlerWrapper constructor +template <typename EventType> +EventHandlerWrapper<EventType>::EventHandlerWrapper(const EventHandler<EventType> & handler) +	: handler(handler) {} + +// Implementation of EventHandlerWrapper::call +template <typename EventType> +bool EventHandlerWrapper<EventType>::call(const Event & e) { +	return this->handler(static_cast<const EventType &>(e)); +} + +} //namespace crepe diff --git a/src/crepe/api/EventManager.cpp b/src/crepe/api/EventManager.cpp new file mode 100644 index 0000000..20f0dd3 --- /dev/null +++ b/src/crepe/api/EventManager.cpp @@ -0,0 +1,46 @@ +#include "EventManager.h" + +using namespace crepe; +using namespace std; + +EventManager & EventManager::get_instance() { +	static EventManager instance; +	return instance; +} + +void EventManager::dispatch_events() { +	for (auto & event : this->events_queue) { +		this->handle_event(event.type, event.channel, *event.event.get()); +	} +	this->events_queue.clear(); +} + +void EventManager::handle_event(type_index type, event_channel_t channel, const Event & data) { +	auto handlers_it = this->subscribers.find(type); +	if (handlers_it == this->subscribers.end()) return; + +	vector<CallbackEntry> & handlers = handlers_it->second; +	for (auto & handler : handlers) { +		bool check_channel = handler.channel != CHANNEL_ALL || channel != CHANNEL_ALL; +		if (check_channel && handler.channel != channel) continue; + +		bool handled = handler.callback->exec(data); +		if (handled) return; +	} +} + +void EventManager::clear() { +	this->subscribers.clear(); +	this->events_queue.clear(); +} + +void EventManager::unsubscribe(subscription_t id) { +	for (auto & [event_type, handlers] : this->subscribers) { +		for (auto it = handlers.begin(); it != handlers.end(); it++) { +			// find listener with subscription id +			if ((*it).id != id) continue; +			it = handlers.erase(it); +			return; +		} +	} +} diff --git a/src/crepe/api/EventManager.h b/src/crepe/api/EventManager.h new file mode 100644 index 0000000..1a33023 --- /dev/null +++ b/src/crepe/api/EventManager.h @@ -0,0 +1,161 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "Event.h" +#include "EventHandler.h" + +namespace crepe { + +//! Event listener unique ID +typedef size_t subscription_t; + +/** + * \brief Event channel + * + * Events can be sent to a specific channel, which prevents listeners on other channels from + * being called. The default channel is EventManager::CHANNEL_ALL, which calls all listeners. + */ +typedef size_t event_channel_t; + +/** + * \class EventManager + * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. + *  + * The `EventManager` acts as a centralized event system. It allows for registering callbacks + * for specific event types, triggering events synchronously, queueing events for later + * processing, and managing subscriptions via unique identifiers. + */ +class EventManager { +public: +	static constexpr const event_channel_t CHANNEL_ALL = -1; + +	/** +	 * \brief Get the singleton instance of the EventManager. +	 *  +	 * This method returns the unique instance of the EventManager, creating it if it +	 * doesn't already exist. Ensures only one instance is active in the program. +	 *  +	 * \return Reference to the singleton instance of the EventManager. +	 */ +	static EventManager & get_instance(); + +	/** +	 * \brief Subscribe to a specific event type. +	 *  +	 * Registers a callback for a given event type and optional channel. Each callback +	 * is assigned a unique subscription ID that can be used for later unsubscription. +	 *  +	 * \tparam EventType The type of the event to subscribe to. +	 * \param callback The callback function to be invoked when the event is triggered. +	 * \param channel The channel number to subscribe to (default is CHANNEL_ALL, which listens to all channels). +	 * \return A unique subscription ID associated with the registered callback. +	 */ +	template <typename EventType> +	subscription_t subscribe(const EventHandler<EventType> & callback, +							 event_channel_t channel = CHANNEL_ALL); + +	/** +	 * \brief Unsubscribe a previously registered callback. +	 *  +	 * Removes a callback from the subscription list based on its unique subscription ID. +	 *  +	 * \param event_id The unique subscription ID of the callback to remove. +	 */ +	void unsubscribe(subscription_t event_id); + +	/** +	 * \brief Trigger an event immediately. +	 *  +	 * Synchronously invokes all registered callbacks for the given event type on the specified channel. +	 *  +	 * \tparam EventType The type of the event to trigger. +	 * \param event The event instance to pass to the callbacks. +	 * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). +	 */ +	template <typename EventType> +	void trigger_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); + +	/** +	 * \brief Queue an event for later processing. +	 *  +	 * Adds an event to the event queue to be processed during the next call to `dispatch_events`. +	 *  +	 * \tparam EventType The type of the event to queue. +	 * \param event The event instance to queue. +	 * \param channel The channel to associate with the event (default is CHANNEL_ALL). +	 */ +	template <typename EventType> +	void queue_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); + +	/** +	 * \brief Process all queued events. +	 *  +	 * Iterates through the event queue and triggers callbacks for each queued event. +	 * Events are removed from the queue once processed. +	 */ +	void dispatch_events(); + +	/** +	 * \brief Clear all subscriptions. +	 *  +	 * Removes all registered event handlers and clears the subscription list. +	 */ +	void clear(); + +private: +	/** +	 * \brief Default constructor for the EventManager. +	 *  +	 * Constructor is private to enforce the singleton pattern. +	 */ +	EventManager() = default; + +	/** +	 * \struct QueueEntry +	 * \brief Represents an entry in the event queue. +	 */ +	struct QueueEntry { +		std::unique_ptr<Event> event; ///< The event instance. +		event_channel_t channel = CHANNEL_ALL; ///< The channel associated with the event. +		std::type_index type; ///< The type of the event. +	}; + +	/** +	 * \brief Internal event handler +	 * +	 * This function processes a single event, and is used to process events both during +	 * EventManager::dispatch_events and inside EventManager::trigger_event +	 * +	 * \param type \c typeid of concrete Event class +	 * \param channel Event channel +	 * \param data Event data +	 */ +	void handle_event(std::type_index type, event_channel_t channel, const Event & data); + +	/** +	 * \struct CallbackEntry +	 * \brief Represents a registered event handler callback. +	 */ +	struct CallbackEntry { +		std::unique_ptr<IEventHandlerWrapper> callback; ///< The callback function wrapper. +		event_channel_t channel = CHANNEL_ALL; ///< The channel this callback listens to. +		subscription_t id = -1; ///< Unique subscription ID. +	}; + +	//! The queue of events to be processed during dispatch. +	std::vector<QueueEntry> events_queue; + +	//! A map of event type to registered callbacks. +	std::unordered_map<std::type_index, std::vector<CallbackEntry>> subscribers; + +	//! Counter to generate unique subscription IDs. +	subscription_t subscription_counter = 0; +}; + +} // namespace crepe + +#include "EventManager.hpp" diff --git a/src/crepe/api/EventManager.hpp b/src/crepe/api/EventManager.hpp new file mode 100644 index 0000000..a5f4556 --- /dev/null +++ b/src/crepe/api/EventManager.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "EventManager.h" + +namespace crepe { + +template <typename EventType> +subscription_t EventManager::subscribe(const EventHandler<EventType> & callback, +									   event_channel_t channel) { +	subscription_counter++; +	std::type_index event_type = typeid(EventType); +	std::unique_ptr<EventHandlerWrapper<EventType>> handler +		= std::make_unique<EventHandlerWrapper<EventType>>(callback); +	std::vector<CallbackEntry> & handlers = this->subscribers[event_type]; +	handlers.emplace_back(CallbackEntry{ +		.callback = std::move(handler), .channel = channel, .id = subscription_counter}); +	return subscription_counter; +} + +template <typename EventType> +void EventManager::queue_event(const EventType & event, event_channel_t channel) { +	static_assert(std::is_base_of<Event, EventType>::value, +				  "EventType must derive from Event"); +	this->events_queue.push_back(QueueEntry{ +		.event = std::make_unique<EventType>(event), +		.channel = channel, +		.type = typeid(EventType), +	}); +} + +template <typename EventType> +void EventManager::trigger_event(const EventType & event, event_channel_t channel) { +	this->handle_event(typeid(EventType), channel, event); +} + +} // namespace crepe diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 287e81d..3c36a21 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -9,7 +9,7 @@ using namespace std;  GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id,  					   const std::string & name, const std::string & tag, -					   const Vector2 & position, double rotation, double scale) +					   const vec2 & position, double rotation, double scale)  	: id(id),  	  component_manager(component_manager) { @@ -23,12 +23,10 @@ void GameObject::set_parent(const GameObject & parent) {  	ComponentManager & mgr = this->component_manager;  	// Set parent on own Metadata component -	vector<reference_wrapper<Metadata>> this_metadata -		= mgr.get_components_by_id<Metadata>(this->id); +	RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id);  	this_metadata.at(0).get().parent = parent.id;  	// Add own id to children list of parent's Metadata component -	vector<reference_wrapper<Metadata>> parent_metadata -		= mgr.get_components_by_id<Metadata>(parent.id); +	RefVector<Metadata> parent_metadata = mgr.get_components_by_id<Metadata>(parent.id);  	parent_metadata.at(0).get().children.push_back(this->id);  } diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index 34ef8bb..fcb8d9a 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -2,7 +2,6 @@  #include <string> -#include "Vector2.h"  #include "types.h"  namespace crepe { @@ -30,7 +29,7 @@ private:  	 * \param scale The scale of the GameObject  	 */  	GameObject(ComponentManager & component_manager, game_object_id_t id, -			   const std::string & name, const std::string & tag, const Vector2 & position, +			   const std::string & name, const std::string & tag, const vec2 & position,  			   double rotation, double scale);  	//! ComponentManager instances GameObject  	friend class ComponentManager; diff --git a/src/crepe/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp new file mode 100644 index 0000000..8642655 --- /dev/null +++ b/src/crepe/api/IKeyListener.cpp @@ -0,0 +1,19 @@ +#include "IKeyListener.h" + +using namespace crepe; + +// Constructor with specified channel +IKeyListener::IKeyListener(event_channel_t channel) +	: event_manager(EventManager::get_instance()) { +	this->press_id = event_manager.subscribe<KeyPressEvent>( +		[this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel); +	this->release_id = event_manager.subscribe<KeyReleaseEvent>( +		[this](const KeyReleaseEvent & event) { return this->on_key_released(event); }, +		channel); +} + +// Destructor, unsubscribe events +IKeyListener::~IKeyListener() { +	event_manager.unsubscribe(this->press_id); +	event_manager.unsubscribe(this->release_id); +} diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h new file mode 100644 index 0000000..328a4c2 --- /dev/null +++ b/src/crepe/api/IKeyListener.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Event.h" +#include "EventHandler.h" +#include "EventManager.h" + +namespace crepe { + +/** + * \class IKeyListener + * \brief Interface for keyboard event handling in the application. + */ +class IKeyListener { +public: +	/** +     * \brief Constructs an IKeyListener with a specified channel. +     * \param channel The channel ID for event handling. +     */ +	IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL); +	virtual ~IKeyListener(); +	IKeyListener(const IKeyListener &) = delete; +	IKeyListener & operator=(const IKeyListener &) = delete; +	IKeyListener & operator=(IKeyListener &&) = delete; +	IKeyListener(IKeyListener &&) = delete; + +	/** +     * \brief Pure virtual function to handle key press events. +     * \param event The key press event to handle. +     * \return True if the event was handled, false otherwise. +     */ +	virtual bool on_key_pressed(const KeyPressEvent & event) = 0; + +	/** +     * \brief Pure virtual function to handle key release events. +     * \param event The key release event to handle. +     * \return True if the event was handled, false otherwise. +     */ +	virtual bool on_key_released(const KeyReleaseEvent & event) = 0; + +private: +	//! Key press event id +	subscription_t press_id = -1; +	//! Key release event id +	subscription_t release_id = -1; +	//! EventManager reference +	EventManager & event_manager; +}; + +} // namespace crepe diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp new file mode 100644 index 0000000..989aeb3 --- /dev/null +++ b/src/crepe/api/IMouseListener.cpp @@ -0,0 +1,29 @@ +#include "IMouseListener.h" + +using namespace crepe; + +IMouseListener::IMouseListener(event_channel_t channel) +	: event_manager(EventManager::get_instance()) { +	this->click_id = event_manager.subscribe<MouseClickEvent>( +		[this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); }, +		channel); + +	this->press_id = event_manager.subscribe<MousePressEvent>( +		[this](const MousePressEvent & event) { return this->on_mouse_pressed(event); }, +		channel); + +	this->release_id = event_manager.subscribe<MouseReleaseEvent>( +		[this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); }, +		channel); + +	this->move_id = event_manager.subscribe<MouseMoveEvent>( +		[this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel); +} + +IMouseListener::~IMouseListener() { +	// Unsubscribe event handlers +	event_manager.unsubscribe(this->click_id); +	event_manager.unsubscribe(this->press_id); +	event_manager.unsubscribe(this->release_id); +	event_manager.unsubscribe(this->move_id); +} diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h new file mode 100644 index 0000000..15e1619 --- /dev/null +++ b/src/crepe/api/IMouseListener.h @@ -0,0 +1,72 @@ +#pragma once + +#include "Event.h" +#include "EventHandler.h" +#include "EventManager.h" + +namespace crepe { + +/** + * \class IMouseListener + * \brief Interface for mouse event handling in the application. + */ +class IMouseListener { +public: +	/** +     * \brief Constructs an IMouseListener with a specified channel. +     * \param channel The channel ID for event handling. +     */ +	IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL); +	virtual ~IMouseListener(); +	IMouseListener & operator=(const IMouseListener &) = delete; +	IMouseListener(const IMouseListener &) = delete; +	IMouseListener & operator=(const IMouseListener &&) = delete; +	IMouseListener(IMouseListener &&) = delete; + +	/** +     * \brief Move assignment operator (deleted). +     */ +	IMouseListener & operator=(IMouseListener &&) = delete; + +	/** +     * \brief Handles a mouse click event. +     * \param event The mouse click event to handle. +     * \return True if the event was handled, false otherwise. +     */ +	virtual bool on_mouse_clicked(const MouseClickEvent & event) = 0; + +	/** +     * \brief Handles a mouse press event. +     * \param event The mouse press event to handle. +     * \return True if the event was handled, false otherwise. +     */ +	virtual bool on_mouse_pressed(const MousePressEvent & event) = 0; + +	/** +     * \brief Handles a mouse release event. +     * \param event The mouse release event to handle. +     * \return True if the event was handled, false otherwise. +     */ +	virtual bool on_mouse_released(const MouseReleaseEvent & event) = 0; + +	/** +     * \brief Handles a mouse move event. +     * \param event The mouse move event to handle. +     * \return True if the event was handled, false otherwise. +     */ +	virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; + +private: +	//! Mouse click event id +	subscription_t click_id = -1; +	//! Mouse press event id +	subscription_t press_id = -1; +	//! Mouse release event id +	subscription_t release_id = -1; +	//! Mouse move event id +	subscription_t move_id = -1; +	//! EventManager reference +	EventManager & event_manager; +}; + +} //namespace crepe diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h new file mode 100644 index 0000000..9e173e0 --- /dev/null +++ b/src/crepe/api/KeyCodes.h @@ -0,0 +1,153 @@ +#pragma once + +//! Enumeration for mouse button inputs, including standard and extended buttons. +enum class MouseButton { +	NONE = 0, //!< No mouse button input. +	LEFT_MOUSE = 1, //!< Left mouse button. +	RIGHT_MOUSE = 2, //!< Right mouse button. +	MIDDLE_MOUSE = 3, //!< Middle mouse button (scroll wheel press). +	X1_MOUSE = 4, //!< First extended mouse button. +	X2_MOUSE = 5, //!< Second extended mouse button. +	SCROLL_UP = 6, //!< Scroll wheel upward movement. +	SCROLL_DOWN = 7, //!< Scroll wheel downward movement. +}; + +//! Enumeration for keyboard key inputs, including printable characters, function keys, and keypad keys. +enum class Keycode { +	NONE = 0, //!< No key input. +	SPACE = 32, //!< Spacebar. +	APOSTROPHE = 39, //!< Apostrophe ('). +	COMMA = 44, //!< Comma (,). +	MINUS = 45, //!< Minus (-). +	PERIOD = 46, //!< Period (.). +	SLASH = 47, //!< Slash (/). +	D0 = 48, //!< Digit 0. +	D1 = 49, //!< Digit 1. +	D2 = 50, //!< Digit 2. +	D3 = 51, //!< Digit 3. +	D4 = 52, //!< Digit 4. +	D5 = 53, //!< Digit 5. +	D6 = 54, //!< Digit 6. +	D7 = 55, //!< Digit 7. +	D8 = 56, //!< Digit 8. +	D9 = 57, //!< Digit 9. +	SEMICOLON = 59, //!< Semicolon (;). +	EQUAL = 61, //!< Equal sign (=). +	A = 65, //!< Key 'A'. +	B = 66, //!< Key 'B'. +	C = 67, //!< Key 'C'. +	D = 68, //!< Key 'D'. +	E = 69, //!< Key 'E'. +	F = 70, //!< Key 'F'. +	G = 71, //!< Key 'G'. +	H = 72, //!< Key 'H'. +	I = 73, //!< Key 'I'. +	J = 74, //!< Key 'J'. +	K = 75, //!< Key 'K'. +	L = 76, //!< Key 'L'. +	M = 77, //!< Key 'M'. +	N = 78, //!< Key 'N'. +	O = 79, //!< Key 'O'. +	P = 80, //!< Key 'P'. +	Q = 81, //!< Key 'Q'. +	R = 82, //!< Key 'R'. +	S = 83, //!< Key 'S'. +	T = 84, //!< Key 'T'. +	U = 85, //!< Key 'U'. +	V = 86, //!< Key 'V'. +	W = 87, //!< Key 'W'. +	X = 88, //!< Key 'X'. +	Y = 89, //!< Key 'Y'. +	Z = 90, //!< Key 'Z'. +	LEFT_BRACKET = 91, //!< Left bracket ([). +	BACKSLASH = 92, //!< Backslash (\). +	RIGHT_BRACKET = 93, //!< Right bracket (]). +	GRAVE_ACCENT = 96, //!< Grave accent (`). +	WORLD1 = 161, //!< Non-US key #1. +	WORLD2 = 162, //!< Non-US key #2. +	ESCAPE = 256, //!< Escape key. +	ENTER = 257, //!< Enter key. +	TAB = 258, //!< Tab key. +	BACKSPACE = 259, //!< Backspace key. +	INSERT = 260, //!< Insert key. +	DELETE = 261, //!< Delete key. +	RIGHT = 262, //!< Right arrow key. +	LEFT = 263, //!< Left arrow key. +	DOWN = 264, //!< Down arrow key. +	UP = 265, //!< Up arrow key. +	PAGE_UP = 266, //!< Page Up key. +	PAGE_DOWN = 267, //!< Page Down key. +	HOME = 268, //!< Home key. +	END = 269, //!< End key. +	CAPS_LOCK = 280, //!< Caps Lock key. +	SCROLL_LOCK = 281, //!< Scroll Lock key. +	NUM_LOCK = 282, //!< Num Lock key. +	PRINT_SCREEN = 283, //!< Print Screen key. +	PAUSE = 284, //!< Pause key. +	/** +	 * \name Function keys (F1-F25). +	 * \{ +	 */ +	F1 = 290, +	F2 = 291, +	F3 = 292, +	F4 = 293, +	F5 = 294, +	F6 = 295, +	F7 = 296, +	F8 = 297, +	F9 = 298, +	F10 = 299, +	F11 = 300, +	F12 = 301, +	F13 = 302, +	F14 = 303, +	F15 = 304, +	F16 = 305, +	F17 = 306, +	F18 = 307, +	F19 = 308, +	F20 = 309, +	F21 = 310, +	F22 = 311, +	F23 = 312, +	F24 = 313, +	F25 = 314, +	/// \} +	/** +	 * \name Keypad digits and operators. +	 * \{ +	 */ +	KP0 = 320, +	KP1 = 321, +	KP2 = 322, +	KP3 = 323, +	KP4 = 324, +	KP5 = 325, +	KP6 = 326, +	KP7 = 327, +	KP8 = 328, +	KP9 = 329, +	KP_DECIMAL = 330, +	KP_DIVIDE = 331, +	KP_MULTIPLY = 332, +	KP_SUBTRACT = 333, +	KP_ADD = 334, +	KP_ENTER = 335, +	KP_EQUAL = 336, +	/// \} +	/** +	 * \name Modifier keys. +	 * \{ +	 */ +	LEFT_SHIFT = 340, +	LEFT_CONTROL = 341, +	LEFT_ALT = 342, +	LEFT_SUPER = 343, +	RIGHT_SHIFT = 344, +	RIGHT_CONTROL = 345, +	RIGHT_ALT = 346, +	RIGHT_SUPER = 347, +	/// \} +	MENU = 348, //!< Menu key. +}; diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index f6904be..13e6dac 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -4,14 +4,28 @@  #include "../ComponentManager.h"  #include "../system/System.h" +#include "api/SceneManager.h"  namespace crepe { +/** + * \brief Main game loop manager + * + * This class is responsible for managing the game loop, including initialization and updating. + */  class LoopManager {  public:  	void start();  	LoopManager(); +	/** +	 * \brief Add a new concrete scene to the scene manager +	 * +	 * \tparam T  Type of concrete scene +	 */ +	template <typename T> +	void add_scene(); +  private:  	/**  	 * \brief Setup function for one-time initialization. @@ -53,12 +67,14 @@ private:  	 * This function updates physics and game logic based on LoopTimer's fixed_delta_time.  	 */  	void fixed_update(); +  	/**  	 * \brief Set game running variable  	 *  	 * \param running running (false = game shutdown, true = game running)  	 */  	void set_running(bool running); +  	/**  	 * \brief Function for executing render-related systems.  	 * @@ -71,6 +87,8 @@ private:  private:  	//! Component manager instance  	ComponentManager component_manager{}; +	//! Scene manager instance +	SceneManager scene_manager{component_manager};  private:  	/** diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/api/LoopManager.hpp index 0b14fdb..9cf470b 100644 --- a/src/crepe/api/LoopManager.hpp +++ b/src/crepe/api/LoopManager.hpp @@ -11,6 +11,11 @@  namespace crepe {  template <class T> +void LoopManager::add_scene() { +	this->scene_manager.add_scene<T>(); +} + +template <class T>  T & LoopManager::get_system() {  	using namespace std;  	static_assert(is_base_of<System, T>::value, diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 33112e1..b83fd61 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -4,7 +4,7 @@  #include "Component.h"  #include "Particle.h" -#include "Vector2.h" +#include "types.h"  namespace crepe { @@ -30,7 +30,7 @@ public:  		//! boundary height (midpoint is emitter location)  		double height = 0.0;  		//! boundary offset from particle emitter location -		Vector2 offset; +		vec2 offset;  		//! reset on exit or stop velocity and set max postion  		bool reset_on_exit = false;  	}; @@ -43,7 +43,7 @@ public:  	 */  	struct Data {  		//! position of the emitter -		Vector2 position; +		vec2 position;  		//! maximum number of particles  		const unsigned int max_particles = 0;  		//! rate of particle emission per update (Lowest value = 0.001 any lower is ignored) @@ -61,7 +61,7 @@ public:  		//! end Lifespan of particle  		double end_lifespan = 0.0;  		//! force over time (physics) -		Vector2 force_over_time; +		vec2 force_over_time;  		//! particle boundary  		Boundary boundary;  		//! collection of particles diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index 6b87695..576ca45 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -6,7 +6,7 @@ crepe::Rigidbody::Rigidbody(game_object_id_t id, const Data & data)  	: Component(id),  	  data(data) {} -void crepe::Rigidbody::add_force_linear(const Vector2 & force) { +void crepe::Rigidbody::add_force_linear(const vec2 & force) {  	this->data.linear_velocity += force;  } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 3e5c7a3..3b0588f 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -2,7 +2,7 @@  #include "../Component.h" -#include "Vector2.h" +#include "types.h"  namespace crepe { @@ -56,11 +56,11 @@ public:  		//! Changes if physics apply  		BodyType body_type = BodyType::DYNAMIC;  		//! linear velocity of object -		Vector2 linear_velocity; +		vec2 linear_velocity;  		//! maximum linear velocity of object -		Vector2 max_linear_velocity; +		vec2 max_linear_velocity;  		//! linear damping of object -		Vector2 linear_damping; +		vec2 linear_damping;  		//! angular velocity of object  		double angular_velocity = 0.0;  		//! max angular velocity of object @@ -90,7 +90,7 @@ public:  	 *   	 * \param force Vector2 that is added to the linear force.  	 */ -	void add_force_linear(const Vector2 & force); +	void add_force_linear(const vec2 & force);  	/**   	 * \brief add a angular force to the Rigidbody.  	 *  diff --git a/src/crepe/api/Scene.cpp b/src/crepe/api/Scene.cpp index 88aa82d..849945e 100644 --- a/src/crepe/api/Scene.cpp +++ b/src/crepe/api/Scene.cpp @@ -2,6 +2,4 @@  using namespace crepe; -Scene::Scene(ComponentManager & mgr, const std::string & name) -	: component_manager(mgr), -	  name(name) {} +Scene::Scene(ComponentManager & mgr) : component_manager(mgr) {} diff --git a/src/crepe/api/Scene.h b/src/crepe/api/Scene.h index 0e516b6..869bf6f 100644 --- a/src/crepe/api/Scene.h +++ b/src/crepe/api/Scene.h @@ -7,19 +7,36 @@ namespace crepe {  class SceneManager;  class ComponentManager; +/** + * \brief Represents a Scene + *  + * This class represents a Scene. The Scene class is only used as an interface for the game + * programmer. + */  class Scene {  protected: -	Scene(ComponentManager & mgr, const std::string & name); +	//TODO: Use Loek's custom reference class to set ComponentManger via SceneManager instead of via constructor +	/** +	 * \param mgr  Reference to the ComponentManager +	 */ +	Scene(ComponentManager & mgr); +	//! SceneManager instances Scene  	friend class SceneManager;  public:  	virtual ~Scene() = default;  public: +	//! Load the scene  	virtual void load_scene() = 0; -	const std::string name; +	/** +	 * \brief Get the scene's name +	 * \return The scene's name +	 */ +	virtual std::string get_name() const = 0;  protected: +	//! Reference to the ComponentManager  	ComponentManager & component_manager;  }; diff --git a/src/crepe/api/SceneManager.cpp b/src/crepe/api/SceneManager.cpp index 7fb5cb0..1f783ad 100644 --- a/src/crepe/api/SceneManager.cpp +++ b/src/crepe/api/SceneManager.cpp @@ -18,7 +18,7 @@ void SceneManager::load_next_scene() {  	auto it = find_if(this->scenes.begin(), this->scenes.end(),  					  [&next_scene = this->next_scene](unique_ptr<Scene> & scene) { -						  return scene->name == next_scene; +						  return scene.get()->get_name() == next_scene;  					  });  	// next scene not found diff --git a/src/crepe/api/SceneManager.h b/src/crepe/api/SceneManager.h index e854794..45ba668 100644 --- a/src/crepe/api/SceneManager.h +++ b/src/crepe/api/SceneManager.h @@ -1,7 +1,6 @@  #pragma once  #include <memory> -#include <queue>  #include <vector>  #include "Scene.h" @@ -10,8 +9,15 @@ namespace crepe {  class ComponentManager; +/** + * \brief Manages scenes + * + * This class manages scenes. It can add new scenes and load them. It also manages the current scene + * and the next scene. + */  class SceneManager {  public: +	//! \param mgr  Reference to the ComponentManager  	SceneManager(ComponentManager & mgr);  public: @@ -19,10 +25,9 @@ public:  	 * \brief Add a new concrete scene to the scene manager  	 *  	 * \tparam T  Type of concrete scene -	 * \param name  Name of new scene  	 */  	template <typename T> -	void add_scene(const std::string & name); +	void add_scene();  	/**  	 * \brief Set the next scene  	 * @@ -35,8 +40,11 @@ public:  	void load_next_scene();  private: +	//! Vector of concrete scenes (added by add_scene())  	std::vector<std::unique_ptr<Scene>> scenes; +	//! Next scene to load  	std::string next_scene; +	//! Reference to the ComponentManager  	ComponentManager & component_manager;  }; diff --git a/src/crepe/api/SceneManager.hpp b/src/crepe/api/SceneManager.hpp index 714f690..94e5946 100644 --- a/src/crepe/api/SceneManager.hpp +++ b/src/crepe/api/SceneManager.hpp @@ -5,16 +5,16 @@  namespace crepe {  template <typename T> -void SceneManager::add_scene(const std::string & name) { +void SceneManager::add_scene() {  	using namespace std;  	static_assert(is_base_of<Scene, T>::value, "T must be derived from Scene"); -	Scene * scene = new T(this->component_manager, name); +	Scene * scene = new T(this->component_manager);  	this->scenes.emplace_back(unique_ptr<Scene>(scene));  	// The first scene added, is the one that will be loaded at the beginning  	if (next_scene.empty()) { -		next_scene = name; +		next_scene = scene->get_name();  	}  } diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..fcbe4c7 --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,15 @@ +#include "Script.h" + +using namespace crepe; + +Script::~Script() { +	EventManager & evmgr = this->event_manager; +	for (auto id : this->listeners) { +		evmgr.unsubscribe(id); +	} +} + +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback) { +	this->subscribe_internal(callback, this->game_object_id); +} diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 2b70379..f0b9c73 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -3,6 +3,9 @@  #include <vector>  #include "../types.h" +#include "../util/OptionalRef.h" + +#include "EventManager.h"  namespace crepe { @@ -16,13 +19,19 @@ class ComponentManager;   * This class is used as a base class for user-defined scripts that can be added to game   * objects using the \c BehaviorScript component.   * - * \note Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as + * \info Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as   * member or lambda methods in derivative user script classes and registered in \c init(). + * + * \see feature_script   */  class Script {  protected:  	/** -	 * \brief Script initialization function +	 * \name Interface functions +	 * \{ +	 */ +	/** +	 * \brief Script initialization function (empty by default)  	 *  	 * This function is called during the ScriptSystem::update() routine *before*  	 * Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component @@ -30,24 +39,32 @@ protected:  	 */  	virtual void init() {}  	/** -	 * \brief Script update function +	 * \brief Script update function (empty by default)  	 *  	 * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript  	 * component holding this script instance is active.  	 */  	virtual void update() {} +	//! \} +  	//! ScriptSystem calls \c init() and \c update()  	friend class crepe::ScriptSystem;  protected:  	/** -	 * \brief Get single component of type \c T on this game object (utility) +	 * \name Utility functions +	 * \{ +	 */ + +	/** +	 * \brief Get single component of type \c T on this game object  	 *  	 * \tparam T Type of component  	 *  	 * \returns Reference to component  	 * -	 * \throws nullptr if this game object does not have a component matching type \c T +	 * \throws std::runtime_error if this game object does not have a component matching type \c +	 * T  	 */  	template <typename T>  	T & get_component() const; @@ -55,34 +72,117 @@ protected:  	// cause compile-time errors  	/** -	 * \brief Get all components of type \c T on this game object (utility) +	 * \brief Get all components of type \c T on this game object  	 *  	 * \tparam T Type of component  	 *  	 * \returns List of component references  	 */  	template <typename T> -	std::vector<std::reference_wrapper<T>> get_components() const; +	RefVector<T> get_components() const; + +	/** +	 * \brief Log a message using Log::logf +	 * +	 * \tparam Args Log::logf parameters +	 * \param args  Log::logf parameters +	 */ +	template <typename... Args> +	void logf(Args &&... args); + +	/** +	 * \brief Subscribe to an event with an explicit channel +	 * \see EventManager::subscribe +	 */ +	template <typename EventType> +	void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); +	/** +	 * \brief Subscribe to an event on EventManager::CHANNEL_ALL +	 * \see EventManager::subscribe +	 */ +	template <typename EventType> +	void subscribe(const EventHandler<EventType> & callback); + +	//! \} + +private: +	/** +	 * \brief Internal subscribe function +	 * +	 * This function exists so certain template specializations of Script::subscribe can be +	 * explicitly deleted, and does the following: +	 * - Wrap the user-provided callback in a check that tests if the parent BehaviorScript +	 *   component is still active +	 * - Store the subscriber handle returned by the event manager so this listener is +	 *   automatically unsubscribed at the end of this Script instance's life +	 * +	 * \tparam EventType concrete Event class +	 * \param callback User-provided callback function +	 * \param channel Event channel (may have been overridden by template specializations) +	 */ +	template <typename EventType> +	void subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel);  protected: -	// NOTE: Script must have a constructor without arguments so the game programmer doesn't need -	// to manually add `using Script::Script` to their concrete script class. +	// NOTE: This must be the only constructor on Script, see "Late references" below  	Script() = default;  	//! Only \c BehaviorScript instantiates Script  	friend class BehaviorScript; +public: +	// std::unique_ptr destroys script +	virtual ~Script(); + +private: +	Script(const Script &) = delete; +	Script(Script &&) = delete; +	Script & operator=(const Script &) = delete; +	Script & operator=(Script &&) = delete; +  private: -	// These references are set by BehaviorScript immediately after calling the constructor of -	// Script. -	game_object_id_t game_object_id = -1; -	ComponentManager * component_manager_ref = nullptr; -	// TODO: use OptionalRef instead of pointer +	/** +	 * \name Late references +	 * +	 * These references are set by BehaviorScript immediately after calling the constructor of +	 * Script. +	 * +	 * \note Script must have a constructor without arguments so the game programmer doesn't need +	 * to manually add `using Script::Script` to their concrete script class if they want to +	 * implement a non-default constructor (e.g. for passing references to their own concrete +	 * Script classes). +	 * +	 * \{ +	 */ +	//! Game object ID of game object parent BehaviorScript is attached to +	game_object_id_t game_object_id; +	//! Reference to parent component +	OptionalRef<bool> active; +	//! Reference to component manager instance +	OptionalRef<ComponentManager> component_manager; +	//! Reference to event manager instance +	OptionalRef<EventManager> event_manager; +	//! \}  private:  	//! Flag to indicate if \c init() has been called already  	bool initialized = false; +	//! List of subscribed events +	std::vector<subscription_t> listeners;  }; +/** + * \brief Subscribe to CollisionEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for CollisionEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback); +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback, event_channel_t) +	= delete; +  } // namespace crepe  #include "Script.hpp" diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a064a90..a2463bf 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -10,7 +10,7 @@ namespace crepe {  template <typename T>  T & Script::get_component() const {  	using namespace std; -	vector<reference_wrapper<T>> all_components = this->get_components<T>(); +	RefVector<T> all_components = this->get_components<T>();  	if (all_components.size() < 1)  		throw runtime_error(  			format("Script: no component found with type = {}", typeid(T).name())); @@ -19,10 +19,39 @@ T & Script::get_component() const {  }  template <typename T> -std::vector<std::reference_wrapper<T>> Script::get_components() const { -	auto & mgr = *this->component_manager_ref; +RefVector<T> Script::get_components() const { +	ComponentManager & mgr = this->component_manager;  	return mgr.get_components_by_id<T>(this->game_object_id);  } +template <typename... Args> +void Script::logf(Args &&... args) { +	Log::logf(std::forward<Args>(args)...); +} + +template <typename EventType> +void Script::subscribe_internal(const EventHandler<EventType> & callback, +								event_channel_t channel) { +	EventManager & mgr = this->event_manager; +	subscription_t listener = mgr.subscribe<EventType>( +		[this, callback](const EventType & data) -> bool { +			bool & active = this->active; +			if (!active) return false; +			return callback(data); +		}, +		channel); +	this->listeners.push_back(listener); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { +	this->subscribe_internal(callback, channel); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback) { +	this->subscribe_internal(callback, EventManager::CHANNEL_ALL); +} +  } // namespace crepe diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index 0192793..74a55d4 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -2,8 +2,9 @@  #include <memory> +#include "../Component.h" +  #include "Color.h" -#include "Component.h"  #include "Texture.h"  namespace crepe { diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index 6a1e4d8..9be9421 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -35,5 +35,5 @@ int Texture::get_width() const {  }  int Texture::get_height() const {  	if (this->texture == nullptr) return 0; -	return SDLContext::get_instance().get_width(*this); +	return SDLContext::get_instance().get_height(*this);  } diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index cd944bd..a85b792 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -4,7 +4,7 @@  using namespace crepe; -Transform::Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale) +Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale)  	: Component(id),  	  position(point),  	  rotation(rotation), diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 18aa293..6243a93 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -1,8 +1,7 @@  #pragma once -#include "api/Vector2.h" -  #include "Component.h" +#include "types.h"  namespace crepe { @@ -15,7 +14,7 @@ namespace crepe {  class Transform : public Component {  public:  	//! Translation (shift) -	Vector2 position = {0, 0}; +	vec2 position = {0, 0};  	//! Rotation, in degrees  	double rotation = 0;  	//! Multiplication factor @@ -28,7 +27,7 @@ protected:  	 * \param rotation The rotation of the GameObject  	 * \param scale The scale of the GameObject  	 */ -	Transform(game_object_id_t id, const Vector2 & point, double rotation, double scale); +	Transform(game_object_id_t id, const vec2 & point, double rotation, double scale);  	/**  	 * There is always exactly one transform component per entity  	 * \return 1 diff --git a/src/crepe/api/Vector2.cpp b/src/crepe/api/Vector2.cpp deleted file mode 100644 index 30b968e..0000000 --- a/src/crepe/api/Vector2.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "Vector2.h" - -using namespace crepe; - -Vector2 Vector2::operator-(const Vector2 & other) const { return {x - other.x, y - other.y}; } - -Vector2 Vector2::operator+(const Vector2 & other) const { return {x + other.x, y + other.y}; } - -Vector2 Vector2::operator*(double scalar) const { return {x * scalar, y * scalar}; } - -Vector2 & Vector2::operator*=(const Vector2 & other) { -	x *= other.x; -	y *= other.y; -	return *this; -} - -Vector2 & Vector2::operator+=(const Vector2 & other) { -	x += other.x; -	y += other.y; -	return *this; -} - -Vector2 & Vector2::operator+=(double other) { -	x += other; -	y += other; -	return *this; -} - -Vector2 Vector2::operator-() const { return {-x, -y}; } - -bool Vector2::operator==(const Vector2 & other) const { return x == other.x && y == other.y; } - -bool Vector2::operator!=(const Vector2 & other) const { return !(*this == other); } diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index 2fb6136..c278c87 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -3,38 +3,71 @@  namespace crepe {  //! 2D vector +template <class T>  struct Vector2 {  	//! X component of the vector -	double x = 0; +	T x = 0;  	//! Y component of the vector -	double y = 0; +	T y = 0;  	//! Subtracts another vector from this vector and returns the result. -	Vector2 operator-(const Vector2 & other) const; +	Vector2 operator-(const Vector2<T> & other) const; + +	//! Subtracts a scalar value from both components of this vector and returns the result. +	Vector2 operator-(T scalar) const;  	//! Adds another vector to this vector and returns the result. -	Vector2 operator+(const Vector2 & other) const; +	Vector2 operator+(const Vector2<T> & other) const; + +	//! Adds a scalar value to both components of this vector and returns the result. +	Vector2 operator+(T scalar) const; + +	//! Multiplies this vector by another vector element-wise and returns the result. +	Vector2 operator*(const Vector2<T> & other) const;  	//! Multiplies this vector by a scalar and returns the result. -	Vector2 operator*(double scalar) const; +	Vector2 operator*(T scalar) const; -	//! Multiplies this vector by another vector element-wise and updates this vector. -	Vector2 & operator*=(const Vector2 & other); +	//! Divides this vector by another vector element-wise and returns the result. +	Vector2 operator/(const Vector2<T> & other) const; + +	//! Divides this vector by a scalar and returns the result. +	Vector2 operator/(T scalar) const;  	//! Adds another vector to this vector and updates this vector. -	Vector2 & operator+=(const Vector2 & other); +	Vector2 & operator+=(const Vector2<T> & other);  	//! Adds a scalar value to both components of this vector and updates this vector. -	Vector2 & operator+=(double other); +	Vector2 & operator+=(T other); + +	//! Subtracts another vector from this vector and updates this vector. +	Vector2 & operator-=(const Vector2<T> & other); + +	//! Subtracts a scalar value from both components of this vector and updates this vector. +	Vector2 & operator-=(T other); + +	//! Multiplies this vector by another vector element-wise and updates this vector. +	Vector2 & operator*=(const Vector2<T> & other); + +	//! Multiplies this vector by a scalar and updates this vector. +	Vector2 & operator*=(T other); + +	//! Divides this vector by another vector element-wise and updates this vector. +	Vector2 & operator/=(const Vector2<T> & other); + +	//! Divides this vector by a scalar and updates this vector. +	Vector2 & operator/=(T other);  	//! Returns the negation of this vector.  	Vector2 operator-() const;  	//! Checks if this vector is equal to another vector. -	bool operator==(const Vector2 & other) const; +	bool operator==(const Vector2<T> & other) const;  	//! Checks if this vector is not equal to another vector. -	bool operator!=(const Vector2 & other) const; +	bool operator!=(const Vector2<T> & other) const;  };  } // namespace crepe + +#include "Vector2.hpp" diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp new file mode 100644 index 0000000..cad15f8 --- /dev/null +++ b/src/crepe/api/Vector2.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "Vector2.h" + +namespace crepe { + +template <class T> +Vector2<T> Vector2<T>::operator-(const Vector2<T> & other) const { +	return {x - other.x, y - other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator-(T scalar) const { +	return {x - scalar, y - scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(const Vector2<T> & other) const { +	return {x + other.x, y + other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator+(T scalar) const { +	return {x + scalar, y + scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(const Vector2<T> & other) const { +	return {x * other.x, y * other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator*(T scalar) const { +	return {x * scalar, y * scalar}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(const Vector2<T> & other) const { +	return {x / other.x, y / other.y}; +} + +template <class T> +Vector2<T> Vector2<T>::operator/(T scalar) const { +	return {x / scalar, y / scalar}; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(const Vector2<T> & other) { +	x += other.x; +	y += other.y; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator+=(T other) { +	x += other; +	y += other; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(const Vector2<T> & other) { +	x -= other.x; +	y -= other.y; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator-=(T other) { +	x -= other; +	y -= other; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(const Vector2<T> & other) { +	x *= other.x; +	y *= other.y; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator*=(T other) { +	x *= other; +	y *= other; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(const Vector2<T> & other) { +	x /= other.x; +	y /= other.y; +	return *this; +} + +template <class T> +Vector2<T> & Vector2<T>::operator/=(T other) { +	x /= other; +	y /= other; +	return *this; +} + +template <class T> +Vector2<T> Vector2<T>::operator-() const { +	return {-x, -y}; +} + +template <class T> +bool Vector2<T>::operator==(const Vector2<T> & other) const { +	return x == other.x && y == other.y; +} + +template <class T> +bool Vector2<T>::operator!=(const Vector2<T> & other) const { +	return !(*this == other); +} + +} // namespace crepe diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index d5d19dc..95cf606 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -18,8 +18,8 @@ DB::DB(const string & path) {  	this->db = {db, [](libdb::DB * db) { db->close(db, 0); }};  	// load or create database file -	ret = this->db->open(this->db.get(), NULL, path.c_str(), NULL, libdb::DB_BTREE, DB_CREATE, -						 0); +	const char * file = path.empty() ? NULL : path.c_str(); +	ret = this->db->open(this->db.get(), NULL, file, NULL, libdb::DB_BTREE, DB_CREATE, 0);  	if (ret != 0) throw runtime_error(format("db->open: {}", libdb::db_strerror(ret)));  	// create cursor diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h index 629b0eb..115c0f1 100644 --- a/src/crepe/facade/DB.h +++ b/src/crepe/facade/DB.h @@ -22,8 +22,10 @@ class DB {  public:  	/**  	 * \param path  The path of the database (created if nonexistant) +	 * +	 * \note If \p path is empty, the database is entirely in-memory  	 */ -	DB(const std::string & path); +	DB(const std::string & path = "");  	virtual ~DB() = default;  public: diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index f2daada..b3298a7 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -1,5 +1,6 @@  #include <SDL2/SDL.h>  #include <SDL2/SDL_image.h> +#include <SDL2/SDL_keycode.h>  #include <SDL2/SDL_rect.h>  #include <SDL2/SDL_render.h>  #include <SDL2/SDL_surface.h> @@ -7,13 +8,15 @@  #include <cmath>  #include <cstddef>  #include <functional> -#include <iostream>  #include <memory> +#include <stdexcept>  #include <string> +#include "../api/Camera.h"  #include "../api/Sprite.h"  #include "../api/Texture.h"  #include "../api/Transform.h" +#include "../api/Vector2.h"  #include "../util/Log.h"  #include "SDLContext.h" @@ -30,29 +33,22 @@ SDLContext::SDLContext() {  	dbg_trace();  	// FIXME: read window defaults from config manager -	if (SDL_Init(SDL_INIT_VIDEO) < 0) { -		// FIXME: throw exception -		std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; -		return; +	if (SDL_Init(SDL_INIT_VIDEO) != 0) { +		throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));  	}  	SDL_Window * tmp_window  		= SDL_CreateWindow("Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,  						   this->viewport.w, this->viewport.h, 0);  	if (!tmp_window) { -		// FIXME: throw exception -		std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; -		return; +		throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError()));  	}  	this->game_window = {tmp_window, [](SDL_Window * window) { SDL_DestroyWindow(window); }};  	SDL_Renderer * tmp_renderer  		= SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED);  	if (!tmp_renderer) { -		// FIXME: throw exception -		std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() -				  << std::endl; -		SDL_DestroyWindow(this->game_window.get()); -		return; +		throw runtime_error( +			format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError()));  	}  	this->game_renderer @@ -60,9 +56,7 @@ SDLContext::SDLContext() {  	int img_flags = IMG_INIT_PNG;  	if (!(IMG_Init(img_flags) & img_flags)) { -		// FIXME: throw exception -		std::cout << "SDL_image could not initialize! SDL_image Error: " << IMG_GetError() -				  << std::endl; +		throw runtime_error("SDLContext: SDL_image could not initialize!");  	}  } @@ -78,7 +72,6 @@ SDLContext::~SDLContext() {  	IMG_Quit();  	SDL_Quit();  } -  void SDLContext::handle_events(bool & running) {  	//TODO: wouter i need events  	/* @@ -103,40 +96,62 @@ void SDLContext::handle_events(bool & running) {  void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); }  void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } -void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { - -	SDL_RendererFlip render_flip -		= (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) -							  | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); - -	double adjusted_x = (transform.position.x - cam.x) * cam.zoom; -	double adjusted_y = (transform.position.y - cam.y) * cam.zoom; -	double adjusted_w = sprite.sprite_rect.w * transform.scale * cam.zoom; -	double adjusted_h = sprite.sprite_rect.h * transform.scale * cam.zoom; - -	SDL_Rect srcrect = { +SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { +	return SDL_Rect{  		.x = sprite.sprite_rect.x,  		.y = sprite.sprite_rect.y,  		.w = sprite.sprite_rect.w,  		.h = sprite.sprite_rect.h,  	}; +} +SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const vec2 & pos, +								  const double & scale, const Camera & cam) const { -	SDL_Rect dstrect = { +	double adjusted_w = sprite.sprite_rect.w * scale * cam.zoom; +	double adjusted_h = sprite.sprite_rect.h * scale * cam.zoom; +	double adjusted_x = (pos.x - cam.x) * cam.zoom - adjusted_w / 2; +	double adjusted_y = (pos.y - cam.y) * cam.zoom - adjusted_h / 2; + +	return SDL_Rect{  		.x = static_cast<int>(adjusted_x),  		.y = static_cast<int>(adjusted_y),  		.w = static_cast<int>(adjusted_w),  		.h = static_cast<int>(adjusted_h),  	}; +} + +void SDLContext::draw_particle(const Sprite & sprite, const vec2 & pos, const double & angle, +							   const double & scale, const Camera & camera) { + +	SDL_RendererFlip render_flip +		= (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) +							  | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + +	SDL_Rect srcrect = this->get_src_rect(sprite); +	SDL_Rect dstrect = this->get_dst_rect(sprite, pos, scale, camera); + +	SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, +					 &dstrect, angle, NULL, render_flip); +} + +void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { + +	SDL_RendererFlip render_flip +		= (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) +							  | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + +	SDL_Rect srcrect = this->get_src_rect(sprite); +	SDL_Rect dstrect = this->get_dst_rect(sprite, transform.position, transform.scale, cam);  	SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect,  					 &dstrect, transform.rotation, NULL, render_flip);  } -void SDLContext::camera(const Camera & cam) { +void SDLContext::set_camera(const Camera & cam) {  	this->viewport.w = static_cast<int>(cam.aspect_width);  	this->viewport.h = static_cast<int>(cam.aspect_height); -	this->viewport.x = static_cast<int>(cam.x) - (SCREEN_WIDTH / 2); -	this->viewport.y = static_cast<int>(cam.y) - (SCREEN_HEIGHT / 2); +	this->viewport.x = static_cast<int>(cam.x) - (this->viewport.w / 2); +	this->viewport.y = static_cast<int>(cam.y) - (this->viewport.h / 2);  	SDL_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, cam.bg_color.g,  						   cam.bg_color.b, cam.bg_color.a); @@ -148,9 +163,8 @@ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>  SDLContext::texture_from_path(const std::string & path) {  	SDL_Surface * tmp = IMG_Load(path.c_str()); -	if (tmp == nullptr) { -		tmp = IMG_Load("asset/texture/ERROR.png"); -	} +	if (tmp == nullptr) +		throw runtime_error(format("SDLContext: IMG_Load error: {}", SDL_GetError()));  	std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> img_surface;  	img_surface = {tmp, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; @@ -159,7 +173,7 @@ SDLContext::texture_from_path(const std::string & path) {  		= SDL_CreateTextureFromSurface(this->game_renderer.get(), img_surface.get());  	if (tmp_texture == nullptr) { -		throw runtime_error(format("Texture cannot be load from {}", path)); +		throw runtime_error(format("SDLContext: Texture cannot be load from {}", path));  	}  	std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> img_texture; diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 007092b..20e30b3 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,8 +1,10 @@  #pragma once  #include <SDL2/SDL_keycode.h> +#include <SDL2/SDL_rect.h>  #include <SDL2/SDL_render.h>  #include <SDL2/SDL_video.h> +#include <cmath>  #include <functional>  #include <memory>  #include <string> @@ -11,9 +13,7 @@  #include "../api/Transform.h"  #include "api/Camera.h" -// FIXME: this needs to be removed -const int SCREEN_WIDTH = 640; -const int SCREEN_HEIGHT = 480; +#include "types.h"  namespace crepe { @@ -21,9 +21,6 @@ namespace crepe {  // typedef is unusable when crepe is packaged. Wouter will fix this later.  typedef SDL_Keycode CREPE_KEYCODES; -class Texture; -class LoopManager; -  /**   * \class SDLContext   * \brief Facade for the SDL library @@ -91,9 +88,6 @@ private:  	//! Will use the funtions: texture_from_path, get_width,get_height.  	friend class Texture; -	//! Will use the funtions: texture_from_path, get_width,get_height. -	friend class Animator; -  	/**  	 * \brief Loads a texture from a file path.  	 * \param path Path to the image file. @@ -127,6 +121,9 @@ private:  	 */  	void draw(const Sprite & sprite, const Transform & transform, const Camera & camera); +	void draw_particle(const Sprite & sprite, const vec2 & pos, const double & angle, +					   const double & scale, const Camera & camera); +  	//! Clears the screen, preparing for a new frame.  	void clear_screen(); @@ -134,10 +131,31 @@ private:  	void present_screen();  	/** -	 * \brief Sets the current camera for rendering. +	 * \brief sets the background of the camera (will be adjusted in future PR)  	 * \param camera Reference to the Camera object.  	 */ -	void camera(const Camera & camera); +	void set_camera(const Camera & camera); + +private: +	/** +	 * \brief calculates the sqaure size of the image +	 * +	 * \param sprite Reference to the sprite to calculate the rectangle +	 * \return sdl rectangle to draw a src image +	 */ +	SDL_Rect get_src_rect(const Sprite & sprite) const; +	/** +	 * \brief calculates the sqaure size of the image for an destination +	 * +	 * \param sprite Reference to the sprite to calculate the rectangle +	 * \param pos the pos in pixel positions +	 * \param scale the multiplier to increase of decrease for the specified sprite  +	 * \param cam Reference to the current camera in the scene to calculate the position based +	 * on the camera  +	 * \return sdl rectangle to draw a dst image to draw on the screen +	 */ +	SDL_Rect get_dst_rect(const Sprite & sprite, const vec2 & pos, const double & scale, +						  const Camera & cam) const;  private:  	//! sdl Window diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index b589759..52496af 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -13,8 +13,7 @@ Sound::Sound(const Asset & src) : Resource(src) {  }  Sound::~Sound() { dbg_trace(); } -void Sound::play() { -	SoundContext & ctx = this->context.get(); +void Sound::play(SoundContext & ctx) {  	if (ctx.engine.getPause(this->handle)) {  		// resume if paused  		ctx.engine.setPause(this->handle, false); @@ -25,35 +24,25 @@ void Sound::play() {  	}  } -void Sound::pause() { -	SoundContext & ctx = this->context.get(); +void Sound::pause(SoundContext & ctx) {  	if (ctx.engine.getPause(this->handle)) return;  	ctx.engine.setPause(this->handle, true);  } -void Sound::rewind() { -	SoundContext & ctx = this->context.get(); +void Sound::rewind(SoundContext & ctx) {  	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;  	ctx.engine.seek(this->handle, 0);  } -void Sound::set_volume(float volume) { +void Sound::set_volume(SoundContext & ctx, float volume) {  	this->volume = volume; - -	SoundContext & ctx = this->context.get();  	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;  	ctx.engine.setVolume(this->handle, this->volume);  } -void Sound::set_looping(bool looping) { +void Sound::set_looping(SoundContext & ctx, bool looping) {  	this->looping = looping; - -	SoundContext & ctx = this->context.get();  	if (!ctx.engine.isValidVoiceHandle(this->handle)) return;  	ctx.engine.setLooping(this->handle, this->looping);  } -void Sound::set_context(SoundContext & ctx) { -	this->context = ctx; -} - diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index a84aa8c..b0b80f8 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -3,7 +3,6 @@  #include <soloud/soloud.h>  #include <soloud/soloud_wav.h> -#include "../util/OptionalRef.h"  #include "../Resource.h"  namespace crepe { @@ -26,7 +25,7 @@ public:  	 * Pauses this sound if it is playing, or does nothing if it is already paused. The playhead  	 * position is saved, such that calling \c play() after this function makes the sound resume.  	 */ -	void pause(); +	void pause(SoundContext & ctx);  	/**  	 * \brief Play this sample  	 * @@ -36,7 +35,7 @@ public:  	 * \c play() while the sound is already playing causes multiple instances of the sample to  	 * play simultaniously. The sample started last is the one that is controlled afterwards.  	 */ -	void play(); +	void play(SoundContext & ctx);  	/**  	 * \brief Reset playhead position  	 *  @@ -44,13 +43,13 @@ public:  	 * from the start of the sample. If the sound is not paused before calling this function,  	 * this function will stop playback.  	 */ -	void rewind(); +	void rewind(SoundContext & ctx);  	/**  	 * \brief Set playback volume / gain  	 *  	 * \param volume  Volume (0 = muted, 1 = full volume)  	 */ -	void set_volume(float volume); +	void set_volume(SoundContext & ctx, float volume);  	/**  	 * \brief Get playback volume / gain  	 * @@ -62,7 +61,7 @@ public:  	 *  	 * \param looping  Looping behavior (false = one-shot, true = loop)  	 */ -	void set_looping(bool looping); +	void set_looping(SoundContext & ctx, bool looping);  	/**  	 * \brief Get looping behavior  	 * @@ -70,13 +69,9 @@ public:  	 */  	bool get_looping() const { return this->looping; } -public: -	void set_context(SoundContext & ctx); -  private:  	SoLoud::Wav sample;  	SoLoud::handle handle; -	OptionalRef<SoundContext> context;  	float volume = 1.0f;  	bool looping = false; diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 9d18873..676e485 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,6 +1,4 @@  #include <cstdint> -#include <functional> -#include <vector>  #include "api/Animator.h"  #include "facade/SDLContext.h" @@ -13,8 +11,7 @@ using namespace crepe;  void AnimatorSystem::update() {  	ComponentManager & mgr = this->component_manager; -	std::vector<std::reference_wrapper<Animator>> animations -		= mgr.get_components_by_type<Animator>(); +	RefVector<Animator> animations = mgr.get_components_by_type<Animator>();  	uint64_t tick = SDLContext::get_instance().get_ticks();  	for (Animator & a : animations) { diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index c8dae9d..b7ac1f2 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -48,7 +48,6 @@ void AudioSystem::update() {  		 *   play/pause/retrigger behavior).  		 */  		Sound & sound = this->resman.cache<Sound>(component.source); -		sound.set_context(this->context);  		// TODO: lots of state diffing  	} diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 7316309..0e62a57 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -4,7 +4,6 @@  #include "api/ParticleEmitter.h"  #include "api/Transform.h" -#include "api/Vector2.h"  #include "ComponentManager.h"  #include "ParticleSystem.h" @@ -14,8 +13,7 @@ using namespace crepe;  void ParticleSystem::update() {  	// Get all emitters  	ComponentManager & mgr = this->component_manager; -	std::vector<std::reference_wrapper<ParticleEmitter>> emitters -		= mgr.get_components_by_type<ParticleEmitter>(); +	RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>();  	for (ParticleEmitter & emitter : emitters) {  		// Get transform linked to emitter @@ -43,17 +41,15 @@ void ParticleSystem::update() {  }  void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { -	constexpr double DEG_TO_RAD = M_PI / 180.0; +	constexpr float DEG_TO_RAD = M_PI / 180.0; -	Vector2 initial_position = emitter.data.position + transform.position; -	double random_angle -		= generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); +	vec2 initial_position = emitter.data.position + transform.position; +	float random_angle = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); -	double random_speed -		= generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); -	double angle_radians = random_angle * DEG_TO_RAD; +	float random_speed = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); +	float angle_radians = random_angle * DEG_TO_RAD; -	Vector2 velocity +	vec2 velocity  		= {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)};  	for (Particle & particle : emitter.data.particles) { @@ -78,7 +74,7 @@ int ParticleSystem::calculate_update(int count, double emission) const {  }  void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) { -	Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; +	vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position;  	double half_width = emitter.data.boundary.width / 2.0;  	double half_height = emitter.data.boundary.height / 2.0; @@ -88,7 +84,7 @@ void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & t  	const double BOTTOM = offset.y + half_height;  	for (Particle & particle : emitter.data.particles) { -		const Vector2 & position = particle.position; +		const vec2 & position = particle.position;  		bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP  							  && position.y <= BOTTOM); diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 4a7dbfb..514a4b3 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -12,10 +12,8 @@ using namespace crepe;  void PhysicsSystem::update() {  	ComponentManager & mgr = this->component_manager; -	std::vector<std::reference_wrapper<Rigidbody>> rigidbodies -		= mgr.get_components_by_type<Rigidbody>(); -	std::vector<std::reference_wrapper<Transform>> transforms -		= mgr.get_components_by_type<Transform>(); +	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); +	RefVector<Transform> transforms = mgr.get_components_by_type<Transform>();  	double gravity = Config::get_instance().physics.gravity;  	for (Rigidbody & rigidbody : rigidbodies) { @@ -36,7 +34,7 @@ void PhysicsSystem::update() {  						if (rigidbody.data.angular_damping != 0) {  							rigidbody.data.angular_velocity *= rigidbody.data.angular_damping;  						} -						if (rigidbody.data.linear_damping != Vector2{0, 0}) { +						if (rigidbody.data.linear_damping != vec2{0, 0}) {  							rigidbody.data.linear_velocity *= rigidbody.data.linear_damping;  						} diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index fa3d0de..ad510f5 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -1,44 +1,102 @@ +#include <algorithm> +#include <cassert> +#include <cmath>  #include <functional> +#include <iostream> +#include <stdexcept>  #include <vector>  #include "../ComponentManager.h" +#include "../api/ParticleEmitter.h"  #include "../api/Sprite.h"  #include "../api/Transform.h" +#include "../api/Vector2.h"  #include "../facade/SDLContext.h" -#include "../util/Log.h"  #include "RenderSystem.h"  using namespace crepe; +using namespace std; -void RenderSystem::clear_screen() const { SDLContext::get_instance().clear_screen(); } +void RenderSystem::clear_screen() { this->context.clear_screen(); } -void RenderSystem::present_screen() const { SDLContext::get_instance().present_screen(); } +void RenderSystem::present_screen() { this->context.present_screen(); }  void RenderSystem::update_camera() {  	ComponentManager & mgr = this->component_manager; -	std::vector<std::reference_wrapper<Camera>> cameras = mgr.get_components_by_type<Camera>(); +	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); + +	if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene");  	for (Camera & cam : cameras) { -		SDLContext::get_instance().camera(cam); -		this->curr_cam = &cam; +		if (!cam.active) continue; +		this->context.set_camera(cam); +		this->curr_cam_ref = &cam;  	}  } -void RenderSystem::render_sprites() const { -	ComponentManager & mgr = this->component_manager; -	std::vector<std::reference_wrapper<Sprite>> sprites = mgr.get_components_by_type<Sprite>(); +bool sorting_comparison(const Sprite & a, const Sprite & b) { +	if (a.sorting_in_layer < b.sorting_in_layer) return true; +	if (a.sorting_in_layer == b.sorting_in_layer) return a.order_in_layer < b.order_in_layer; -	SDLContext & render = SDLContext::get_instance(); -	for (const Sprite & sprite : sprites) { -		auto transforms = mgr.get_components_by_id<Transform>(sprite.game_object_id); -		render.draw(sprite, transforms[0], *curr_cam); -	} +	return false; +} + +RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const { +	RefVector<Sprite> sorted_objs(objs); +	std::sort(sorted_objs.begin(), sorted_objs.end(), sorting_comparison); + +	return sorted_objs;  }  void RenderSystem::update() {  	this->clear_screen();  	this->update_camera(); -	this->render_sprites(); +	this->render();  	this->present_screen();  } + +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { + +	ComponentManager & mgr = this->component_manager; + +	vector<reference_wrapper<ParticleEmitter>> emitters +		= mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); + +	bool rendering_particles = false; + +	for (const ParticleEmitter & em : emitters) { +		if (!(&em.data.sprite == &sprite)) continue; +		rendering_particles = true; +		if (!em.active) continue; + +		for (const Particle & p : em.data.particles) { +			if (!p.active) continue; +			this->context.draw_particle(sprite, p.position, p.angle, scale, +										*this->curr_cam_ref); +		} +	} +	return rendering_particles; +} +void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { +	this->context.draw(sprite, tm, *this->curr_cam_ref); +} + +void RenderSystem::render() { + +	ComponentManager & mgr = this->component_manager; +	RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); +	RefVector<Sprite> sorted_sprites = this->sort(sprites); + +	for (const Sprite & sprite : sorted_sprites) { +		if (!sprite.active) continue; +		const Transform & transform +			= mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); + +		bool rendered_particles = this->render_particle(sprite, transform.scale); + +		if (rendered_particles) continue; + +		this->render_normal(sprite, transform); +	} +} diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 87ec494..30b41cf 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -1,18 +1,24 @@  #pragma once -#include "api/Camera.h" +#include <functional> +#include <vector> + +#include "facade/SDLContext.h"  #include "System.h" +#include <cmath>  namespace crepe { +class Camera; +class Sprite; +  /**   * \class RenderSystem   * \brief Manages rendering operations for all game objects.   * - * RenderSystem is responsible for rendering sprites, clearing and presenting the screen, and - * managing the active camera. It functions as a singleton, providing centralized rendering - * services for the application. + * RenderSystem is responsible for rendering, clearing and presenting the screen, and + * managing the active camera.    */  class RenderSystem : public System {  public: @@ -25,20 +31,44 @@ public:  private:  	//! Clears the screen in preparation for rendering. -	void clear_screen() const; +	void clear_screen();  	//! Presents the rendered frame to the display. -	void present_screen() const; +	void present_screen();  	//! Updates the active camera used for rendering.  	void update_camera(); -	//! Renders all active sprites to the screen. -	void render_sprites() const; +	//! Renders the whole screen +	void render(); + +	/** +	 * \brief Renders all the particles on the screen from a given sprite. +	 * +	 * \param sprite renders the particles with given texture +	 * \param tm the Transform component for scale +	 * \return true if particles have been rendered +	 */ +	bool render_particle(const Sprite & sprite, const double & scale); + +	/** +	 * \brief renders a sprite with a Transform component on the screen  +	 * +	 * \param sprite  the sprite component that holds all the data +	 * \param tm the Transform component that holds the position,rotation and scale  +	 */ +	void render_normal(const Sprite & sprite, const Transform & tm); + +	/** +	 * \brief sort a vector sprite objects with +	 * +	 * \param objs the vector that will do a sorting algorithm on  +	 * \return returns a sorted reference vector +	 */ +	RefVector<Sprite> sort(RefVector<Sprite> & objs) const;  	/**  	 * \todo Include color handling for sprites. -	 * \todo Implement particle emitter rendering with sprites.  	 * \todo Add text rendering using SDL_ttf for text components.  	 * \todo Implement a text component and a button component.  	 * \todo Ensure each sprite is checked for active status before rendering. @@ -48,8 +78,10 @@ private:  private:  	//! Pointer to the current active camera for rendering -	Camera * curr_cam = nullptr; +	Camera * curr_cam_ref = nullptr;  	// TODO: needs a better solution + +	SDLContext & context = SDLContext::get_instance();  };  } // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index c4d724c..20a83f7 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,7 +1,3 @@ -#include <forward_list> -#include <functional> -#include <vector> -  #include "../ComponentManager.h"  #include "../api/BehaviorScript.h"  #include "../api/Script.h" @@ -14,31 +10,19 @@ using namespace crepe;  void ScriptSystem::update() {  	dbg_trace(); -	forward_list<reference_wrapper<Script>> scripts = this->get_scripts(); - -	for (auto & script_ref : scripts) { -		Script & script = script_ref.get(); -		if (!script.initialized) { -			script.init(); -			script.initialized = true; -		} -		script.update(); -	} -} - -forward_list<reference_wrapper<Script>> ScriptSystem::get_scripts() const { -	forward_list<reference_wrapper<Script>> scripts = {};  	ComponentManager & mgr = this->component_manager; -	vector<reference_wrapper<BehaviorScript>> behavior_scripts -		= mgr.get_components_by_type<BehaviorScript>(); +	RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); -	for (auto behavior_script_ref : behavior_scripts) { -		BehaviorScript & behavior_script = behavior_script_ref.get(); +	for (BehaviorScript & behavior_script : behavior_scripts) {  		if (!behavior_script.active) continue; +  		Script * script = behavior_script.script.get();  		if (script == nullptr) continue; -		scripts.push_front(*script); -	} -	return scripts; +		if (!script->initialized) { +			script->init(); +			script->initialized = true; +		} +		script->update(); +	}  } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index deb89cb..936e9ca 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -1,7 +1,5 @@  #pragma once -#include <forward_list> -  #include "System.h"  namespace crepe { @@ -25,15 +23,6 @@ public:  	 * the \c BehaviorScript instance.  	 */  	void update() override; - -private: -	/** -	 * \brief Aggregate all active \c BehaviorScript components and return a list -	 * of references to their \c Script instances (utility) -	 * -	 * \returns List of active \c Script instances -	 */ -	std::forward_list<std::reference_wrapper<Script>> get_scripts() const;  };  } // namespace crepe diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 36f7edc..28ea20e 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -1,7 +1,5 @@  #pragma once -#include "../ComponentManager.h" -  namespace crepe {  class ComponentManager; diff --git a/src/crepe/types.h b/src/crepe/types.h index 0d459e8..17f1619 100644 --- a/src/crepe/types.h +++ b/src/crepe/types.h @@ -1,9 +1,30 @@  #pragma once +#include "api/Vector2.h" +  #include <cstdint> +#include <functional> +#include <vector>  namespace crepe { +//! GameObject ID  typedef uint32_t game_object_id_t; -} +//! vector of reference_wrapper +template <typename T> +using RefVector = std::vector<std::reference_wrapper<T>>; + +//! Default Vector2<int> type +typedef Vector2<int> ivec2; + +//! Default Vector2<unsigned int> type +typedef Vector2<unsigned int> uvec2; + +//! Default Vector2<float> type +typedef Vector2<float> vec2; + +//! Default Vector2<double> type +typedef Vector2<double> dvec2; + +} // namespace crepe diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index d55b11e..fc0bb3a 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -34,11 +34,11 @@ class Log {  public:  	//! Log message severity  	enum Level { -		TRACE, //< Include (internal) function calls -		DEBUG, //< Include dbg_logf output -		INFO, //< General-purpose messages -		WARNING, //< Non-fatal errors -		ERROR, //< Fatal errors +		TRACE, //!< Include (internal) function calls +		DEBUG, //!< Include dbg_logf output +		INFO, //!< General-purpose messages +		WARNING, //!< Non-fatal errors +		ERROR, //!< Fatal errors  	};  	/** diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h index 8417a25..3201667 100644 --- a/src/crepe/util/OptionalRef.h +++ b/src/crepe/util/OptionalRef.h @@ -23,20 +23,7 @@ public:  	 *  	 * \return Reference to this (required for operator)  	 */ -  OptionalRef<T> & operator=(T & ref); -	/** -	 * \brief Check if this reference is not empty -	 * -	 * \returns `true` if reference is set, or `false` if it is not -	 */ -	explicit operator bool() const noexcept; - -	/** -	 * \brief Assign new reference -	 * -	 * \param ref Reference to assign -	 */ -	void set(T &) noexcept; +	OptionalRef<T> & operator=(T & ref);  	/**  	 * \brief Retrieve this reference  	 * @@ -44,21 +31,19 @@ public:  	 *  	 * \throws std::runtime_error if this function is called while the reference it not set  	 */ -	T & get() const; +	operator T &() const; +	/** +	 * \brief Check if this reference is not empty +	 * +	 * \returns `true` if reference is set, or `false` if it is not +	 */ +	explicit operator bool() const noexcept; +  	/**  	 * \brief Make this reference empty  	 */  	void clear() noexcept; -	//! Copy constructor -  OptionalRef(const OptionalRef<T> &); -	//! Move constructor -  OptionalRef(OptionalRef<T> &&); -	//! Copy assignment -  OptionalRef<T> & operator=(const OptionalRef<T> &); -	//! Move assignment -  OptionalRef<T> & operator=(OptionalRef<T> &&); -  private:  	/**  	 * \brief Reference to the value of type \c T @@ -68,7 +53,6 @@ private:  	T * ref = nullptr;  }; -} +} // namespace crepe  #include "OptionalRef.hpp" - diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp index 7b201b0..4608c9e 100644 --- a/src/crepe/util/OptionalRef.hpp +++ b/src/crepe/util/OptionalRef.hpp @@ -8,53 +8,19 @@ namespace crepe {  template <typename T>  OptionalRef<T>::OptionalRef(T & ref) { -	this->set(ref); -} - -template <typename T> -OptionalRef<T>::OptionalRef(const OptionalRef<T> & other) { -	this->ref = other.ref; -} - -template <typename T> -OptionalRef<T>::OptionalRef(OptionalRef<T> && other) { -	this->ref = other.ref; -	other.clear(); -} - -template <typename T> -OptionalRef<T> & OptionalRef<T>::operator=(const OptionalRef<T> & other) { -	this->ref = other.ref; -	return *this; -} - -template <typename T> -OptionalRef<T> & OptionalRef<T>::operator=(OptionalRef<T> && other) { -	this->ref = other.ref; -	other.clear(); -	return *this; +	this->ref = &ref;  }  template <typename T> -T & OptionalRef<T>::get() const { +OptionalRef<T>::operator T &() const {  	if (this->ref == nullptr)  		throw std::runtime_error("OptionalRef: attempt to dereference nullptr");  	return *this->ref;  }  template <typename T> -void OptionalRef<T>::set(T & ref) noexcept { -	this->ref = &ref; -} - -template <typename T> -void OptionalRef<T>::clear() noexcept { -	this->ref = nullptr; -} - -template <typename T>  OptionalRef<T> & OptionalRef<T>::operator=(T & ref) { -	this->set(ref); +	this->ref = &ref;  	return *this;  } @@ -63,5 +29,9 @@ OptionalRef<T>::operator bool() const noexcept {  	return this->ref != nullptr;  } +template <typename T> +void OptionalRef<T>::clear() noexcept { +	this->ref = nullptr;  } +} // namespace crepe diff --git a/src/crepe/util/Proxy.h b/src/crepe/util/Proxy.h index b34f7c6..789144a 100644 --- a/src/crepe/util/Proxy.h +++ b/src/crepe/util/Proxy.h @@ -16,6 +16,8 @@ template <typename T>  class Proxy {  public:  	//! Set operator +	Proxy & operator=(Proxy &); +	//! Set operator  	Proxy & operator=(const T &);  	//! Get operator  	operator const T &(); diff --git a/src/crepe/util/Proxy.hpp b/src/crepe/util/Proxy.hpp index b9923db..ef2b69f 100644 --- a/src/crepe/util/Proxy.hpp +++ b/src/crepe/util/Proxy.hpp @@ -14,6 +14,12 @@ Proxy<T> & Proxy<T>::operator=(const T & val) {  }  template <typename T> +Proxy<T> & Proxy<T>::operator=(Proxy & proxy) { +	this->broker.set(T(proxy)); +	return *this; +} + +template <typename T>  Proxy<T>::operator const T &() {  	return this->broker.get();  } diff --git a/src/doc/feature/gameobject.dox b/src/doc/feature/gameobject.dox new file mode 100644 index 0000000..c561874 --- /dev/null +++ b/src/doc/feature/gameobject.dox @@ -0,0 +1,18 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_gameobject GameObjects +\ingroup feature +\brief GameObject to create a Scene + +GameObjects are the fundamental building blocks of a Scene. They represent entities +in the game world and can have various components attached to them to define their +behavior and properties. GameObjects can be created and modified within the +Scene, allowing for a flexible and dynamic game environment. + +\see Component +\see Scene + +*/ +} diff --git a/src/doc/feature/scene.dox b/src/doc/feature/scene.dox new file mode 100644 index 0000000..eedc69a --- /dev/null +++ b/src/doc/feature/scene.dox @@ -0,0 +1,67 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_scene Scenes +\ingroup feature +\brief User-defined scenes + +Scenes can be used to implement game environments, and allow arbitrary game objects to be organized +as part of the game structure. Scenes are implemented as derivative classes of Scene, which are +added to the game using the SceneManager. Scenes describe the start of a Scene and cannot modify +GameObjects during runtime of a Scene (use \ref feature_script "Scripting" for this purpose). + +\see SceneManager +\see GameObject +\see Script +\see Scene + +\par Example + +This example demonstrates how to define and add scenes to the loop/scene manager in the `crepe` framework. +Each concrete scene should be derived from Scene. In the example below, the concrete scene is named MyScene. +A concrete scene should, at least, implement (override) two methods, namely load_scene() and get_name(). The +scene is build (using GameObjects) in the load_scene() method. GameObjects should be made using the +component_manager::new_object(). In the example below, two GameObjects (named object1 and object2) are added +to MyScene. object1 and object2 do not have any non-default Components attached to them, however, if needed, +this should also be done in load_scene(). Each concrete scene must have a unique name. This unique name is +used to load a new concrete scene (via a Script). The unique name is set using the get_name() method. In the +example below, MyScene's unique name is my_scene. +After setting up one or more concrete scene(s), the concrete scene(s) should be added to the loop/scene manager. +This is done in your main(). Firstly, the LoopManager should be instantiated. Than, all the concrete scene(s) +should be added to the loop/scene manger via loop_mgr::add_scene<>(). The templated argument should define the +concrete scene to be added. + +```cpp +#include <crepe/api/LoopManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Vector2.h> + +using namespace crepe; + +class MyScene : public Scene { +public: +	using Scene::Scene; + +	void load_scene() { +		auto & mgr = this->component_manager; +		GameObject object1 = mgr.new_object("object1", "tag_my_scene", vec2{0, 0}, 0, 1); +		GameObject object2 = mgr.new_object("object2", "tag_my_scene", vec2{1, 0}, 0, 1); +	} + +	string get_name() const { return "my_scene"; } +}; + +int main() { +	LoopManager loop_mgr; +	 +	// Add the scenes to the loop manager +	loop_mgr.add_scene<MyScene>(); + +	loop_mgr.start(); +} +``` + +*/ +} diff --git a/src/doc/feature/script.dox b/src/doc/feature/script.dox new file mode 100644 index 0000000..d25a63b --- /dev/null +++ b/src/doc/feature/script.dox @@ -0,0 +1,62 @@ +// vim:ft=doxygen +namespace crepe { +/** + +\defgroup feature_script Scripting +\ingroup feature +\brief User-defined scripts for game objects + +Scripts can be used to implement game behavior, and allow arbitrary code to run +as part of the game loop. Scripts are implemented as derivative classes of +Script, which are added to game objects using the BehaviorScript \ref Component +"component". + +\todo This section is incomplete: +- Utility functions to get components/events/etc inside script +- How to listen for events +- Extensions of script (keylistener) + +\see Script +\see BehaviorScript +\see GameObject + +\par Example + +First, define a class that inherits from Script. This class acts as an +interface, and has two functions (\ref Script::init "\c init()" and \ref +Script::update "\c update()"), which may be implemented (they are empty by +default). From now on, this derivative class will be referred to as a *concrete +script*. + +```cpp +#include <crepe/api/Script.h> +#include <crepe/api/BehaviorScript.h> + +class MyScript : public crepe::Script { +	void init() { +		// called once +	} +	void update() { +		// called on fixed update +	} +}; +``` + +Concrete scripts can be instantiated and attached to \ref GameObject +"game objects" using the BehaviorScript \ref Component "component". + +```cpp +using namespace crepe; +GameObject obj = component_manager.new_object("name"); + +// create BehaviorScript instance +BehaviorScript & behavior_script = obj.add_component<BehaviorScript>(); +// attach (and instantiate) MyScript to behavior_script +behavior_script.set_script<MyScript>(); + +// the above can also be done in a single call for convenience: +obj.add_component<BehaviorScript>().set_script<MyScript>(); +``` + +*/ +} diff --git a/src/doc/features.dox b/src/doc/features.dox new file mode 100644 index 0000000..4786bed --- /dev/null +++ b/src/doc/features.dox @@ -0,0 +1,10 @@ +// vim:ft=doxygen +/** + +\defgroup feature Features +\brief Engine components + +This page lists engine features and contains usage instructions for each +feature. + +*/ diff --git a/src/doc/index.dox b/src/doc/index.dox new file mode 100644 index 0000000..5ec7889 --- /dev/null +++ b/src/doc/index.dox @@ -0,0 +1,10 @@ +// vim:ft=doxygen +/** + +\mainpage crêpe game engine + +Welcome to the documentation for the crêpe game engine. + +\see feature + +*/ diff --git a/src/doc/installing.dox b/src/doc/installing.dox new file mode 100644 index 0000000..48b27d7 --- /dev/null +++ b/src/doc/installing.dox @@ -0,0 +1,9 @@ +// vim:ft=doxygen +/** + +\defgroup install Installation +\brief Engine installation instructions + +\todo This entire page + +*/ diff --git a/src/doc/layout.xml b/src/doc/layout.xml new file mode 100644 index 0000000..7f514d4 --- /dev/null +++ b/src/doc/layout.xml @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> +<doxygenlayout version="1.0"> +	<navindex> +		<tab type="mainpage" visible="yes" title=""/> +		<tab type="pages" visible="no" title="" intro=""/> +		<tab type="topics" visible="yes" title="" intro=""/> +		<tab type="modules" visible="yes" title="" intro=""> +			<tab type="modulelist" visible="yes" title="" intro=""/> +			<tab type="modulemembers" visible="yes" title="" intro=""/> +		</tab> +		<tab type="namespaces" visible="no" title=""> +			<tab type="namespacelist" visible="yes" title="" intro=""/> +			<tab type="namespacemembers" visible="yes" title="" intro=""/> +		</tab> +		<tab type="concepts" visible="yes" title=""> +		</tab> +		<tab type="interfaces" visible="yes" title=""> +			<tab type="interfacelist" visible="yes" title="" intro=""/> +			<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> +			<tab type="interfacehierarchy" visible="yes" title="" intro=""/> +		</tab> +		<tab type="classes" visible="yes" title=""> +			<tab type="classlist" visible="yes" title="" intro=""/> +			<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> +			<tab type="hierarchy" visible="yes" title="" intro=""/> +			<tab type="classmembers" visible="yes" title="" intro=""/> +		</tab> +		<tab type="structs" visible="yes" title=""> +			<tab type="structlist" visible="yes" title="" intro=""/> +			<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> +		</tab> +		<tab type="exceptions" visible="yes" title=""> +			<tab type="exceptionlist" visible="yes" title="" intro=""/> +			<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> +			<tab type="exceptionhierarchy" visible="yes" title="" intro=""/> +		</tab> +		<tab type="files" visible="yes" title=""> +			<tab type="filelist" visible="yes" title="" intro=""/> +			<tab type="globals" visible="yes" title="" intro=""/> +		</tab> +		<tab type="examples" visible="yes" title="" intro=""/> +	</navindex> +	<class> +		<briefdescription visible="yes"/> +		<detaileddescription title=""/> +		<includes visible="$SHOW_HEADERFILE"/> +		<inheritancegraph visible="yes"/> +		<collaborationgraph visible="yes"/> +		<memberdecl> +			<nestedclasses visible="yes" title=""/> +			<publictypes title=""/> +			<services title=""/> +			<interfaces title=""/> +			<publicslots title=""/> +			<signals title=""/> +			<publicmethods title=""/> +			<publicstaticmethods title=""/> +			<publicattributes title=""/> +			<publicstaticattributes title=""/> +			<protectedtypes title=""/> +			<protectedslots title=""/> +			<protectedmethods title=""/> +			<protectedstaticmethods title=""/> +			<protectedattributes title=""/> +			<protectedstaticattributes title=""/> +			<packagetypes title=""/> +			<packagemethods title=""/> +			<packagestaticmethods title=""/> +			<packageattributes title=""/> +			<packagestaticattributes title=""/> +			<properties title=""/> +			<events title=""/> +			<privatetypes title=""/> +			<privateslots title=""/> +			<privatemethods title=""/> +			<privatestaticmethods title=""/> +			<privateattributes title=""/> +			<privatestaticattributes title=""/> +			<friends title=""/> +			<related title="" subtitle=""/> +			<membergroups visible="yes"/> +		</memberdecl> +		<memberdef> +			<inlineclasses title=""/> +			<typedefs title=""/> +			<enums title=""/> +			<services title=""/> +			<interfaces title=""/> +			<constructors title=""/> +			<functions title=""/> +			<related title=""/> +			<variables title=""/> +			<properties title=""/> +			<events title=""/> +		</memberdef> +		<allmemberslink visible="yes"/> +		<usedfiles visible="$SHOW_USED_FILES"/> +		<authorsection visible="yes"/> +	</class> +	<namespace> +		<briefdescription visible="yes"/> +		<memberdecl> +			<nestednamespaces visible="yes" title=""/> +			<constantgroups visible="yes" title=""/> +			<interfaces visible="yes" title=""/> +			<classes visible="yes" title=""/> +			<concepts visible="yes" title=""/> +			<structs visible="yes" title=""/> +			<exceptions visible="yes" title=""/> +			<typedefs title=""/> +			<sequences title=""/> +			<dictionaries title=""/> +			<enums title=""/> +			<functions title=""/> +			<variables title=""/> +			<properties title=""/> +			<membergroups visible="yes"/> +		</memberdecl> +		<detaileddescription title=""/> +		<memberdef> +			<inlineclasses title=""/> +			<typedefs title=""/> +			<sequences title=""/> +			<dictionaries title=""/> +			<enums title=""/> +			<functions title=""/> +			<variables title=""/> +			<properties title=""/> +		</memberdef> +		<authorsection visible="yes"/> +	</namespace> +	<concept> +		<briefdescription visible="yes"/> +		<includes visible="$SHOW_HEADERFILE"/> +		<definition visible="yes" title=""/> +		<detaileddescription title=""/> +		<authorsection visible="yes"/> +	</concept> +	<file> +		<briefdescription visible="yes"/> +		<includes visible="$SHOW_INCLUDE_FILES"/> +		<includegraph visible="yes"/> +		<includedbygraph visible="yes"/> +		<sourcelink visible="yes"/> +		<memberdecl> +			<interfaces visible="yes" title=""/> +			<classes visible="yes" title=""/> +			<structs visible="yes" title=""/> +			<exceptions visible="yes" title=""/> +			<namespaces visible="yes" title=""/> +			<concepts visible="yes" title=""/> +			<constantgroups visible="yes" title=""/> +			<defines title=""/> +			<typedefs title=""/> +			<sequences title=""/> +			<dictionaries title=""/> +			<enums title=""/> +			<functions title=""/> +			<variables title=""/> +			<properties title=""/> +			<membergroups visible="yes"/> +		</memberdecl> +		<detaileddescription title=""/> +		<memberdef> +			<inlineclasses title=""/> +			<defines title=""/> +			<typedefs title=""/> +			<sequences title=""/> +			<dictionaries title=""/> +			<enums title=""/> +			<functions title=""/> +			<variables title=""/> +			<properties title=""/> +		</memberdef> +		<authorsection/> +	</file> +	<group> +		<detaileddescription title=""/> +		<groupgraph visible="yes"/> +		<memberdecl> +			<nestedgroups visible="yes" title=""/> +			<modules visible="yes" title=""/> +			<dirs visible="yes" title=""/> +			<files visible="yes" title=""/> +			<namespaces visible="yes" title=""/> +			<concepts visible="yes" title=""/> +			<classes visible="yes" title=""/> +			<defines title=""/> +			<typedefs title=""/> +			<sequences title=""/> +			<dictionaries title=""/> +			<enums title=""/> +			<enumvalues title=""/> +			<functions title=""/> +			<variables title=""/> +			<signals title=""/> +			<publicslots title=""/> +			<protectedslots title=""/> +			<privateslots title=""/> +			<events title=""/> +			<properties title=""/> +			<friends title=""/> +			<membergroups visible="yes"/> +		</memberdecl> +		<memberdef> +			<pagedocs/> +			<inlineclasses title=""/> +			<defines title=""/> +			<typedefs title=""/> +			<sequences title=""/> +			<dictionaries title=""/> +			<enums title=""/> +			<enumvalues title=""/> +			<functions title=""/> +			<variables title=""/> +			<signals title=""/> +			<publicslots title=""/> +			<protectedslots title=""/> +			<privateslots title=""/> +			<events title=""/> +			<properties title=""/> +			<friends title=""/> +		</memberdef> +		<authorsection visible="yes"/> +	</group> +	<module> +		<briefdescription visible="yes"/> +		<exportedmodules visible="yes"/> +		<memberdecl> +			<concepts visible="yes" title=""/> +			<classes visible="yes" title=""/> +			<enums title=""/> +			<typedefs title=""/> +			<functions title=""/> +			<variables title=""/> +			<membergroups title=""/> +		</memberdecl> +		<detaileddescription title=""/> +		<memberdecl> +			<files visible="yes"/> +		</memberdecl> +	</module> +	<directory> +		<briefdescription visible="yes"/> +		<directorygraph visible="yes"/> +		<memberdecl> +			<dirs visible="yes"/> +			<files visible="yes"/> +		</memberdecl> +		<detaileddescription title=""/> +	</directory> +</doxygenlayout> diff --git a/src/doc/style.css b/src/doc/style.css new file mode 100644 index 0000000..08bc9f5 --- /dev/null +++ b/src/doc/style.css @@ -0,0 +1,4 @@ +#titlearea, +address { +	display: none; +} diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 3a5b543..560e2bc 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,18 +16,8 @@ function(add_example target_name)  	add_dependencies(examples ${target_name})  endfunction() -add_example(audio_internal) -# add_example(components_internal) -add_example(script) -add_example(log) -add_example(rendering)  add_example(asset_manager) -add_example(physics)  add_example(savemgr) -add_example(proxy) -add_example(db) -add_example(ecs) -add_example(scene_manager) -add_example(particles) +add_example(rendering_particle)  add_example(gameloop) diff --git a/src/example/audio_internal.cpp b/src/example/audio_internal.cpp deleted file mode 100644 index f3bf349..0000000 --- a/src/example/audio_internal.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** \file - *  - * Standalone example for usage of the internal \c Sound class. - */ - -#include <crepe/api/Config.h> -#include <crepe/facade/SoundContext.h> -#include <crepe/facade/Sound.h> -#include <crepe/Asset.h> -#include <crepe/util/Log.h> - -#include <thread> - -using namespace crepe; -using namespace std; -using namespace std::chrono_literals; -using std::make_unique; - -// Unrelated stuff that is not part of this POC -int _ = []() { -	// Show dbg_trace() output -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::TRACE; - -	return 0; // satisfy compiler -}(); - -int main() { -	SoundContext ctx{}; -	Sound sound{ctx}; -	// Load a background track (Ogg Vorbis) -	auto bgm = Sound("mwe/audio/bgm.ogg"); -	// Load three short samples (WAV) -	auto sfx1 = Sound("mwe/audio/sfx1.wav"); -	auto sfx2 = Sound("mwe/audio/sfx2.wav"); -	auto sfx3 = Sound("mwe/audio/sfx3.wav"); - -	// Start the background track -	bgm.play(); - -	// Play each sample sequentially while pausing and resuming the background track -	this_thread::sleep_for(500ms); -	sfx1.play(); -	this_thread::sleep_for(500ms); -	sfx2.play(); -	bgm.pause(); -	this_thread::sleep_for(500ms); -	sfx3.play(); -	bgm.play(); -	this_thread::sleep_for(500ms); - -	// Play all samples simultaniously -	sfx1.play(); -	sfx2.play(); -	sfx3.play(); -	this_thread::sleep_for(1000ms); - -	// Stop all audio and exit -	return EXIT_SUCCESS; -} diff --git a/src/example/components_internal.cpp b/src/example/components_internal.cpp deleted file mode 100644 index 2a232a9..0000000 --- a/src/example/components_internal.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/** \file - *  - * Standalone example for usage of the internal ECS - */ - -#include <cassert> -#include <chrono> - -#include <crepe/Component.h> -#include <crepe/ComponentManager.h> - -#include <crepe/api/GameObject.h> -#include <crepe/api/Rigidbody.h> -#include <crepe/api/Sprite.h> - -#include <crepe/util/Log.h> - -using namespace crepe; -using namespace std; - -#define OBJ_COUNT 100000 - -int main() { -	dbg_trace(); - -	ComponentManager mgr{}; - -	auto start_adding = chrono::high_resolution_clock::now(); - -	for (int i = 0; i < OBJ_COUNT; ++i) { -		GameObject obj = mgr.new_object("Name", "Tag"); -		obj.add_component<Sprite>("test"); -		obj.add_component<Rigidbody>(0, 0, i); -	} - -	auto stop_adding = chrono::high_resolution_clock::now(); - -	auto sprites = mgr.get_components_by_type<Sprite>(); -	for (auto sprite : sprites) { -		assert(true); -	} - -	auto stop_looping = chrono::high_resolution_clock::now(); - -	auto add_time = chrono::duration_cast<chrono::microseconds>(stop_adding - start_adding); -	auto loop_time = chrono::duration_cast<chrono::microseconds>(stop_looping - stop_adding); -	printf("add time:  %ldus\n", add_time.count()); -	printf("loop time: %ldus\n", loop_time.count()); - -	return 0; -} diff --git a/src/example/db.cpp b/src/example/db.cpp deleted file mode 100644 index ee4e8fc..0000000 --- a/src/example/db.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include <crepe/api/Config.h> -#include <crepe/facade/DB.h> -#include <crepe/util/Log.h> - -using namespace crepe; -using namespace std; - -// run before main -static auto _ = []() { -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::TRACE; -	return 0; -}(); - -int main() { -	dbg_trace(); - -	DB db("file.db"); - -	const char * test_key = "test-key"; -	string test_data = "Hello world!"; - -	dbg_logf("DB has key = {}", db.has(test_key)); - -	db.set(test_key, test_data); - -	dbg_logf("key = \"{}\"", db.get(test_key)); - -	return 0; -} diff --git a/src/example/ecs.cpp b/src/example/ecs.cpp deleted file mode 100644 index d5ba51b..0000000 --- a/src/example/ecs.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include <iostream> - -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Metadata.h> -#include <crepe/api/Transform.h> - -using namespace crepe; -using namespace std; - -int main() { -	ComponentManager mgr{}; - -	// Create a few GameObjects -	try { -		GameObject body = mgr.new_object("body", "person", Vector2{0, 0}, 0, 1); -		GameObject right_leg = mgr.new_object("rightLeg", "person", Vector2{1, 1}, 0, 1); -		GameObject left_leg = mgr.new_object("leftLeg", "person", Vector2{1, 1}, 0, 1); -		GameObject right_foot = mgr.new_object("rightFoot", "person", Vector2{2, 2}, 0, 1); -		GameObject left_foot = mgr.new_object("leftFoot", "person", Vector2{2, 2}, 0, 1); - -		// Set the parent of each GameObject -		right_foot.set_parent(right_leg); -		left_foot.set_parent(left_leg); -		right_leg.set_parent(body); -		left_leg.set_parent(body); - -		// Adding a second Transform component is not allowed and will invoke an exception -		body.add_component<Transform>(Vector2{10, 10}, 0, 1); -	} catch (const exception & e) { -		cerr << e.what() << endl; -	} - -	// Get the Metadata and Transform components of each GameObject -	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); -	vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); - -	// Print the Metadata and Transform components -	for (auto & m : metadata) { -		cout << "Id: " << m.get().game_object_id << " Name: " << m.get().name -			 << " Tag: " << m.get().tag << " Parent: " << m.get().parent << " Children: "; -		for (auto & c : m.get().children) { -			cout << c << " "; -		} -		cout << endl; -	} -	for (auto & t : transform) { -		cout << "Id: " << t.get().game_object_id << " Position: [" << t.get().position.x -			 << ", " << t.get().position.y << "]" << endl; -	} - -	return 0; -} diff --git a/src/example/log.cpp b/src/example/log.cpp deleted file mode 100644 index 5baa021..0000000 --- a/src/example/log.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** \file - *  - * Standalone example for usage of the logging functions - */ - -#include <crepe/api/Config.h> -#include <crepe/util/Log.h> - -using namespace crepe; - -// unrelated setup code -int _ = []() { -	// make sure all log messages get printed -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::TRACE; - -	return 0; // satisfy compiler -}(); - -int main() { -	dbg_trace(); -	dbg_log("debug message"); -	Log::logf("info message with variable: {}", 3); -	Log::logf(Log::Level::WARNING, "warning"); -	Log::logf(Log::Level::ERROR, "error"); - -	return 0; -} diff --git a/src/example/particles.cpp b/src/example/particles.cpp deleted file mode 100644 index d4638a2..0000000 --- a/src/example/particles.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include <crepe/ComponentManager.h> -#include <crepe/api/AssetManager.h> - -#include <crepe/Component.h> -#include <crepe/api/Color.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/ParticleEmitter.h> -#include <crepe/api/Rigidbody.h> -#include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h> -#include <crepe/api/Transform.h> - -using namespace crepe; -using namespace std; - -int main(int argc, char * argv[]) { -	ComponentManager mgr{}; -	GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); -	Color color(0, 0, 0, 0); -	Sprite test_sprite = game_object.add_component<Sprite>( -		make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{true, true}); -	game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ -		.position = {0, 0}, -		.max_particles = 100, -		.emission_rate = 0, -		.min_speed = 0, -		.max_speed = 0, -		.min_angle = 0, -		.max_angle = 0, -		.begin_lifespan = 0, -		.end_lifespan = 0, -		.force_over_time = Vector2{0, 0}, -		.boundary{ -			.width = 0, -			.height = 0, -			.offset = Vector2{0, 0}, -			.reset_on_exit = false, -		}, -		.sprite = test_sprite, -	}); - -	return 0; -} diff --git a/src/example/physics.cpp b/src/example/physics.cpp deleted file mode 100644 index ad663a0..0000000 --- a/src/example/physics.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include <crepe/Component.h> -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Rigidbody.h> -#include <crepe/api/Transform.h> -#include <crepe/system/PhysicsSystem.h> - -using namespace crepe; -using namespace std; - -int main(int argc, char * argv[]) { -	ComponentManager mgr{}; - -	GameObject game_object = mgr.new_object("Name", "Tag", Vector2{0, 0}, 0, 0); -	game_object.add_component<Rigidbody>(Rigidbody::Data{ -		.mass = 1, -		.gravity_scale = 1, -		.body_type = Rigidbody::BodyType::DYNAMIC, -		.constraints = {0, 0, 0}, -		.use_gravity = true, -		.bounce = false, -	}); -	return 0; -} diff --git a/src/example/proxy.cpp b/src/example/proxy.cpp deleted file mode 100644 index 69451f8..0000000 --- a/src/example/proxy.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** \file - *  - * Standalone example for usage of the proxy type - */ - -#include <crepe/ValueBroker.h> -#include <crepe/api/Config.h> -#include <crepe/util/Log.h> -#include <crepe/util/Proxy.h> - -using namespace std; -using namespace crepe; - -void test_ro_ref(const int & val) {} -void test_rw_ref(int & val) {} -void test_ro_val(int val) {} - -int main() { -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::DEBUG; - -	int real_value = 0; - -	ValueBroker<int> broker{ -		[&real_value](const int & target) { -			dbg_logf("set {} to {}", real_value, target); -			real_value = target; -		}, -		[&real_value]() -> const int & { -			dbg_logf("get {}", real_value); -			return real_value; -		}, -	}; - -	Proxy<int> proxy{broker}; - -	broker.set(54); -	proxy = 84; - -	test_ro_ref(proxy); // this is allowed -	// test_rw_ref(proxy); // this should throw a compile error -	test_ro_val(proxy); - -	return 0; -} diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp deleted file mode 100644 index c813524..0000000 --- a/src/example/rendering.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "api/Camera.h" -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/system/RenderSystem.h> -#include <crepe/util/Log.h> - -#include <crepe/api/AssetManager.h> -#include <crepe/api/Color.h> -#include <crepe/api/Sprite.h> -#include <crepe/api/Texture.h> -#include <crepe/api/Transform.h> -#include <crepe/api/Vector2.h> - -#include <chrono> -#include <memory> - -using namespace std; -using namespace crepe; - -int main() { -	dbg_trace(); - -	ComponentManager mgr{}; -	RenderSystem sys{mgr}; - -	GameObject obj = mgr.new_object("name", "tag", Vector2{0, 0}, 1, 1); -	GameObject obj1 = mgr.new_object("name", "tag", Vector2{500, 0}, 1, 0.1); -	GameObject obj2 = mgr.new_object("name", "tag", Vector2{800, 0}, 1, 0.1); - -	// Normal adding components -	{ -		Color color(0, 0, 0, 0); -		obj.add_component<Sprite>( -			make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{false, false}); -		obj.add_component<Camera>(Color::get_red()); -	} -	{ -		Color color(0, 0, 0, 0); -		obj1.add_component<Sprite>( -			make_shared<Texture>("asset/texture/second.png"), color, FlipSettings{true, true}); -	} - -	/* -	{ -		Color color(0, 0, 0, 0); -		auto img = mgr.cache<Texture>("asset/texture/second.png"); -		obj2.add_component<Sprite>(img, color, FlipSettings{true, true}); -	} -	*/ - -	auto start = std::chrono::steady_clock::now(); -	while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { -		sys.update(); -	} -} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp new file mode 100644 index 0000000..b38e860 --- /dev/null +++ b/src/example/rendering_particle.cpp @@ -0,0 +1,72 @@ +#include "api/Camera.h" +#include "system/ParticleSystem.h" +#include "types.h" +#include <SDL2/SDL_timer.h> +#include <crepe/ComponentManager.h> + +#include <crepe/Component.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <crepe/system/RenderSystem.h> + +#include <chrono> +#include <iostream> +#include <memory> + +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { +	ComponentManager mgr; +	GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 0.1); +	RenderSystem sys{mgr}; +	ParticleSystem psys{mgr}; + +	Color color(255, 255, 255, 255); + +	Sprite & test_sprite = game_object.add_component<Sprite>( +		make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{false, false}); +	test_sprite.order_in_layer = 5; + +	auto & test = game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ +		.position = {0, 0}, +		.max_particles = 10, +		.emission_rate = 0.1, +		.min_speed = 6, +		.max_speed = 20, +		.min_angle = -20, +		.max_angle = 20, +		.begin_lifespan = 0, +		.end_lifespan = 60, +		.force_over_time = vec2{0, 0}, +		.boundary{ +			.width = 1000, +			.height = 1000, +			.offset = vec2{0, 0}, +			.reset_on_exit = false, +		}, +		.sprite = test_sprite, +	}); +	game_object.add_component<Camera>(Color::WHITE); + +	game_object +		.add_component<Sprite>(make_shared<Texture>("asset/texture/img.png"), color, +							   FlipSettings{false, false}) +		.order_in_layer +		= 6; + +	auto start = std::chrono::steady_clock::now(); +	while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { +		psys.update(); +		sys.update(); +		SDL_Delay(10); +	} + +	return 0; +} diff --git a/src/example/scene_manager.cpp b/src/example/scene_manager.cpp deleted file mode 100644 index accec7d..0000000 --- a/src/example/scene_manager.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <iostream> - -#include <crepe/ComponentManager.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Metadata.h> -#include <crepe/api/Scene.h> -#include <crepe/api/SceneManager.h> -#include <crepe/api/Vector2.h> - -using namespace crepe; -using namespace std; - -class ConcreteScene1 : public Scene { -public: -	using Scene::Scene; - -	void load_scene() { -		auto & mgr = this->component_manager; -		GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", Vector2{0, 0}, 0, 1); -		GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", Vector2{1, 0}, 0, 1); -		GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", Vector2{2, 0}, 0, 1); -	} -}; - -class ConcreteScene2 : public Scene { -public: -	using Scene::Scene; - -	void load_scene() { -		auto & mgr = this->component_manager; -		GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 0}, 0, 1); -		GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 1}, 0, 1); -		GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 2}, 0, 1); -		GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", Vector2{0, 3}, 0, 1); -	} -}; - -int main() { -	ComponentManager component_mgr{}; -	SceneManager scene_mgr{component_mgr}; - -	// Add the scenes to the scene manager -	scene_mgr.add_scene<ConcreteScene1>("scene1"); -	scene_mgr.add_scene<ConcreteScene2>("scene2"); - -	// There is no need to call set_next_scene() at the beginnen, because the first scene will be -	// automatically set as the next scene - -	// Load scene1 (the first scene added) -	scene_mgr.load_next_scene(); - -	// Get the Metadata components of each GameObject of Scene1 -	vector<reference_wrapper<Metadata>> metadata -		= component_mgr.get_components_by_type<Metadata>(); - -	cout << "Metadata components of Scene1:" << endl; -	// Print the Metadata -	for (auto & m : metadata) { -		cout << "Id: " << m.get().game_object_id << " Name: " << m.get().name -			 << " Tag: " << m.get().tag << endl; -	} - -	// Set scene2 as the next scene -	scene_mgr.set_next_scene("scene2"); -	// Load scene2 -	scene_mgr.load_next_scene(); - -	// Get the Metadata components of each GameObject of Scene2 -	metadata = component_mgr.get_components_by_type<Metadata>(); - -	cout << "Metadata components of Scene2:" << endl; -	// Print the Metadata -	for (auto & m : metadata) { -		cout << "Id: " << m.get().game_object_id << " Name: " << m.get().name -			 << " Tag: " << m.get().tag << endl; -	} - -	return 0; -} diff --git a/src/example/script.cpp b/src/example/script.cpp deleted file mode 100644 index a23295b..0000000 --- a/src/example/script.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** \file - *  - * Standalone example for usage of the script component and system - */ - -#include <crepe/ComponentManager.h> -#include <crepe/system/ScriptSystem.h> -#include <crepe/util/Log.h> - -#include <crepe/api/BehaviorScript.h> -#include <crepe/api/Config.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Script.h> -#include <crepe/api/Transform.h> - -using namespace crepe; -using namespace std; - -// Unrelated stuff that is not part of this POC -int _ = []() { -	// Show dbg_trace() output -	auto & cfg = Config::get_instance(); -	cfg.log.level = Log::Level::TRACE; - -	return 0; // satisfy compiler -}(); - -// User-defined script: -class MyScript : public Script { -	void update() { -		// Retrieve component from the same GameObject this script is on -		Transform & test = get_component<Transform>(); -		dbg_logf("Transform({:.2f}, {:.2f})", test.position.x, test.position.y); -	} -}; - -int main() { -	ComponentManager component_manager{}; -	ScriptSystem system{component_manager}; - -	// Create game object with Transform and BehaviorScript components -	GameObject obj = component_manager.new_object("name"); -	obj.add_component<BehaviorScript>().set_script<MyScript>(); - -	// Update all scripts. This should result in MyScript::update being called -	system.update(); - -	return EXIT_SUCCESS; -} diff --git a/src/makefile b/src/makefile index 5f80204..a0e8f02 100644 --- a/src/makefile +++ b/src/makefile @@ -1,6 +1,9 @@  .PHONY: FORCE -FMT := $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp')  format: FORCE -	clang-tidy -p build/compile_commands.json --fix-errors $(FMT) +	$(MAKE) -C .. $@ + +LINT := $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') +lint: FORCE +	clang-tidy -p build/compile_commands.json --fix-errors $(LINT) diff --git a/src/test/AssetTest.cpp b/src/test/AssetTest.cpp index 563a253..8aa7629 100644 --- a/src/test/AssetTest.cpp +++ b/src/test/AssetTest.cpp @@ -10,18 +10,12 @@ using namespace testing;  class AssetTest : public Test {  public:  	Config & cfg = Config::get_instance(); -	void SetUp() override { -		this->cfg.asset.root_pattern = ".crepe-root"; -	} +	void SetUp() override { this->cfg.asset.root_pattern = ".crepe-root"; }  }; -TEST_F(AssetTest, Existant) { -	ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); -} +TEST_F(AssetTest, Existant) { ASSERT_NO_THROW(Asset{"asset/texture/img.png"}); } -TEST_F(AssetTest, Nonexistant) { -	ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); -} +TEST_F(AssetTest, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); }  TEST_F(AssetTest, Rootless) {  	cfg.asset.root_pattern.clear(); @@ -30,4 +24,3 @@ TEST_F(AssetTest, Rootless) {  	Asset asset{arbitrary};  	ASSERT_EQ(arbitrary, asset.get_path());  } - diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 437c296..b67baad 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -7,5 +7,11 @@ target_sources(test_main PUBLIC  	AssetTest.cpp  	ResourceManagerTest.cpp  	OptionalRefTest.cpp +	RenderSystemTest.cpp +	EventTest.cpp +	ECSTest.cpp +	SceneManagerTest.cpp +	ValueBrokerTest.cpp +	DBTest.cpp +	Vector2Test.cpp  ) - diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp new file mode 100644 index 0000000..e80814c --- /dev/null +++ b/src/test/DBTest.cpp @@ -0,0 +1,28 @@ +#include <crepe/facade/DB.h> +#include <gtest/gtest.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class DBTest : public Test { +public: +	DB db; +}; + +TEST_F(DBTest, ReadWrite) { +	db.set("foo", "bar"); +	EXPECT_EQ(db.get("foo"), "bar"); +} + +TEST_F(DBTest, Nonexistant) { +	EXPECT_THROW(db.get("foo"), std::out_of_range); +	db.set("foo", "bar"); +	EXPECT_NO_THROW(db.get("foo")); +} + +TEST_F(DBTest, Has) { +	EXPECT_EQ(db.has("foo"), false); +	db.set("foo", "bar"); +	EXPECT_EQ(db.has("foo"), true); +} diff --git a/src/test/ECSTest.cpp b/src/test/ECSTest.cpp new file mode 100644 index 0000000..80b936b --- /dev/null +++ b/src/test/ECSTest.cpp @@ -0,0 +1,236 @@ +#include <gtest/gtest.h> + +#define protected public + +#include <crepe/ComponentManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> + +using namespace std; +using namespace crepe; + +class ECSTest : public ::testing::Test { +public: +	ComponentManager mgr{}; +}; + +TEST_F(ECSTest, createGameObject) { +	GameObject obj = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); +	vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 1); +	EXPECT_EQ(transform.size(), 1); + +	EXPECT_EQ(metadata[0].get().name, "body"); +	EXPECT_EQ(metadata[0].get().tag, "person"); +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[0].get().children.size(), 0); + +	EXPECT_EQ(transform[0].get().position.x, 0); +	EXPECT_EQ(transform[0].get().position.y, 0); +	EXPECT_EQ(transform[0].get().rotation, 0); +	EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, deleteAllGameObjects) { +	GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + +	mgr.delete_all_components(); + +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); +	vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 0); +	EXPECT_EQ(transform.size(), 0); + +	GameObject obj2 = mgr.new_object("body2", "person2", vec2{1, 0}, 5, 1); + +	metadata = mgr.get_components_by_type<Metadata>(); +	transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 1); +	EXPECT_EQ(transform.size(), 1); + +	EXPECT_EQ(metadata[0].get().game_object_id, 0); +	EXPECT_EQ(metadata[0].get().name, "body2"); +	EXPECT_EQ(metadata[0].get().tag, "person2"); +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[0].get().children.size(), 0); + +	EXPECT_EQ(transform[0].get().game_object_id, 0); +	EXPECT_EQ(transform[0].get().position.x, 1); +	EXPECT_EQ(transform[0].get().position.y, 0); +	EXPECT_EQ(transform[0].get().rotation, 5); +	EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, deleteGameObject) { +	GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + +	mgr.delete_all_components_of_id(0); + +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); +	vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 1); +	EXPECT_EQ(transform.size(), 1); + +	EXPECT_EQ(metadata[0].get().game_object_id, 1); +	EXPECT_EQ(metadata[0].get().name, "body"); +	EXPECT_EQ(metadata[0].get().tag, "person"); +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[0].get().children.size(), 0); + +	EXPECT_EQ(transform[0].get().game_object_id, 1); +	EXPECT_EQ(transform[0].get().position.x, 0); +	EXPECT_EQ(transform[0].get().position.y, 0); +	EXPECT_EQ(transform[0].get().rotation, 0); +	EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, manyGameObjects) { +	for (int i = 0; i < 5000; i++) { +		GameObject obj = mgr.new_object("body", "person", vec2{0, 0}, 0, i); +	} + +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); +	vector<reference_wrapper<Transform>> transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 5000); +	EXPECT_EQ(transform.size(), 5000); +	for (int i = 0; i < 5000; i++) { +		EXPECT_EQ(metadata[i].get().game_object_id, i); +		EXPECT_EQ(metadata[i].get().name, "body"); +		EXPECT_EQ(metadata[i].get().tag, "person"); +		EXPECT_EQ(metadata[i].get().parent, -1); +		EXPECT_EQ(metadata[i].get().children.size(), 0); + +		EXPECT_EQ(transform[i].get().game_object_id, i); +		EXPECT_EQ(transform[i].get().position.x, 0); +		EXPECT_EQ(transform[i].get().position.y, 0); +		EXPECT_EQ(transform[i].get().rotation, 0); +		EXPECT_EQ(transform[i].get().scale, i); +	} + +	mgr.delete_components<Metadata>(); + +	metadata = mgr.get_components_by_type<Metadata>(); +	transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 0); +	EXPECT_EQ(transform.size(), 5000); + +	for (int i = 0; i < 10000 - 5000; i++) { +		string tag = "person" + to_string(i); +		GameObject obj = mgr.new_object("body", tag, vec2{0, 0}, i, 0); +	} + +	metadata = mgr.get_components_by_type<Metadata>(); +	transform = mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 10000 - 5000); +	EXPECT_EQ(transform.size(), 10000); +} + +TEST_F(ECSTest, getComponentsByID) { +	GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +	GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); + +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_id<Metadata>(0); +	vector<reference_wrapper<Transform>> transform = mgr.get_components_by_id<Transform>(1); + +	EXPECT_EQ(metadata.size(), 1); +	EXPECT_EQ(transform.size(), 1); + +	EXPECT_EQ(metadata[0].get().game_object_id, 0); +	EXPECT_EQ(metadata[0].get().name, "body"); +	EXPECT_EQ(metadata[0].get().tag, "person"); +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[0].get().children.size(), 0); + +	EXPECT_EQ(transform[0].get().game_object_id, 1); +	EXPECT_EQ(transform[0].get().position.x, 0); +	EXPECT_EQ(transform[0].get().position.y, 0); +	EXPECT_EQ(transform[0].get().rotation, 0); +	EXPECT_EQ(transform[0].get().scale, 1); +} + +TEST_F(ECSTest, tooMuchComponents) { +	try { +		GameObject obj0 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +		obj0.add_component<Transform>(vec2{10, 10}, 0, 1); +	} catch (const exception & e) { +		EXPECT_EQ(e.what(), +				  string("Exceeded maximum number of instances for this component type")); +	} + +	try { +		GameObject obj1 = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +		obj1.add_component<Metadata>("body", "person"); +	} catch (const exception & e) { +		EXPECT_EQ(e.what(), +				  string("Exceeded maximum number of instances for this component type")); +	} + +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + +	EXPECT_EQ(metadata.size(), 2); +	EXPECT_EQ(metadata[0].get().name, "body"); +	EXPECT_EQ(metadata[1].get().name, "body"); +} + +TEST_F(ECSTest, partentChild) { +	{ +		GameObject body = mgr.new_object("body", "person", vec2{0, 0}, 0, 1); +		GameObject right_leg = mgr.new_object("rightLeg", "person", vec2{1, 1}, 0, 1); +		GameObject left_leg = mgr.new_object("leftLeg", "person", vec2{1, 1}, 0, 1); +		GameObject right_foot = mgr.new_object("rightFoot", "person", vec2{2, 2}, 0, 1); +		GameObject left_foot = mgr.new_object("leftFoot", "person", vec2{2, 2}, 0, 1); + +		// Set the parent of each GameObject +		right_foot.set_parent(right_leg); +		left_foot.set_parent(left_leg); +		right_leg.set_parent(body); +		left_leg.set_parent(body); +	} + +	// Get the Metadata and Transform components of each GameObject +	vector<reference_wrapper<Metadata>> metadata = mgr.get_components_by_type<Metadata>(); + +	// Check IDs +	EXPECT_EQ(metadata[0].get().game_object_id, 0); +	EXPECT_EQ(metadata[1].get().game_object_id, 1); +	EXPECT_EQ(metadata[2].get().game_object_id, 2); +	EXPECT_EQ(metadata[3].get().game_object_id, 3); +	EXPECT_EQ(metadata[4].get().game_object_id, 4); + +	// Check the parent-child relationships +	EXPECT_EQ(metadata[0].get().name, "body"); +	EXPECT_EQ(metadata[1].get().name, "rightLeg"); +	EXPECT_EQ(metadata[2].get().name, "leftLeg"); +	EXPECT_EQ(metadata[3].get().name, "rightFoot"); +	EXPECT_EQ(metadata[4].get().name, "leftFoot"); + +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[1].get().parent, 0); +	EXPECT_EQ(metadata[2].get().parent, 0); +	EXPECT_EQ(metadata[3].get().parent, 1); +	EXPECT_EQ(metadata[4].get().parent, 2); + +	EXPECT_EQ(metadata[0].get().children.size(), 2); +	EXPECT_EQ(metadata[1].get().children.size(), 1); +	EXPECT_EQ(metadata[2].get().children.size(), 1); +	EXPECT_EQ(metadata[3].get().children.size(), 0); +	EXPECT_EQ(metadata[4].get().children.size(), 0); + +	EXPECT_EQ(metadata[0].get().children[0], 1); +	EXPECT_EQ(metadata[0].get().children[1], 2); +	EXPECT_EQ(metadata[1].get().children[0], 3); +	EXPECT_EQ(metadata[2].get().children[0], 4); +} diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp new file mode 100644 index 0000000..b0e6c9c --- /dev/null +++ b/src/test/EventTest.cpp @@ -0,0 +1,254 @@ + +#include "api/Event.h" +#include "api/EventManager.h" +#include "api/IKeyListener.h" +#include "api/IMouseListener.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class EventManagerTest : public ::testing::Test { +protected: +	void SetUp() override { +		// Clear any existing subscriptions or events before each test +		EventManager::get_instance().clear(); +	} + +	void TearDown() override { +		// Ensure cleanup after each test +		EventManager::get_instance().clear(); +	} +}; +class MockKeyListener : public IKeyListener { +public: +	MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override)); +	MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override)); +}; + +class MockMouseListener : public IMouseListener { +public: +	MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override)); +	MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override)); +	MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override)); +	MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override)); +}; + +TEST_F(EventManagerTest, EventSubscription) { +	EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { +		std::cout << "Key Event Triggered" << std::endl; +		return true; +	}; + +	// Subscribe to KeyPressEvent +	EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1); + +	// Verify subscription (not directly verifiable; test by triggering event) + +	EventManager::get_instance().trigger_event<KeyPressEvent>( +		KeyPressEvent{ +			.repeat = true, +			.key = Keycode::A, +		}, +		1); +	EventManager::get_instance().trigger_event<KeyPressEvent>( +		KeyPressEvent{ +			.repeat = true, +			.key = Keycode::A, + +		}, +		EventManager::CHANNEL_ALL); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) { +	bool triggered = false; + +	EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { +		triggered = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; +	}; +	EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, +															EventManager::CHANNEL_ALL); + +	MouseClickEvent click_event{ +		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; +	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, +																EventManager::CHANNEL_ALL); + +	EXPECT_TRUE(triggered); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) { +	bool triggered = false; +	int test_channel = 1; +	EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { +		triggered = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; +	}; +	EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel); + +	MouseClickEvent click_event{ +		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; +	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, +																EventManager::CHANNEL_ALL); + +	EXPECT_FALSE(triggered); +	EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel); +} + +TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { +	EventManager & event_manager = EventManager::get_instance(); + +	// Flags to track handler calls +	bool triggered_true = false; +	bool triggered_false = false; + +	// Handlers +	EventHandler<MouseClickEvent> mouse_handler_true = [&](const MouseClickEvent & e) { +		triggered_true = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return true; // Stops propagation +	}; + +	EventHandler<MouseClickEvent> mouse_handler_false = [&](const MouseClickEvent & e) { +		triggered_false = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; // Allows propagation +	}; + +	// Test event +	MouseClickEvent click_event{ +		.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; +	event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); +	event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + +	// Trigger event +	event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + +	// Check that only the true handler was triggered +	EXPECT_TRUE(triggered_true); +	EXPECT_FALSE(triggered_false); + +	// Reset and clear +	triggered_true = false; +	triggered_false = false; +	event_manager.clear(); +	event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); +	event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + +	// Trigger event again +	event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + +	// Check that both handlers were triggered +	EXPECT_TRUE(triggered_true); +	EXPECT_TRUE(triggered_false); +} + +TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { +	EventManager & event_manager = EventManager::get_instance(); +	bool triggered1 = false; +	bool triggered2 = false; +	int test_channel = 1; +	EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { +		triggered1 = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; // Allows propagation +	}; +	EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { +		triggered2 = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; // Allows propagation +	}; +	event_manager.subscribe<MouseClickEvent>(mouse_handler1); +	event_manager.subscribe<MouseClickEvent>(mouse_handler2, test_channel); + +	event_manager.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); +	event_manager.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}, +		test_channel); +	event_manager.dispatch_events(); +	EXPECT_TRUE(triggered1); +	EXPECT_TRUE(triggered2); +} + +TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { +	EventManager & event_manager = EventManager::get_instance(); + +	// Flags to track if handlers are triggered +	bool triggered1 = false; +	bool triggered2 = false; + +	// Define EventHandlers +	EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { +		triggered1 = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; // Allows propagation +	}; + +	EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { +		triggered2 = true; +		EXPECT_EQ(e.mouse_x, 100); +		EXPECT_EQ(e.mouse_y, 200); +		EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); +		return false; // Allows propagation +	}; +	// Subscribe handlers +	subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1); +	subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2); + +	// Queue events +	event_manager.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + +	// Dispatch events - both handlers should be triggered +	event_manager.dispatch_events(); +	EXPECT_TRUE(triggered1); // Handler 1 should be triggered +	EXPECT_TRUE(triggered2); // Handler 2 should be triggered + +	// Reset flags +	triggered1 = false; +	triggered2 = false; + +	// Unsubscribe handler1 +	event_manager.unsubscribe(handler1_id); + +	// Queue the same event again +	event_manager.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + +	// Dispatch events - only handler 2 should be triggered, handler 1 should NOT +	event_manager.dispatch_events(); +	EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered +	EXPECT_TRUE(triggered2); // Handler 2 should be triggered + +	// Reset flags +	triggered2 = false; + +	// Unsubscribe handler2 +	event_manager.unsubscribe(handler2_id); + +	// Queue the event again +	event_manager.queue_event<MouseClickEvent>( +		MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + +	// Dispatch events - no handler should be triggered +	event_manager.dispatch_events(); +	EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered +	EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered +} diff --git a/src/test/OptionalRefTest.cpp b/src/test/OptionalRefTest.cpp index 219ccca..83f7b23 100644 --- a/src/test/OptionalRefTest.cpp +++ b/src/test/OptionalRefTest.cpp @@ -6,33 +6,37 @@ using namespace std;  using namespace crepe;  using namespace testing; -TEST(OptionalRefTest, Explicit) { +TEST(OptionalRefTest, Normal) {  	string value = "foo"; -	OptionalRef<string> ref; -	EXPECT_FALSE(ref); -	ASSERT_THROW(ref.get(), runtime_error); +	OptionalRef<string> ref = value; -	ref.set(value);  	EXPECT_TRUE(ref); -	ASSERT_NO_THROW(ref.get()); +	ASSERT_NO_THROW({ +		string & value_ref = ref; +		EXPECT_EQ(value_ref, value); +	});  	ref.clear();  	EXPECT_FALSE(ref); -	ASSERT_THROW(ref.get(), runtime_error); +	ASSERT_THROW({ string & value_ref = ref; }, runtime_error);  } -TEST(OptionalRefTest, Implicit) { +TEST(OptionalRefTest, Empty) {  	string value = "foo"; -	OptionalRef<string> ref = value; -	EXPECT_TRUE(ref); -	ASSERT_NO_THROW(ref.get()); +	OptionalRef<string> ref; -	ref.clear();  	EXPECT_FALSE(ref); -	ASSERT_THROW(ref.get(), runtime_error); - -	ref = value; -	EXPECT_TRUE(ref); -	ASSERT_NO_THROW(ref.get()); +	ASSERT_THROW({ string & value_ref = ref; }, runtime_error);  } +TEST(OptionalRefTest, Chain) { +	string value = "foo"; +	OptionalRef<string> ref1 = value; +	OptionalRef<string> ref2 = ref1; + +	EXPECT_TRUE(ref2); +	string & value_ref = ref2; +	EXPECT_EQ(value_ref, value); +	value_ref = "bar"; +	EXPECT_EQ(value_ref, value); +} diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index cfbbc0e..8b81e74 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -25,10 +25,10 @@ public:  		std::vector<std::reference_wrapper<Transform>> transforms  			= mgr.get_components_by_id<Transform>(0);  		if (transforms.empty()) { -			GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); +			GameObject game_object = mgr.new_object("", "", vec2{0, 0}, 0, 0);  			Color color(0, 0, 0, 0); -			Sprite test_sprite = game_object.add_component<Sprite>( +			Sprite & test_sprite = game_object.add_component<Sprite>(  				make_shared<Texture>("asset/texture/img.png"), color,  				FlipSettings{true, true}); @@ -42,11 +42,11 @@ public:  				.max_angle = 0,  				.begin_lifespan = 0,  				.end_lifespan = 0, -				.force_over_time = Vector2{0, 0}, +				.force_over_time = vec2{0, 0},  				.boundary{  					.width = 0,  					.height = 0, -					.offset = Vector2{0, 0}, +					.offset = vec2{0, 0},  					.reset_on_exit = false,  				},  				.sprite = test_sprite, @@ -68,8 +68,8 @@ public:  		emitter.data.max_angle = 0;  		emitter.data.begin_lifespan = 0;  		emitter.data.end_lifespan = 0; -		emitter.data.force_over_time = Vector2{0, 0}; -		emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; +		emitter.data.force_over_time = vec2{0, 0}; +		emitter.data.boundary = {0, 0, vec2{0, 0}, false};  		for (auto & particle : emitter.data.particles) {  			particle.active = false;  		} diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 1e37c26..33b6020 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -20,12 +20,12 @@ public:  		vector<reference_wrapper<Transform>> transforms  			= mgr.get_components_by_id<Transform>(0);  		if (transforms.empty()) { -			auto entity = mgr.new_object("", "", Vector2{0, 0}, 0, 0); +			auto entity = mgr.new_object("", "", vec2{0, 0}, 0, 0);  			entity.add_component<Rigidbody>(Rigidbody::Data{  				.mass = 1,  				.gravity_scale = 1,  				.body_type = Rigidbody::BodyType::DYNAMIC, -				.max_linear_velocity = Vector2{10, 10}, +				.max_linear_velocity = vec2{10, 10},  				.max_angular_velocity = 10,  				.constraints = {0, 0},  				.use_gravity = true, diff --git a/src/test/RenderSystemTest.cpp b/src/test/RenderSystemTest.cpp new file mode 100644 index 0000000..ac479d3 --- /dev/null +++ b/src/test/RenderSystemTest.cpp @@ -0,0 +1,174 @@ +#include "api/Camera.h" +#include <functional> +#include <gtest/gtest.h> +#include <memory> +#include <vector> + +#define private public +#define protected public + +#include <crepe/ComponentManager.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> + +#include <crepe/system/RenderSystem.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class RenderSystemTest : public Test { +public: +	ComponentManager mgr{}; +	RenderSystem sys{mgr}; +	GameObject entity1 = this->mgr.new_object("name"); +	GameObject entity2 = this->mgr.new_object("name"); +	GameObject entity3 = this->mgr.new_object("name"); +	GameObject entity4 = this->mgr.new_object("name"); + +	void SetUp() override { +		auto & sprite1 +			= entity1.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), +											Color(0, 0, 0, 0), FlipSettings{false, false}); +		ASSERT_NE(sprite1.sprite_image.get(), nullptr); +		sprite1.order_in_layer = 5; +		sprite1.sorting_in_layer = 5; +		EXPECT_EQ(sprite1.order_in_layer, 5); +		EXPECT_EQ(sprite1.sorting_in_layer, 5); +		auto & sprite2 +			= entity2.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), +											Color(0, 0, 0, 0), FlipSettings{false, false}); +		ASSERT_NE(sprite2.sprite_image.get(), nullptr); +		sprite2.sorting_in_layer = 2; +		sprite2.order_in_layer = 1; + +		EXPECT_EQ(sprite2.sorting_in_layer, 2); +		EXPECT_EQ(sprite2.order_in_layer, 1); + +		auto & sprite3 +			= entity3.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), +											Color(0, 0, 0, 0), FlipSettings{false, false}); +		ASSERT_NE(sprite3.sprite_image.get(), nullptr); +		sprite3.sorting_in_layer = 1; +		sprite3.order_in_layer = 2; + +		EXPECT_EQ(sprite3.sorting_in_layer, 1); +		EXPECT_EQ(sprite3.order_in_layer, 2); + +		auto & sprite4 +			= entity4.add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), +											Color(0, 0, 0, 0), FlipSettings{false, false}); +		ASSERT_NE(sprite4.sprite_image.get(), nullptr); +		sprite4.sorting_in_layer = 1; +		sprite4.order_in_layer = 1; +		EXPECT_EQ(sprite4.sorting_in_layer, 1); +		EXPECT_EQ(sprite4.order_in_layer, 1); +	} +}; + +TEST_F(RenderSystemTest, expected_throws) { +	GameObject entity1 = this->mgr.new_object("NAME"); + +	// no texture img +	EXPECT_ANY_THROW({ +		entity1.add_component<Sprite>(make_shared<Texture>("NO_IMAGE"), Color(0, 0, 0, 0), +									  FlipSettings{false, false}); +	}); + +	// No camera +	EXPECT_ANY_THROW({ this->sys.update(); }); +} + +TEST_F(RenderSystemTest, make_sprites) {} + +TEST_F(RenderSystemTest, sorting_sprites) { +	vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); +	ASSERT_EQ(sprites.size(), 4); + +	vector<reference_wrapper<Sprite>> sorted_sprites = this->sys.sort(sprites); +	ASSERT_EQ(sorted_sprites.size(), 4); + +	// Expected order after sorting: +	// 1. sorting_in_layer: 1, order_in_layer: 1 (entity4) +	// 2. sorting_in_layer: 1, order_in_layer: 2 (entity3) +	// 3. sorting_in_layer: 2, order_in_layer: 1 (entity2) +	// 4. sorting_in_layer: 5, order_in_layer: 5 (entity1) + +	EXPECT_EQ(sorted_sprites[0].get().sorting_in_layer, 1); +	EXPECT_EQ(sorted_sprites[0].get().order_in_layer, 1); + +	EXPECT_EQ(sorted_sprites[1].get().sorting_in_layer, 1); +	EXPECT_EQ(sorted_sprites[1].get().order_in_layer, 2); + +	EXPECT_EQ(sorted_sprites[2].get().sorting_in_layer, 2); +	EXPECT_EQ(sorted_sprites[2].get().order_in_layer, 1); + +	EXPECT_EQ(sorted_sprites[3].get().sorting_in_layer, 5); +	EXPECT_EQ(sorted_sprites[3].get().order_in_layer, 5); + +	for (size_t i = 1; i < sorted_sprites.size(); ++i) { +		const Sprite & prev = sorted_sprites[i - 1].get(); +		const Sprite & curr = sorted_sprites[i].get(); + +		if (prev.sorting_in_layer == curr.sorting_in_layer) { +			EXPECT_LE(prev.order_in_layer, curr.order_in_layer); +		} else { +			EXPECT_LE(prev.sorting_in_layer, curr.sorting_in_layer); +		} +	} +} + +TEST_F(RenderSystemTest, Update) { +	entity1.add_component<Camera>(Color::WHITE); +	{ +		vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); +		ASSERT_EQ(sprites.size(), 4); + +		EXPECT_EQ(sprites[0].get().game_object_id, 0); +		EXPECT_EQ(sprites[1].get().game_object_id, 1); +		EXPECT_EQ(sprites[2].get().game_object_id, 2); +		EXPECT_EQ(sprites[3].get().game_object_id, 3); +	} +	this->sys.update(); +	{ +		vector<reference_wrapper<Sprite>> sprites = this->mgr.get_components_by_type<Sprite>(); +		ASSERT_EQ(sprites.size(), 4); + +		EXPECT_EQ(sprites[0].get().game_object_id, 0); +		EXPECT_EQ(sprites[1].get().game_object_id, 1); +		EXPECT_EQ(sprites[2].get().game_object_id, 2); +		EXPECT_EQ(sprites[3].get().game_object_id, 3); +	} +} + +TEST_F(RenderSystemTest, Camera) { +	{ +		auto cameras = this->mgr.get_components_by_type<Camera>(); +		EXPECT_NE(cameras.size(), 1); +	} +	{ +		entity1.add_component<Camera>(Color::WHITE); +		auto cameras = this->mgr.get_components_by_type<Camera>(); +		EXPECT_EQ(cameras.size(), 1); +	} + +	//TODO improve with newer version +} +TEST_F(RenderSystemTest, Color) { +	entity1.add_component<Camera>(Color::WHITE); +	auto & sprite = this->mgr.get_components_by_id<Sprite>(entity1.id).front().get(); +	ASSERT_NE(sprite.sprite_image.get(), nullptr); + +	sprite.color = Color::GREEN; +	EXPECT_EQ(sprite.color.r, Color::GREEN.r); +	EXPECT_EQ(sprite.color.g, Color::GREEN.g); +	EXPECT_EQ(sprite.color.b, Color::GREEN.b); +	EXPECT_EQ(sprite.color.a, Color::GREEN.a); +	this->sys.update(); +	EXPECT_EQ(sprite.color.r, Color::GREEN.r); +	EXPECT_EQ(sprite.color.g, Color::GREEN.g); +	EXPECT_EQ(sprite.color.b, Color::GREEN.b); +	EXPECT_EQ(sprite.color.a, Color::GREEN.a); +} diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp new file mode 100644 index 0000000..f3d2387 --- /dev/null +++ b/src/test/SceneManagerTest.cpp @@ -0,0 +1,126 @@ +#include <crepe/ComponentManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Metadata.h> +#include <crepe/api/Scene.h> +#include <crepe/api/SceneManager.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <gtest/gtest.h> + +using namespace std; +using namespace crepe; + +class ConcreteScene1 : public Scene { +public: +	using Scene::Scene; + +	void load_scene() { +		auto & mgr = this->component_manager; +		GameObject object1 = mgr.new_object("scene_1", "tag_scene_1", vec2{0, 0}, 0, 1); +		GameObject object2 = mgr.new_object("scene_1", "tag_scene_1", vec2{1, 0}, 0, 1); +		GameObject object3 = mgr.new_object("scene_1", "tag_scene_1", vec2{2, 0}, 0, 1); +	} + +	string get_name() const { return "scene1"; } +}; + +class ConcreteScene2 : public Scene { +public: +	using Scene::Scene; + +	void load_scene() { +		auto & mgr = this->component_manager; +		GameObject object1 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 0}, 0, 1); +		GameObject object2 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 1}, 0, 1); +		GameObject object3 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 2}, 0, 1); +		GameObject object4 = mgr.new_object("scene_2", "tag_scene_2", vec2{0, 3}, 0, 1); +	} + +	string get_name() const { return "scene2"; } +}; + +class SceneManagerTest : public ::testing::Test { +public: +	ComponentManager component_mgr{}; +	SceneManager scene_mgr{component_mgr}; +}; + +TEST_F(SceneManagerTest, loadScene) { +	scene_mgr.add_scene<ConcreteScene1>(); +	scene_mgr.add_scene<ConcreteScene2>(); + +	scene_mgr.load_next_scene(); + +	vector<reference_wrapper<Metadata>> metadata +		= component_mgr.get_components_by_type<Metadata>(); +	vector<reference_wrapper<Transform>> transform +		= component_mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 3); +	EXPECT_EQ(transform.size(), 3); + +	EXPECT_EQ(metadata[0].get().game_object_id, 0); +	EXPECT_EQ(metadata[0].get().name, "scene_1"); +	EXPECT_EQ(metadata[0].get().tag, "tag_scene_1"); +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[0].get().children.size(), 0); +	EXPECT_EQ(transform[0].get().position.x, 0); +	EXPECT_EQ(transform[0].get().position.y, 0); + +	EXPECT_EQ(metadata[1].get().game_object_id, 1); +	EXPECT_EQ(metadata[1].get().name, "scene_1"); +	EXPECT_EQ(metadata[1].get().tag, "tag_scene_1"); +	EXPECT_EQ(metadata[1].get().parent, -1); +	EXPECT_EQ(metadata[1].get().children.size(), 0); +	EXPECT_EQ(transform[1].get().position.x, 1); +	EXPECT_EQ(transform[1].get().position.y, 0); + +	EXPECT_EQ(metadata[2].get().game_object_id, 2); +	EXPECT_EQ(metadata[2].get().name, "scene_1"); +	EXPECT_EQ(metadata[2].get().tag, "tag_scene_1"); +	EXPECT_EQ(metadata[2].get().parent, -1); +	EXPECT_EQ(metadata[2].get().children.size(), 0); +	EXPECT_EQ(transform[2].get().position.x, 2); +	EXPECT_EQ(transform[2].get().position.y, 0); + +	scene_mgr.set_next_scene("scene2"); +	scene_mgr.load_next_scene(); + +	metadata = component_mgr.get_components_by_type<Metadata>(); +	transform = component_mgr.get_components_by_type<Transform>(); + +	EXPECT_EQ(metadata.size(), 4); +	EXPECT_EQ(transform.size(), 4); + +	EXPECT_EQ(metadata[0].get().game_object_id, 0); +	EXPECT_EQ(metadata[0].get().name, "scene_2"); +	EXPECT_EQ(metadata[0].get().tag, "tag_scene_2"); +	EXPECT_EQ(metadata[0].get().parent, -1); +	EXPECT_EQ(metadata[0].get().children.size(), 0); +	EXPECT_EQ(transform[0].get().position.x, 0); +	EXPECT_EQ(transform[0].get().position.y, 0); + +	EXPECT_EQ(metadata[1].get().game_object_id, 1); +	EXPECT_EQ(metadata[1].get().name, "scene_2"); +	EXPECT_EQ(metadata[1].get().tag, "tag_scene_2"); +	EXPECT_EQ(metadata[1].get().parent, -1); +	EXPECT_EQ(metadata[1].get().children.size(), 0); +	EXPECT_EQ(transform[1].get().position.x, 0); +	EXPECT_EQ(transform[1].get().position.y, 1); + +	EXPECT_EQ(metadata[2].get().game_object_id, 2); +	EXPECT_EQ(metadata[2].get().name, "scene_2"); +	EXPECT_EQ(metadata[2].get().tag, "tag_scene_2"); +	EXPECT_EQ(metadata[2].get().parent, -1); +	EXPECT_EQ(metadata[2].get().children.size(), 0); +	EXPECT_EQ(transform[2].get().position.x, 0); +	EXPECT_EQ(transform[2].get().position.y, 2); + +	EXPECT_EQ(metadata[3].get().game_object_id, 3); +	EXPECT_EQ(metadata[3].get().name, "scene_2"); +	EXPECT_EQ(metadata[3].get().tag, "tag_scene_2"); +	EXPECT_EQ(metadata[3].get().parent, -1); +	EXPECT_EQ(metadata[3].get().children.size(), 0); +	EXPECT_EQ(transform[3].get().position.x, 0); +	EXPECT_EQ(transform[3].get().position.y, 3); +} diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 19fef6d..78d5061 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -6,6 +6,8 @@  #include <crepe/ComponentManager.h>  #include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/EventManager.h>  #include <crepe/api/GameObject.h>  #include <crepe/api/Script.h>  #include <crepe/api/Vector2.h> @@ -15,58 +17,113 @@ using namespace std;  using namespace crepe;  using namespace testing; +class MyEvent : public Event {}; +  class ScriptTest : public Test {  public:  	ComponentManager component_manager{};  	ScriptSystem system{component_manager}; +	EventManager & event_manager = EventManager::get_instance();  	class MyScript : public Script {  		// NOTE: default (private) visibility of init and update shouldn't cause  		// issues! -		void init() { this->init_count++; } +		void init() { +			this->init_count++; + +			subscribe<MyEvent>([this](const MyEvent &) { +				this->event_count++; +				return true; +			}); + +			// init should never be called more than once +			EXPECT_LE(this->init_count, 1); +		}  		void update() { this->update_count++; }  	public:  		unsigned init_count = 0;  		unsigned update_count = 0; +		unsigned event_count = 0;  	}; -	BehaviorScript * behaviorscript_ref = nullptr; -	MyScript * script_ref = nullptr; +	OptionalRef<BehaviorScript> behaviorscript; +	OptionalRef<MyScript> script;  	void SetUp() override {  		auto & mgr = this->component_manager;  		GameObject entity = mgr.new_object("name");  		BehaviorScript & component = entity.add_component<BehaviorScript>(); -		this->behaviorscript_ref = &component; -		EXPECT_EQ(this->behaviorscript_ref->script.get(), nullptr); +		this->behaviorscript = component; +		ASSERT_TRUE(this->behaviorscript); +		EXPECT_EQ(component.script.get(), nullptr);  		component.set_script<MyScript>(); -		ASSERT_NE(this->behaviorscript_ref->script.get(), nullptr); +		ASSERT_NE(component.script.get(), nullptr); -		this->script_ref = (MyScript *) this->behaviorscript_ref->script.get(); -		ASSERT_NE(this->script_ref, nullptr); +		this->script = *(MyScript *) component.script.get(); +		ASSERT_TRUE(this->script); + +		// sanity +		MyScript & script = this->script; +		ASSERT_EQ(script.init_count, 0); +		ASSERT_EQ(script.update_count, 0); +		ASSERT_EQ(script.event_count, 0);  	}  };  TEST_F(ScriptTest, Default) { -	EXPECT_EQ(0, this->script_ref->init_count); -	EXPECT_EQ(0, this->script_ref->update_count); +	MyScript & script = this->script; +	EXPECT_EQ(0, script.init_count); +	EXPECT_EQ(0, script.update_count); +	EXPECT_EQ(0, script.event_count);  }  TEST_F(ScriptTest, UpdateOnce) { -	EXPECT_EQ(0, this->script_ref->init_count); -	EXPECT_EQ(0, this->script_ref->update_count); +	MyScript & script = this->script; -	this->system.update(); -	EXPECT_EQ(1, this->script_ref->init_count); -	EXPECT_EQ(1, this->script_ref->update_count); +	system.update(); +	EXPECT_EQ(1, script.init_count); +	EXPECT_EQ(1, script.update_count); +	EXPECT_EQ(0, script.event_count);  } -TEST_F(ScriptTest, ListScripts) { -	size_t script_count = 0; -	for (auto & _ : this->system.get_scripts()) { -		script_count++; -	} -	ASSERT_EQ(1, script_count); +TEST_F(ScriptTest, UpdateInactive) { +	BehaviorScript & behaviorscript = this->behaviorscript; +	MyScript & script = this->script; + +	behaviorscript.active = false; +	system.update(); +	EXPECT_EQ(0, script.init_count); +	EXPECT_EQ(0, script.update_count); +	EXPECT_EQ(0, script.event_count); + +	behaviorscript.active = true; +	system.update(); +	EXPECT_EQ(1, script.init_count); +	EXPECT_EQ(1, script.update_count); +	EXPECT_EQ(0, script.event_count); +} + +TEST_F(ScriptTest, EventInactive) { +	BehaviorScript & behaviorscript = this->behaviorscript; +	MyScript & script = this->script; +	EventManager & evmgr = this->event_manager; + +	system.update(); +	behaviorscript.active = false; +	EXPECT_EQ(1, script.init_count); +	EXPECT_EQ(1, script.update_count); +	EXPECT_EQ(0, script.event_count); + +	evmgr.trigger_event<MyEvent>(); +	EXPECT_EQ(1, script.init_count); +	EXPECT_EQ(1, script.update_count); +	EXPECT_EQ(0, script.event_count); + +	behaviorscript.active = true; +	evmgr.trigger_event<MyEvent>(); +	EXPECT_EQ(1, script.init_count); +	EXPECT_EQ(1, script.update_count); +	EXPECT_EQ(1, script.event_count);  } diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp new file mode 100644 index 0000000..e6bb058 --- /dev/null +++ b/src/test/ValueBrokerTest.cpp @@ -0,0 +1,63 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/util/Proxy.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ValueBrokerTest : public Test { +public: +	int read_count = 0; +	int write_count = 0; +	int value = 0; + +	ValueBroker<int> broker{ +		[this](const int & target) -> void { +			this->write_count++; +			this->value = target; +		}, +		[this]() -> const int & { +			this->read_count++; +			return this->value; +		}, +	}; +	Proxy<int> proxy{broker}; + +	void SetUp() override { +		ASSERT_EQ(read_count, 0); +		ASSERT_EQ(write_count, 0); +	} +}; + +TEST_F(ValueBrokerTest, BrokerWrite) { +	broker.set(0); +	EXPECT_EQ(read_count, 0); +	EXPECT_EQ(write_count, 1); +} + +TEST_F(ValueBrokerTest, BrokerRead) { +	broker.get(); +	EXPECT_EQ(read_count, 1); +	EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyWrite) { +	proxy = 0; +	EXPECT_EQ(read_count, 0); +	EXPECT_EQ(write_count, 1); +} + +void dummy(int) {} +TEST_F(ValueBrokerTest, ProxyRead) { +	dummy(proxy); +	EXPECT_EQ(read_count, 1); +	EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyReadWrite) { +	proxy = proxy; +	ASSERT_EQ(read_count, 1); +	ASSERT_EQ(write_count, 1); +} diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp new file mode 100644 index 0000000..17bca41 --- /dev/null +++ b/src/test/Vector2Test.cpp @@ -0,0 +1,384 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Vector2.h> + +using namespace crepe; + +class Vector2Test : public ::testing::Test { +public: +	Vector2<int> int_vec1; +	Vector2<int> int_vec2; +	Vector2<double> double_vec1; +	Vector2<double> double_vec2; +	Vector2<long> long_vec1; +	Vector2<long> long_vec2; +	Vector2<float> float_vec1; +	Vector2<float> float_vec2; + +	void SetUp() override { +		int_vec1 = {1, 2}; +		int_vec2 = {3, 4}; +		double_vec1 = {1.0, 2.0}; +		double_vec2 = {3.0, 4.0}; +		long_vec1 = {1, 2}; +		long_vec2 = {3, 4}; +		float_vec1 = {1.0f, 2.0f}; +		float_vec2 = {3.0f, 4.0f}; +	} +}; + +TEST_F(Vector2Test, Subtract) { +	Vector2<int> result = int_vec1 - int_vec2; +	EXPECT_EQ(result.x, -2); +	EXPECT_EQ(result.y, -2); + +	Vector2<double> result2 = double_vec1 - double_vec2; +	EXPECT_FLOAT_EQ(result2.x, -2.0); +	EXPECT_FLOAT_EQ(result2.y, -2.0); + +	Vector2<long> result3 = long_vec1 - long_vec2; +	EXPECT_EQ(result3.x, -2); +	EXPECT_EQ(result3.y, -2); + +	Vector2<float> result4 = float_vec1 - float_vec2; +	EXPECT_FLOAT_EQ(result4.x, -2.0f); +	EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalar) { +	Vector2<int> result = int_vec1 - 1; +	EXPECT_EQ(result.x, 0); +	EXPECT_EQ(result.y, 1); + +	Vector2<double> result2 = double_vec1 - 1.0; +	EXPECT_FLOAT_EQ(result2.x, 0.0); +	EXPECT_FLOAT_EQ(result2.y, 1.0); + +	Vector2<long> result3 = long_vec1 - 1; +	EXPECT_EQ(result3.x, 0); +	EXPECT_EQ(result3.y, 1); + +	Vector2<float> result4 = float_vec1 - 1.0f; +	EXPECT_FLOAT_EQ(result4.x, 0.0f); +	EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Add) { +	Vector2<int> result = int_vec1 + int_vec2; +	EXPECT_EQ(result.x, 4); +	EXPECT_EQ(result.y, 6); + +	Vector2<double> result2 = double_vec1 + double_vec2; +	EXPECT_FLOAT_EQ(result2.x, 4.0); +	EXPECT_FLOAT_EQ(result2.y, 6.0); + +	Vector2<long> result3 = long_vec1 + long_vec2; +	EXPECT_EQ(result3.x, 4); +	EXPECT_EQ(result3.y, 6); + +	Vector2<float> result4 = float_vec1 + float_vec2; +	EXPECT_FLOAT_EQ(result4.x, 4.0f); +	EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalar) { +	Vector2<int> result = int_vec1 + 1; +	EXPECT_EQ(result.x, 2); +	EXPECT_EQ(result.y, 3); + +	Vector2<double> result2 = double_vec1 + 1.0; +	EXPECT_FLOAT_EQ(result2.x, 2.0); +	EXPECT_FLOAT_EQ(result2.y, 3.0); + +	Vector2<long> result3 = long_vec1 + 1; +	EXPECT_EQ(result3.x, 2); +	EXPECT_EQ(result3.y, 3); + +	Vector2<float> result4 = float_vec1 + 1.0f; +	EXPECT_FLOAT_EQ(result4.x, 2.0f); +	EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, Multiply) { +	Vector2<int> result = int_vec1 * int_vec2; +	EXPECT_EQ(result.x, 3); +	EXPECT_EQ(result.y, 8); + +	Vector2<double> result2 = double_vec1 * double_vec2; +	EXPECT_FLOAT_EQ(result2.x, 3.0); +	EXPECT_FLOAT_EQ(result2.y, 8.0); + +	Vector2<long> result3 = long_vec1 * long_vec2; +	EXPECT_EQ(result3.x, 3); +	EXPECT_EQ(result3.y, 8); + +	Vector2<float> result4 = float_vec1 * float_vec2; +	EXPECT_FLOAT_EQ(result4.x, 3.0f); +	EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalar) { +	Vector2<int> result = int_vec1 * 2; +	EXPECT_EQ(result.x, 2); +	EXPECT_EQ(result.y, 4); + +	Vector2<double> result2 = double_vec1 * 2.0; +	EXPECT_FLOAT_EQ(result2.x, 2.0); +	EXPECT_FLOAT_EQ(result2.y, 4.0); + +	Vector2<long> result3 = long_vec1 * 2; +	EXPECT_EQ(result3.x, 2); +	EXPECT_EQ(result3.y, 4); + +	Vector2<float> result4 = float_vec1 * 2.0f; +	EXPECT_FLOAT_EQ(result4.x, 2.0f); +	EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, Divide) { +	Vector2<int> result = int_vec1 / int_vec2; +	EXPECT_EQ(result.x, 0); +	EXPECT_EQ(result.y, 0); + +	Vector2<double> result2 = double_vec1 / double_vec2; +	EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); +	EXPECT_FLOAT_EQ(result2.y, 0.5); + +	Vector2<long> result3 = long_vec1 / long_vec2; +	EXPECT_EQ(result3.x, 0); +	EXPECT_EQ(result3.y, 0); + +	Vector2<float> result4 = float_vec1 / float_vec2; +	EXPECT_FLOAT_EQ(result4.x, 0.333333343f); +	EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalar) { +	Vector2<int> result = int_vec1 / 2; +	EXPECT_EQ(result.x, 0); +	EXPECT_EQ(result.y, 1); + +	Vector2<double> result2 = double_vec1 / 2.0; +	EXPECT_FLOAT_EQ(result2.x, 0.5); +	EXPECT_FLOAT_EQ(result2.y, 1.0); + +	Vector2<long> result3 = long_vec1 / 2; +	EXPECT_EQ(result3.x, 0); +	EXPECT_EQ(result3.y, 1); + +	Vector2<float> result4 = float_vec1 / 2.0f; +	EXPECT_FLOAT_EQ(result4.x, 0.5f); +	EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, AddChain) { +	Vector2<int> result = int_vec1; +	result += int_vec2; +	EXPECT_EQ(result.x, 4); +	EXPECT_EQ(result.y, 6); + +	Vector2<double> result2 = double_vec1; +	result2 += double_vec2; +	EXPECT_FLOAT_EQ(result2.x, 4.0); +	EXPECT_FLOAT_EQ(result2.y, 6.0); + +	Vector2<long> result3 = long_vec1; +	result3 += long_vec2; +	EXPECT_EQ(result3.x, 4); +	EXPECT_EQ(result3.y, 6); + +	Vector2<float> result4 = float_vec1; +	result4 += float_vec2; +	EXPECT_FLOAT_EQ(result4.x, 4.0f); +	EXPECT_FLOAT_EQ(result4.y, 6.0f); +} + +TEST_F(Vector2Test, AddScalarChain) { +	Vector2<int> result = int_vec1; +	result += 1; +	EXPECT_EQ(result.x, 2); +	EXPECT_EQ(result.y, 3); + +	Vector2<double> result2 = double_vec1; +	result2 += 1.0; +	EXPECT_FLOAT_EQ(result2.x, 2.0); +	EXPECT_FLOAT_EQ(result2.y, 3.0); + +	Vector2<long> result3 = long_vec1; +	result3 += 1; +	EXPECT_EQ(result3.x, 2); +	EXPECT_EQ(result3.y, 3); + +	Vector2<float> result4 = float_vec1; +	result4 += 1.0f; +	EXPECT_FLOAT_EQ(result4.x, 2.0f); +	EXPECT_FLOAT_EQ(result4.y, 3.0f); +} + +TEST_F(Vector2Test, SubtractChain) { +	Vector2<int> result = int_vec1; +	result -= int_vec2; +	EXPECT_EQ(result.x, -2); +	EXPECT_EQ(result.y, -2); + +	Vector2<double> result2 = double_vec1; +	result2 -= double_vec2; +	EXPECT_FLOAT_EQ(result2.x, -2.0); +	EXPECT_FLOAT_EQ(result2.y, -2.0); + +	Vector2<long> result3 = long_vec1; +	result3 -= long_vec2; +	EXPECT_EQ(result3.x, -2); +	EXPECT_EQ(result3.y, -2); + +	Vector2<float> result4 = float_vec1; +	result4 -= float_vec2; +	EXPECT_FLOAT_EQ(result4.x, -2.0f); +	EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, SubtractScalarChain) { +	Vector2<int> result = int_vec1; +	result -= 1; +	EXPECT_EQ(result.x, 0); +	EXPECT_EQ(result.y, 1); + +	Vector2<double> result2 = double_vec1; +	result2 -= 1.0; +	EXPECT_FLOAT_EQ(result2.x, 0.0); +	EXPECT_FLOAT_EQ(result2.y, 1.0); + +	Vector2<long> result3 = long_vec1; +	result3 -= 1; +	EXPECT_EQ(result3.x, 0); +	EXPECT_EQ(result3.y, 1); + +	Vector2<float> result4 = float_vec1; +	result4 -= 1.0f; +	EXPECT_FLOAT_EQ(result4.x, 0.0f); +	EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, MultiplyChain) { +	Vector2<int> result = int_vec1; +	result *= int_vec2; +	EXPECT_EQ(result.x, 3); +	EXPECT_EQ(result.y, 8); + +	Vector2<double> result2 = double_vec1; +	result2 *= double_vec2; +	EXPECT_FLOAT_EQ(result2.x, 3.0); +	EXPECT_FLOAT_EQ(result2.y, 8.0); + +	Vector2<long> result3 = long_vec1; +	result3 *= long_vec2; +	EXPECT_EQ(result3.x, 3); +	EXPECT_EQ(result3.y, 8); + +	Vector2<float> result4 = float_vec1; +	result4 *= float_vec2; +	EXPECT_FLOAT_EQ(result4.x, 3.0f); +	EXPECT_FLOAT_EQ(result4.y, 8.0f); +} + +TEST_F(Vector2Test, MultiplyScalarChain) { +	Vector2<int> result = int_vec1; +	result *= 2; +	EXPECT_EQ(result.x, 2); +	EXPECT_EQ(result.y, 4); + +	Vector2<double> result2 = double_vec1; +	result2 *= 2.0; +	EXPECT_FLOAT_EQ(result2.x, 2.0); +	EXPECT_FLOAT_EQ(result2.y, 4.0); + +	Vector2<long> result3 = long_vec1; +	result3 *= 2; +	EXPECT_EQ(result3.x, 2); +	EXPECT_EQ(result3.y, 4); + +	Vector2<float> result4 = float_vec1; +	result4 *= 2.0f; +	EXPECT_FLOAT_EQ(result4.x, 2.0f); +	EXPECT_FLOAT_EQ(result4.y, 4.0f); +} + +TEST_F(Vector2Test, DivideChain) { +	Vector2<int> result = int_vec1; +	result /= int_vec2; +	EXPECT_EQ(result.x, 0); +	EXPECT_EQ(result.y, 0); + +	Vector2<double> result2 = double_vec1; +	result2 /= double_vec2; +	EXPECT_FLOAT_EQ(result2.x, 0.33333333333333331); +	EXPECT_FLOAT_EQ(result2.y, 0.5); + +	Vector2<long> result3 = long_vec1; +	result3 /= long_vec2; +	EXPECT_EQ(result3.x, 0); +	EXPECT_EQ(result3.y, 0); + +	Vector2<float> result4 = float_vec1; +	result4 /= float_vec2; +	EXPECT_FLOAT_EQ(result4.x, 0.333333343f); +	EXPECT_FLOAT_EQ(result4.y, 0.5f); +} + +TEST_F(Vector2Test, DivideScalarChain) { +	Vector2<int> result = int_vec1; +	result /= 2; +	EXPECT_EQ(result.x, 0); +	EXPECT_EQ(result.y, 1); + +	Vector2<double> result2 = double_vec1; +	result2 /= 2.0; +	EXPECT_FLOAT_EQ(result2.x, 0.5); +	EXPECT_FLOAT_EQ(result2.y, 1.0); + +	Vector2<long> result3 = long_vec1; +	result3 /= 2; +	EXPECT_EQ(result3.x, 0); +	EXPECT_EQ(result3.y, 1); + +	Vector2<float> result4 = float_vec1; +	result4 /= 2.0f; +	EXPECT_FLOAT_EQ(result4.x, 0.5f); +	EXPECT_FLOAT_EQ(result4.y, 1.0f); +} + +TEST_F(Vector2Test, Negatation) { +	Vector2<int> result = -int_vec1; +	EXPECT_EQ(result.x, -1); +	EXPECT_EQ(result.y, -2); + +	Vector2<double> result2 = -double_vec1; +	EXPECT_FLOAT_EQ(result2.x, -1.0); +	EXPECT_FLOAT_EQ(result2.y, -2.0); + +	Vector2<long> result3 = -long_vec1; +	EXPECT_EQ(result3.x, -1); +	EXPECT_EQ(result3.y, -2); + +	Vector2<float> result4 = -float_vec1; +	EXPECT_FLOAT_EQ(result4.x, -1.0f); +	EXPECT_FLOAT_EQ(result4.y, -2.0f); +} + +TEST_F(Vector2Test, Equals) { +	EXPECT_TRUE(int_vec1 == int_vec1); +	EXPECT_FALSE(int_vec1 == int_vec2); +	EXPECT_TRUE(double_vec1 == double_vec1); +	EXPECT_FALSE(double_vec1 == double_vec2); +	EXPECT_TRUE(long_vec1 == long_vec1); +	EXPECT_FALSE(long_vec1 == long_vec2); +} + +TEST_F(Vector2Test, NotEquals) { +	EXPECT_FALSE(int_vec1 != int_vec1); +	EXPECT_TRUE(int_vec1 != int_vec2); +	EXPECT_FALSE(double_vec1 != double_vec1); +	EXPECT_TRUE(double_vec1 != double_vec2); +	EXPECT_FALSE(long_vec1 != long_vec1); +	EXPECT_TRUE(long_vec1 != long_vec2); +}  |