diff options
| author | JAROWMR <jarorutjes07@gmail.com> | 2024-11-21 20:40:08 +0100 | 
|---|---|---|
| committer | JAROWMR <jarorutjes07@gmail.com> | 2024-11-21 20:40:08 +0100 | 
| commit | d58a059fd6398527274e77a37b65aa105c8a778b (patch) | |
| tree | 26d345400739ec685bbe6b26d8d789bbe33b02aa /src | |
| parent | faa9adb84ad3f675587b52fba79cf44b7bdd2034 (diff) | |
| parent | 115d6f50152dc018073345800ca90b85846ebaa9 (diff) | |
merge with master
Diffstat (limited to 'src')
70 files changed, 1546 insertions, 1014 deletions
| diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 445a8b2..c3f29da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ find_package(SDL2 REQUIRED)  find_package(SDL2_image REQUIRED)  find_package(SoLoud REQUIRED)  find_package(GTest REQUIRED) +find_package(whereami REQUIRED)  find_library(BERKELEY_DB db)  add_library(crepe SHARED) @@ -25,6 +26,7 @@ target_link_libraries(crepe  	PUBLIC SDL2  	PUBLIC SDL2_image  	PUBLIC ${BERKELEY_DB} +	PUBLIC whereami  )  add_subdirectory(crepe) diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp deleted file mode 100644 index 9c41ecb..0000000 --- a/src/crepe/Asset.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include <filesystem> - -#include "Asset.h" - -using namespace crepe; -using namespace std; - -// FIXME: restore this -// src(std::filesystem::canonical(src)) -Asset::Asset(const std::string & src) : src(src) { -	this->file = std::ifstream(this->src, std::ios::in | std::ios::binary); -} - -istream & Asset::get_stream() { return this->file; } - -const string & Asset::get_canonical() const { return this->src; } diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h deleted file mode 100644 index 9051c5e..0000000 --- a/src/crepe/Asset.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include <fstream> -#include <iostream> -#include <string> - -namespace crepe { - -/** - * \brief Asset location helper - * - * This class is used to locate and canonicalize paths to game asset files, and should *always* - * be used when retrieving files from disk. - */ -class Asset { -public: -	/** -	 * \param src  Unique identifier to asset -	 */ -	Asset(const std::string & src); - -public: -	/** -	 * \brief Get an input stream to the contents of this asset -	 * \return Input stream with file contents -	 */ -	std::istream & get_stream(); -	/** -	 * \brief Get the canonical path to this asset -	 * \return Canonical path to this asset -	 */ -	const std::string & get_canonical() const; - -private: -	//! Canonical path to asset -	const std::string src; -	//! File handle (stream) -	std::ifstream file; -}; - -} // namespace crepe diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 3b05742..7e176e7 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,5 +1,4 @@  target_sources(crepe PUBLIC -	Asset.cpp  	Particle.cpp  	ComponentManager.cpp  	Component.cpp @@ -7,7 +6,6 @@ target_sources(crepe PUBLIC  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES -	Asset.h  	ComponentManager.h  	ComponentManager.hpp  	Component.h diff --git a/src/crepe/Component.h b/src/crepe/Component.h index 5279fb3..dc17721 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -27,6 +27,11 @@ protected:  	//! Only the 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.h b/src/crepe/ComponentManager.h index 2107453..0956d1e 100644 --- a/src/crepe/ComponentManager.h +++ b/src/crepe/ComponentManager.h @@ -8,6 +8,7 @@  #include "api/Vector2.h"  #include "Component.h" +#include "types.h"  namespace crepe { @@ -112,7 +113,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 +123,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/api/Asset.cpp b/src/crepe/api/Asset.cpp new file mode 100644 index 0000000..e148367 --- /dev/null +++ b/src/crepe/api/Asset.cpp @@ -0,0 +1,54 @@ +#include <filesystem> +#include <stdexcept> +#include <whereami.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)) {} + +const string & Asset::get_path() const noexcept { return this->src; } + +string Asset::find_asset(const string & src) const { +	auto & cfg = Config::get_instance(); +	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); + +	// find directory matching root_pattern +	filesystem::path root = this->whereami(); +	while (1) { +		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(); +	} + +	// join path to root (base directory) and canonicalize +	return filesystem::canonical(root / path); +} + +string Asset::whereami() const noexcept { +	string path; +	size_t path_length = wai_getExecutablePath(NULL, 0, NULL); +	path.resize(path_length + 1); // wai writes null byte +	wai_getExecutablePath(path.data(), path_length, NULL); +	path.resize(path_length); +	return path; +} + +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 new file mode 100644 index 0000000..bfd0ac7 --- /dev/null +++ b/src/crepe/api/Asset.h @@ -0,0 +1,84 @@ +#pragma once + +#include <string> + +namespace crepe { + +/** + * \brief Asset location helper + * + * This class is used to locate game asset files, and should *always* be used + * instead of reading file paths directly. + */ +class Asset { +public: +	/** +	 * \param src  Unique identifier to asset +	 */ +	Asset(const std::string & src); +	/** +	 * \param src  Unique identifier to asset +	 */ +	Asset(const char * src); + +public: +	/** +	 * \brief Get the path to this asset +	 * \return path to this asset +	 */ +	const std::string & get_path() const noexcept; + +	/** +	 * \brief Comparison operator +	 * \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; + +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 +	 */ +	std::string whereami() const noexcept; +}; + +} // namespace crepe + +namespace std { + +//! Hash helper struct +template <> +struct hash<const crepe::Asset> { +	/** +	 * \brief Hash operator for crepe::Asset +	 * +	 * This function hashes a crepe::Asset instance, allowing it to be used as a key in an \c +	 * std::unordered_map. +	 * +	 * \returns Hash value +	 */ +	size_t operator()(const crepe::Asset & asset) const noexcept; +}; + +} // namespace std diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 07b3a82..830f5bd 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -20,11 +20,12 @@ target_sources(crepe PUBLIC  	BoxCollider.cpp  	CircleCollider.cpp  	EventManager.cpp -	EventHandler.cpp  	IKeyListener.cpp  	IMouseListener.cpp  	LoopManager.cpp  	LoopTimer.cpp +	Asset.cpp +	EventHandler.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -54,9 +55,11 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	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/Config.h b/src/crepe/api/Config.h index 3ab877a..13eabd1 100644 --- a/src/crepe/api/Config.h +++ b/src/crepe/api/Config.h @@ -62,6 +62,20 @@ public:  		 */  		double gravity = 1;  	} physics; + +	//! Asset loading options +	struct { +		/** +		 * \brief Pattern to match for Asset base directory +		 * +		 * All non-absolute paths resolved using \c Asset will be made relative to +		 * the first parent directory relative to the calling executable where +		 * appending this pattern results in a path that exists. If this string is +		 * empty, path resolution is disabled, and Asset will return all paths +		 * as-is. +		 */ +		std::string root_pattern = ".crepe-root"; +	} asset;  };  } // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index bd6a541..8a6b931 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -1,59 +1,115 @@ +// TODO discussing the location of these events  #pragma once -#include "KeyCodes.h" -#include <iostream> +  #include <string> -#include <typeindex> +  #include "system/CollisionSystem.h" -class Event { -public: -}; +#include "KeyCodes.h" +/** + * \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: -	int repeat = 0; +	//! 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 {  public:  	crepe::CollisionSystem::CollisionInfo info;  	CollisionEvent(const crepe::CollisionSystem::CollisionInfo& collisionInfo)          : info(collisionInfo) {}  }; + +/** + * \brief Event triggered when text is submitted, e.g., from a text input. + */  class TextSubmitEvent : public Event {  public: +	//! The submitted text.  	std::string text = "";  }; -class ShutDownEvent : public Event { -public: -}; + +/** + * \brief Event triggered to indicate the application is shutting down. + */ +class ShutDownEvent : public Event {}; diff --git a/src/crepe/api/EventHandler.cpp b/src/crepe/api/EventHandler.cpp index 93a116a..4dc232f 100644 --- a/src/crepe/api/EventHandler.cpp +++ b/src/crepe/api/EventHandler.cpp @@ -1,2 +1,5 @@  #include "EventHandler.h" -bool IEventHandlerWrapper::exec(const Event & e) { return call(e); } + +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 index 0ab90de..ef659fd 100644 --- a/src/crepe/api/EventHandler.h +++ b/src/crepe/api/EventHandler.h @@ -1,9 +1,11 @@  #pragma once -#include "Event.h" +  #include <functional> -#include <iostream> -#include <typeindex> +#include <string> +#include "Event.h" + +namespace crepe {  /**   * \brief A type alias for an event handler function.   *  @@ -11,8 +13,9 @@   * 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.   */ -// TODO: typedef  template <typename EventType>  using EventHandler = std::function<bool(const EventType & e)>; @@ -21,7 +24,7 @@ using EventHandler = std::function<bool(const EventType & e)>;   * \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 and the `get_type()` method to return the handler's type. + * `call()` method to process events   */  class IEventHandlerWrapper {  public: @@ -40,15 +43,6 @@ public:       */  	bool exec(const Event & e); -	/** -     * \brief Get the type of the event handler. -     *  -     * This method returns the type of the event handler as a string. -     *  -     * \return A string representing the handler's type. -     */ -	virtual std::string get_type() const = 0; -  private:  	/**       * \brief The method responsible for handling the event. @@ -81,8 +75,7 @@ public:       *        * \param handler The event handler function.       */ -	explicit EventHandlerWrapper(const EventHandler<EventType> & handler) -		: m_handler(handler), m_handler_type(m_handler.target_type().name()) {} +	explicit EventHandlerWrapper(const EventHandler<EventType> & handler);  private:  	/** @@ -93,20 +86,11 @@ private:       * \param e The event to be handled.       * \return A boolean value indicating whether the event is handled.       */ -	bool call(const Event & e) override { -		return m_handler(static_cast<const EventType &>(e)); -	} - -	/** -     * \brief Returns the type of the handler. -     *  -     * This method returns a string representing the type of the event handler. -     *  -     * \return The handler type as a string. -     */ -	std::string get_type() const override { return m_handler_type; } +	bool call(const Event & e) override;  	//! The event handler function. -	EventHandler<EventType> m_handler; -	//! The type name of the handler function. -	const std::string m_handler_type; +	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..391dcca --- /dev/null +++ b/src/crepe/api/EventHandler.hpp @@ -0,0 +1,18 @@ +#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 index e881d49..20f0dd3 100644 --- a/src/crepe/api/EventManager.cpp +++ b/src/crepe/api/EventManager.cpp @@ -1,6 +1,7 @@  #include "EventManager.h"  using namespace crepe; +using namespace std;  EventManager & EventManager::get_instance() {  	static EventManager instance; @@ -8,74 +9,38 @@ EventManager & EventManager::get_instance() {  }  void EventManager::dispatch_events() { -	for (std::vector<std::tuple<std::unique_ptr<Event>, int, -								std::type_index>>::iterator event_it -		 = this->events_queue.begin(); -		 event_it != this->events_queue.end();) { -		std::unique_ptr<Event> & event = std::get<0>(*event_it); -		int channel = std::get<1>(*event_it); -		std::type_index event_type = std::get<2>(*event_it); +	for (auto & event : this->events_queue) { +		this->handle_event(event.type, event.channel, *event.event.get()); +	} +	this->events_queue.clear(); +} -		bool event_handled = false; +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; -		if (channel) { -			std::unordered_map< -				std::type_index, -				std::unordered_map< -					int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>>:: -				iterator handlers_it -				= subscribers_by_event_id.find(event_type); -			if (handlers_it != subscribers_by_event_id.end()) { -				std::unordered_map< -					int, std::vector<std::unique_ptr<IEventHandlerWrapper>>> & -					handlers_map -					= handlers_it->second; -				std::unordered_map< -					int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>:: -					iterator handlers -					= handlers_map.find(channel); -				if (handlers != handlers_map.end()) { -					std::vector<std::unique_ptr<IEventHandlerWrapper>> & -						callbacks -						= handlers->second; -					for (std::vector<std::unique_ptr<IEventHandlerWrapper>>:: -							 iterator handler_it -						 = callbacks.begin(); -						 handler_it != callbacks.end(); ++handler_it) { -						if ((*handler_it)->exec(*event)) { -							event_it = events_queue.erase(event_it); -							event_handled = true; -							break; -						} -					} -				} -			} -		} else { -			// Handle event for all channels -			std::unordered_map< -				std::type_index, -				std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator -				handlers_it -				= this->subscribers.find(event_type); -			if (handlers_it != this->subscribers.end()) { -				std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers -					= handlers_it->second; -				for (std::vector<std::unique_ptr<IEventHandlerWrapper>>:: -						 iterator handler_it -					 = handlers.begin(); -					 handler_it != handlers.end(); ++handler_it) { -					// remove event from queue since and continue when callback returns true -					if ((*handler_it)->exec(*event)) { -						event_it = this->events_queue.erase(event_it); -						event_handled = true; -						break; -					} -				} -			} -		} +	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(); +} -		if (!event_handled) { -			++event_it; +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 index 38d2e64..348a04d 100644 --- a/src/crepe/api/EventManager.h +++ b/src/crepe/api/EventManager.h @@ -1,8 +1,6 @@  #pragma once -#include <functional>  #include <memory> -#include <type_traits>  #include <typeindex>  #include <unordered_map>  #include <vector> @@ -10,113 +8,154 @@  #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 The EventManager class is responsible for managing the subscription, triggering,  - * and queueing of events. It handles events and dispatches them to appropriate subscribers. + * \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: -	/** -     * \brief Deleted copy constructor to prevent copying of the EventManager instance. -     */ -	EventManager(const EventManager &) = delete; - -	/** -     * \brief Deleted copy assignment operator to prevent assignment of the EventManager instance. -     */ -	const EventManager & operator=(const EventManager &) = delete; +	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 on the first call. -     *  -     * \return Reference to the EventManager instance. -     */ +	 * \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 an event. -     *  -     * This method allows the registration of a callback for a specific event type and channel. -     *  -     * \tparam EventType The type of the event to subscribe to. -     * \param callback The callback function to invoke when the event is triggered. -     * \param channel The channel number to subscribe to (default is 0). -     */ +	 * \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> -	void subscribe(EventHandler<EventType> && callback, int channel = 0); +	subscription_t subscribe(const EventHandler<EventType> & callback, +							 event_channel_t channel = CHANNEL_ALL);  	/** -     * \brief Unsubscribe from an event. -     *  -     * This method removes a previously registered callback from an event. -     *  -     * \tparam EventType The type of the event to unsubscribe from. -     * \param callback The callback function to remove from the subscription list. -     * \param channel The event ID to unsubscribe from. -     */ -	template <typename EventType> -	void unsubscribe(const EventHandler<EventType> &, int channel); +	 * \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. -     *  -     * This method invokes the appropriate callback(s) for the specified event. -     *  -     * \tparam EventType The type of the event to trigger. -     * \param event The event data to pass to the callback. -     * \param channel The channel from which to trigger the event (default is 0). -     */ +	 * \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, int channel); +	void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL);  	/** -     * \brief Queue an event for later processing. -     *  -     * This method adds an event to the event queue, which will be processed in the  -     * dispatch_events function. -     *  -     * \tparam EventType The type of the event to queue. -     * \param event The event to queue. -     * \param channel The channel number for the event (default is 0). -     */ +	 * \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(EventType && event, int channel); +	void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL);  	/** -     * \brief Dispatch all queued events. -     *  -     * This method processes all events in the event queue and triggers the corresponding  -     * callbacks for each event. -     */ +	 * \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. -     *  -     * This constructor is private to enforce the singleton pattern. -     */ +	 * \brief Default constructor for the EventManager. +	 *  +	 * Constructor is private to enforce the singleton pattern. +	 */  	EventManager() = default; -	//! The queue of events to be processed. -	std::vector<std::tuple<std::unique_ptr<Event>, int, std::type_index>> -		events_queue; -	//! Registered event handlers. -	std::unordered_map<std::type_index, -					   std::vector<std::unique_ptr<IEventHandlerWrapper>>> -		subscribers; -	//! Event handlers indexed by event ID. -	std::unordered_map< -		std::type_index, -		std::unordered_map<int, -						   std::vector<std::unique_ptr<IEventHandlerWrapper>>>> -		subscribers_by_event_id; +	/** +	 * \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 index b509097..a5f4556 100644 --- a/src/crepe/api/EventManager.hpp +++ b/src/crepe/api/EventManager.hpp @@ -1,140 +1,36 @@ +#pragma once +  #include "EventManager.h" +  namespace crepe {  template <typename EventType> -void EventManager::subscribe(EventHandler<EventType> && callback, int channel) { +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); - -	if (channel) { -		std::unordered_map<int, -						   std::vector<std::unique_ptr<IEventHandlerWrapper>>> & -			handlers_map -			= this->subscribers_by_event_id[event_type]; -		std::unordered_map< -			int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator -			handlers -			= handlers_map.find(channel); -		if (handlers != handlers_map.end()) { -			handlers->second.emplace_back(std::move(handler)); -		} else { -			handlers_map[channel].emplace_back(std::move(handler)); -		} -	} else { -		std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers -			= this->subscribers[event_type]; -		handlers.emplace_back(std::move(handler)); -	} -} - -template <typename EventType> -void EventManager::queue_event(EventType && event, int channel) { -	std::type_index event_type = std::type_index(typeid(EventType)); - -	std::unique_ptr<EventType> event_ptr -		= std::make_unique<EventType>(std::forward<EventType>(event)); - -	std::tuple<std::unique_ptr<Event>, int, std::type_index> tuple( -		std::move(event_ptr), channel, event_type); -	this->events_queue.push_back(std::move(tuple)); +	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::trigger_event(const EventType & event, int channel) { -	std::type_index event_type = std::type_index(typeid(EventType)); - -	if (channel > 0) { -		std::unordered_map<int, -						   std::vector<std::unique_ptr<IEventHandlerWrapper>>> & -			handlers_map -			= this->subscribers_by_event_id[event_type]; -		std::unordered_map< -			int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator -			handlers_it -			= handlers_map.find(channel); - -		if (handlers_it != handlers_map.end()) { -			std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers -				= handlers_it->second; -			for (std::vector<std::unique_ptr<IEventHandlerWrapper>>::iterator it -				 = handlers.begin(); -				 it != handlers.end();++it) { -				// stops when callback returns true -				if((*it)->exec(event)){ -					break; -				} -			} -		} -	} else { -		std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers -			= this->subscribers[event_type]; -		for (std::vector<std::unique_ptr<IEventHandlerWrapper>>::iterator it -			 = handlers.begin(); -			 it != handlers.end();++it) { -			// stops when callback returns true -			if((*it)->exec(event)){ -					break; -				} -		} -	} +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::unsubscribe(const EventHandler<EventType> & callback, -							   int channel) { -	std::type_index event_type(typeid(EventType)); -	std::string handler_name = callback.target_type().name(); - -	if (channel) { -		std::unordered_map< -			std::type_index, -			std::unordered_map< -				int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>>:: -			iterator subscriber_list -			= this->subscribers_by_event_id.find(event_type); -		if (subscriber_list != this->subscribers_by_event_id.end()) { -			std::unordered_map< -				int, std::vector<std::unique_ptr<IEventHandlerWrapper>>> & -				handlers_map -				= subscriber_list->second; -			std::unordered_map< -				int, -				std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator -				handlers -				= handlers_map.find(channel); -			if (handlers != handlers_map.end()) { -				std::vector<std::unique_ptr<IEventHandlerWrapper>> & callbacks -					= handlers->second; -				for (std::vector< -						 std::unique_ptr<IEventHandlerWrapper>>::iterator it -					 = callbacks.begin(); -					 it != callbacks.end(); ++it) { -					if ((*it)->get_type() == handler_name) { -						it = callbacks.erase(it); -						return; -					} -				} -			} -		} -	} else { -		std::unordered_map<std::type_index, -						   std::vector<std::unique_ptr<IEventHandlerWrapper>>>:: -			iterator handlers_it -			= this->subscribers.find(event_type); -		if (handlers_it != this->subscribers.end()) { -			std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers -				= handlers_it->second; -			for (std::vector<std::unique_ptr<IEventHandlerWrapper>>::iterator it -				 = handlers.begin(); -				 it != handlers.end(); ++it) { -				if ((*it)->get_type() == handler_name) { -					it = handlers.erase(it); -					return; -				} -			} -		} -	} +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..4874426 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -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/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp index 4fd9855..8642655 100644 --- a/src/crepe/api/IKeyListener.cpp +++ b/src/crepe/api/IKeyListener.cpp @@ -2,50 +2,18 @@  using namespace crepe; -IKeyListener::IKeyListener() { -	this->channel = channel; -	this->subscribe_events(); -} -IKeyListener::IKeyListener(int channel) { -	this->channel = channel; -	this->subscribe_events(); -} -IKeyListener::~IKeyListener() { this->unsubscribe_events(); } - -void IKeyListener::subscribe_events() { -	key_pressed_handler = [this](const KeyPressEvent & event) { -		return this->on_key_pressed(event); -	}; -	key_released_handler = [this](const KeyReleaseEvent & event) { -		return this->on_key_released(event); -	}; -	EventManager::get_instance().subscribe<KeyPressEvent>( -		std::move(this->key_pressed_handler), this->channel); -	EventManager::get_instance().subscribe<KeyReleaseEvent>( -		std::move(this->key_released_handler), this->channel); +// 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);  } -void IKeyListener::unsubscribe_events() { -	EventManager::get_instance().unsubscribe<KeyPressEvent>( -		this->key_pressed_handler, channel); -	EventManager::get_instance().unsubscribe<KeyReleaseEvent>( -		this->key_released_handler, channel); -	std::cout << std::endl; -} -void IKeyListener::activate_keys() { -	if (this->active) { -		return; -	} -	this->subscribe_events(); -} -void IKeyListener::deactivate_keys() { -	if (!this->active) { -		return; -	} -	this->unsubscribe_events(); -} -void IKeyListener::set_channel(int channel) { -	this->unsubscribe_events(); -	this->channel = channel; -	this->subscribe_events(); +// 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 index 839acbf..328a4c2 100644 --- a/src/crepe/api/IKeyListener.h +++ b/src/crepe/api/IKeyListener.h @@ -1,8 +1,11 @@  #pragma once +  #include "Event.h"  #include "EventHandler.h"  #include "EventManager.h" +namespace crepe { +  /**   * \class IKeyListener   * \brief Interface for keyboard event handling in the application. @@ -13,17 +16,12 @@ public:       * \brief Constructs an IKeyListener with a specified channel.       * \param channel The channel ID for event handling.       */ -	IKeyListener(int channel); - -	/** -     * \brief Default constructor for IKeyListener. -     */ -	IKeyListener(); - -	/** -     * \brief Destructor. -     */ +	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. @@ -39,40 +37,13 @@ public:       */  	virtual bool on_key_released(const KeyReleaseEvent & event) = 0; -	/** -     * \brief Activates key listening. -     */ -	void activate_keys(); - -	/** -     * \brief Deactivates key listening. -     */ -	void deactivate_keys(); - -	/** -     * \brief Sets the channel ID for event handling. -     * \param channel The channel ID to set. -     */ -	void set_channel(int channel); - -protected: -	/** -     * \brief Subscribes to key events. -     */ -	void subscribe_events(); - -	/** -     * \brief Unsubscribes from key events. -     */ -	void unsubscribe_events(); -  private: -	//! Indicates whether key listening is active. -	bool active = true; -	//! Channel ID for event handling. -	int channel = 0; -	//! Key press event handler. -	EventHandler<KeyPressEvent> key_pressed_handler; -	//!< Key release event handler. -	EventHandler<KeyReleaseEvent> key_released_handler; +	//! 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 index 489e55b..989aeb3 100644 --- a/src/crepe/api/IMouseListener.cpp +++ b/src/crepe/api/IMouseListener.cpp @@ -2,63 +2,28 @@  using namespace crepe; -IMouseListener::IMouseListener(int channel) { this->channel = channel; } - -IMouseListener::IMouseListener() { this->subscribe_events(); } - -IMouseListener::~IMouseListener() { this->unsubscribe_events(); } - -void IMouseListener::subscribe_events() { -	// Define handler lambdas and subscribe them -	mouse_click_handler = [this](const MouseClickEvent & event) { -		return this->on_mouse_clicked(event); -	}; -	mouse_press_handler = [this](const MousePressEvent & event) { -		return this->on_mouse_pressed(event); -	}; -	mouse_release_handler = [this](const MouseReleaseEvent & event) { -		return this->on_mouse_released(event); -	}; -	mouse_move_handler = [this](const MouseMoveEvent & event) { -		return this->on_mouse_moved(event); -	}; -	EventManager::get_instance().subscribe<MouseClickEvent>( -		std::move(this->mouse_click_handler), this->channel); -	EventManager::get_instance().subscribe<MousePressEvent>( -		std::move(this->mouse_press_handler), this->channel); -	EventManager::get_instance().subscribe<MouseReleaseEvent>( -		std::move(this->mouse_release_handler), this->channel); -	EventManager::get_instance().subscribe<MouseMoveEvent>( -		std::move(this->mouse_move_handler), this->channel); -} -// TODO: reference voor singleton -void IMouseListener::unsubscribe_events() { -	EventManager::get_instance().unsubscribe<MouseClickEvent>( -		this->mouse_click_handler, this->channel); -	EventManager::get_instance().unsubscribe<MousePressEvent>( -		this->mouse_press_handler, this->channel); -	EventManager::get_instance().unsubscribe<MouseReleaseEvent>( -		this->mouse_release_handler, this->channel); -	EventManager::get_instance().unsubscribe<MouseMoveEvent>( -		this->mouse_move_handler, this->channel); -} - -void IMouseListener::activate_mouse() { -	if (this->active) { -		return; -	} -	this->subscribe_events(); -} - -void IMouseListener::deactivate_mouse() { -	if (!this->active) { -		return; -	} -	this->unsubscribe_events(); +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);  } -void IMouseListener::set_channel(int channel) { -	this->unsubscribe_events(); -	this->channel = channel; -	this->subscribe_events(); +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 index 7e92956..15e1619 100644 --- a/src/crepe/api/IMouseListener.h +++ b/src/crepe/api/IMouseListener.h @@ -4,6 +4,8 @@  #include "EventHandler.h"  #include "EventManager.h" +namespace crepe { +  /**   * \class IMouseListener   * \brief Interface for mouse event handling in the application. @@ -11,34 +13,14 @@  class IMouseListener {  public:  	/** -     * \brief Default constructor. -     */ -	IMouseListener(); - -	/**       * \brief Constructs an IMouseListener with a specified channel.       * \param channel The channel ID for event handling.       */ -	IMouseListener(int channel); - -	/** -     * \brief Destructor. -     */ +	IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL);  	virtual ~IMouseListener(); - -	/** -     * \brief Copy constructor (deleted). -     */ -	IMouseListener(const IMouseListener &) = delete; - -	/** -     * \brief Copy assignment operator (deleted). -     */  	IMouseListener & operator=(const IMouseListener &) = delete; - -	/** -     * \brief Move constructor (deleted). -     */ +	IMouseListener(const IMouseListener &) = delete; +	IMouseListener & operator=(const IMouseListener &&) = delete;  	IMouseListener(IMouseListener &&) = delete;  	/** @@ -74,44 +56,17 @@ public:       */  	virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; -	/** -     * \brief Activates mouse listening. -     */ -	void activate_mouse(); - -	/** -     * \brief Deactivates mouse listening. -     */ -	void deactivate_mouse(); - -	/** -     * \brief Sets the channel ID for event handling. -     * \param channel The channel ID to set. -     */ -	void set_channel(int channel); - -protected: -	/** -     * \brief Subscribes to mouse events on the specified channel. -     */ -	void subscribe_events(); - -	/** -     * \brief Unsubscribes from mouse events on the specified channel. -     */ -	void unsubscribe_events(); -  private: -	//! Indicates whether mouse listening is active. -	bool active = true; -	//! Channel ID for event handling. -	int channel = 0; -	//! Mouse click event handler. -	EventHandler<MouseClickEvent> mouse_click_handler; -	//! Mouse press event handler. -	EventHandler<MousePressEvent> mouse_press_handler; -	//! Mouse release event handler. -	EventHandler<MouseReleaseEvent> mouse_release_handler; -	//! Mouse move event handler. -	EventHandler<MouseMoveEvent> mouse_move_handler; +	//! 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 index e5a91fc..9e173e0 100644 --- a/src/crepe/api/KeyCodes.h +++ b/src/crepe/api/KeyCodes.h @@ -1,94 +1,93 @@  #pragma once +//! Enumeration for mouse button inputs, including standard and extended buttons.  enum class MouseButton { -	NONE = 0, -	LEFT_MOUSE = 1, -	RIGHT_MOUSE = 2, -	MIDDLE_MOUSE = 3, -	X1_MOUSE = 4, -	X2_MOUSE = 5, -	SCROLL_UP = 6, -	SCROLL_DOWN = 7, +	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.  }; -enum class Keycode : int { -	NONE = 0, -	SPACE = 32, -	APOSTROPHE = 39, /* ' */ -	COMMA = 44, /* , */ -	MINUS = 45, /* - */ -	PERIOD = 46, /* . */ -	SLASH = 47, /* / */ - -	D0 = 48, /* 0 */ -	D1 = 49, /* 1 */ -	D2 = 50, /* 2 */ -	D3 = 51, /* 3 */ -	D4 = 52, /* 4 */ -	D5 = 53, /* 5 */ -	D6 = 54, /* 6 */ -	D7 = 55, /* 7 */ -	D8 = 56, /* 8 */ -	D9 = 57, /* 9 */ - -	SEMICOLON = 59, /* ; */ -	EQUAL = 61, /* = */ - -	A = 65, -	B = 66, -	C = 67, -	D = 68, -	E = 69, -	F = 70, -	G = 71, -	H = 72, -	I = 73, -	J = 74, -	K = 75, -	L = 76, -	M = 77, -	N = 78, -	O = 79, -	P = 80, -	Q = 81, -	R = 82, -	S = 83, -	T = 84, -	U = 85, -	V = 86, -	W = 87, -	X = 88, -	Y = 89, -	Z = 90, - -	LEFT_BRACKET = 91, /* [ */ -	BACKSLASH = 92, /* \ */ -	RIGHT_BRACKET = 93, /* ] */ -	GRAVE_ACCENT = 96, /* ` */ - -	WORLD1 = 161, /* non-US #1 */ -	WORLD2 = 162, /* non-US #2 */ - -	/* Function keys */ -	ESCAPE = 256, -	ENTER = 257, -	TAB = 258, -	BACKSPACE = 259, -	INSERT = 260, -	DELETE = 261, -	RIGHT = 262, -	LEFT = 263, -	DOWN = 264, -	UP = 265, -	PAGE_UP = 266, -	PAGE_DOWN = 267, -	HOME = 268, -	END = 269, -	CAPS_LOCK = 280, -	SCROLL_LOCK = 281, -	NUM_LOCK = 282, -	PRINT_SCREEN = 283, -	PAUSE = 284, +//! 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, @@ -114,8 +113,11 @@ enum class Keycode : int {  	F23 = 312,  	F24 = 313,  	F25 = 314, - -	/* Keypad */ +	/// \} +	/** +	 * \name Keypad digits and operators. +	 * \{ +	 */  	KP0 = 320,  	KP1 = 321,  	KP2 = 322, @@ -133,7 +135,11 @@ enum class Keycode : int {  	KP_ADD = 334,  	KP_ENTER = 335,  	KP_EQUAL = 336, - +	/// \} +	/** +	 * \name Modifier keys. +	 * \{ +	 */  	LEFT_SHIFT = 340,  	LEFT_CONTROL = 341,  	LEFT_ALT = 342, @@ -142,5 +148,6 @@ enum class Keycode : int {  	RIGHT_CONTROL = 345,  	RIGHT_ALT = 346,  	RIGHT_SUPER = 347, -	MENU = 348 +	/// \} +	MENU = 348, //!< Menu key.  }; diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h index 37f13ac..13e6dac 100644 --- a/src/crepe/api/LoopManager.h +++ b/src/crepe/api/LoopManager.h @@ -8,11 +8,24 @@  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. @@ -54,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.  	 * @@ -72,9 +87,9 @@ private:  private:  	//! Component manager instance  	ComponentManager component_manager{}; -public:  	//! Scene manager instance  	SceneManager scene_manager{component_manager}; +  private:  	/**  	 * \brief Collection of System instances 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/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.h b/src/crepe/api/Script.h index 939d142..0702e36 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -62,15 +62,15 @@ protected:  	 * \returns List of component references  	 */  	template <typename T> -	std::vector<std::reference_wrapper<T>> get_components() const; -	 +	RefVector<T> get_components() const; +  	/**  	 * \brief Gets game object id this script is attached to  	 *  	 * \returns game object id  	 */  	game_object_id_t get_game_object_id() const {return this->game_object_id;}; -	 +  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. diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a064a90..a85d814 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,9 +19,8 @@ T & Script::get_component() const {  }  template <typename T> -std::vector<std::reference_wrapper<T>> Script::get_components() const { +RefVector<T> Script::get_components() const {  	auto & mgr = *this->component_manager_ref; -  	return mgr.get_components_by_id<T>(this->game_object_id);  } diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index de0d0ea..9be9421 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -26,7 +26,7 @@ Texture::~Texture() {  void Texture::load(unique_ptr<Asset> res) {  	SDLContext & ctx = SDLContext::get_instance(); -	this->texture = std::move(ctx.texture_from_path(res->get_canonical())); +	this->texture = std::move(ctx.texture_from_path(res->get_path()));  }  int Texture::get_width() const { @@ -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/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 803516e..00523a6 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,63 @@ 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 -(sprite.sprite_rect.w/2)) * cam.zoom; -	double adjusted_y = (transform.position.y - cam.y -(sprite.sprite_rect.h/2)) * 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 Vector2 & pos, +								  const double & scale, const Camera & cam) const { -	SDL_Rect dstrect = { +	double adjusted_x = (pos.x - cam.x) * cam.zoom; +	double adjusted_y = (pos.y - cam.y) * cam.zoom; +	double adjusted_w = sprite.sprite_rect.w * scale * cam.zoom; +	double adjusted_h = sprite.sprite_rect.h * scale * cam.zoom; + +	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 Vector2 & 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 +164,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 +174,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 652a83e..841ffc9 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> @@ -10,10 +12,7 @@  #include "../api/Sprite.h"  #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 "api/Vector2.h"  namespace crepe { @@ -121,6 +120,9 @@ private:  	 */  	void draw(const Sprite & sprite, const Transform & transform, const Camera & camera); +	void draw_particle(const Sprite & sprite, const Vector2 & pos, const double & angle, +					   const double & scale, const Camera & camera); +  	//! Clears the screen, preparing for a new frame.  	void clear_screen(); @@ -128,10 +130,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 Vector2 & 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 7aa89a9..4d3abf5 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -16,7 +16,7 @@ Sound::Sound(const char * src) {  	this->load(make_unique<Asset>(src));  } -void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_canonical().c_str()); } +void Sound::load(unique_ptr<Asset> res) { this->sample.load(res->get_path().c_str()); }  void Sound::play() {  	SoundContext & ctx = SoundContext::get_instance(); diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 32b6478..4c68f32 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -4,7 +4,7 @@  #include <soloud/soloud.h>  #include <soloud/soloud_wav.h> -#include "../Asset.h" +#include "../api/Asset.h"  namespace crepe { 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/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 7316309..fcf7522 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -14,8 +14,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 diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 4a7dbfb..bcde431 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) { diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 96c5f27..8f9467e 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -1,12 +1,16 @@  #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 "RenderSystem.h" @@ -20,12 +24,15 @@ 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");  	if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene");  	for (Camera & cam : cameras) { -		this->context.camera(cam); +		if (!cam.active) continue; +		this->context.set_camera(cam);  		this->curr_cam_ref = &cam;  	}  } @@ -37,29 +44,61 @@ bool sorting_comparison(const Sprite & a, const Sprite & b) {  	return false;  } -std::vector<std::reference_wrapper<Sprite>> -RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) { - -	std::vector<std::reference_wrapper<Sprite>> sorted_objs(objs); +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::render_sprites() { +void RenderSystem::update() { +	this->clear_screen(); +	this->update_camera(); +	this->render(); +	this->present_screen(); +} + +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { +  	ComponentManager & mgr = this->component_manager; -	vector<reference_wrapper<Sprite>> sprites = mgr.get_components_by_type<Sprite>(); -	vector<reference_wrapper<Sprite>> sorted_sprites = this->sort(sprites); -	for (const Sprite & sprite : sorted_sprites) { -		auto transforms = mgr.get_components_by_id<Transform>(sprite.game_object_id); -		this->context.draw(sprite, transforms[0], *this->curr_cam_ref); +	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::update() { -	this->clear_screen(); -	this->update_camera(); -	this->render_sprites(); -	this->present_screen(); +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 57b9c73..30b41cf 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -6,6 +6,7 @@  #include "facade/SDLContext.h"  #include "System.h" +#include <cmath>  namespace crepe { @@ -38,8 +39,25 @@ private:  	//! Updates the active camera used for rendering.  	void update_camera(); -	//! Renders all active sprites to the screen. -	void render_sprites(); +	//! 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 @@ -47,12 +65,10 @@ private:  	 * \param objs the vector that will do a sorting algorithm on   	 * \return returns a sorted reference vector  	 */ -	std::vector<std::reference_wrapper<Sprite>> -	sort(std::vector<std::reference_wrapper<Sprite>> & objs); +	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. diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index c4d724c..c33309c 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,6 +1,4 @@ -#include <forward_list>  #include <functional> -#include <vector>  #include "../ComponentManager.h"  #include "../api/BehaviorScript.h" @@ -14,7 +12,7 @@ using namespace crepe;  void ScriptSystem::update() {  	dbg_trace(); -	forward_list<reference_wrapper<Script>> scripts = this->get_scripts(); +	RefVector<Script> scripts = this->get_scripts();  	for (auto & script_ref : scripts) {  		Script & script = script_ref.get(); @@ -26,18 +24,17 @@ void ScriptSystem::update() {  	}  } -forward_list<reference_wrapper<Script>> ScriptSystem::get_scripts() const { -	forward_list<reference_wrapper<Script>> scripts = {}; +RefVector<Script> ScriptSystem::get_scripts() const { +	RefVector<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();  		if (!behavior_script.active) continue;  		Script * script = behavior_script.script.get();  		if (script == nullptr) continue; -		scripts.push_front(*script); +		scripts.push_back(*script);  	}  	return scripts; diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index deb89cb..32e1fcd 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -1,9 +1,9 @@  #pragma once -#include <forward_list> -  #include "System.h" +#include "../types.h" +  namespace crepe {  class Script; @@ -33,7 +33,7 @@ private:  	 *  	 * \returns List of active \c Script instances  	 */ -	std::forward_list<std::reference_wrapper<Script>> get_scripts() const; +	RefVector<Script> get_scripts() const;  };  } // namespace crepe diff --git a/src/crepe/types.h b/src/crepe/types.h index 0d459e8..914c76c 100644 --- a/src/crepe/types.h +++ b/src/crepe/types.h @@ -1,9 +1,16 @@  #pragma once  #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>>; + +} // namespace crepe diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 4be738a..94ed906 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -9,5 +9,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Log.hpp  	Proxy.h  	Proxy.hpp +	OptionalRef.h +	OptionalRef.hpp  ) diff --git a/src/crepe/util/OptionalRef.h b/src/crepe/util/OptionalRef.h new file mode 100644 index 0000000..3201667 --- /dev/null +++ b/src/crepe/util/OptionalRef.h @@ -0,0 +1,58 @@ +#pragma once + +namespace crepe { + +/** + * \brief Optional reference utility + * + * This class doesn't need to know the full definition of \c T to be used. + * + * \tparam T Value type + */ +template <typename T> +class OptionalRef { +public: +	//! Initialize empty (nonexistant) reference +	OptionalRef() = default; +	//! Initialize reference with value +	OptionalRef(T & ref); +	/** +	 * \brief Assign new reference +	 * +	 * \param ref Reference to assign +	 * +	 * \return Reference to this (required for operator) +	 */ +	OptionalRef<T> & operator=(T & ref); +	/** +	 * \brief Retrieve this reference +	 * +	 * \returns Internal reference if it is set +	 * +	 * \throws std::runtime_error if this function is called while the reference it not set +	 */ +	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; + +private: +	/** +	 * \brief Reference to the value of type \c T +	 * +	 * \note This raw pointer is *not* managed, and only used as a reference! +	 */ +	T * ref = nullptr; +}; + +} // namespace crepe + +#include "OptionalRef.hpp" diff --git a/src/crepe/util/OptionalRef.hpp b/src/crepe/util/OptionalRef.hpp new file mode 100644 index 0000000..4608c9e --- /dev/null +++ b/src/crepe/util/OptionalRef.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include <stdexcept> + +#include "OptionalRef.h" + +namespace crepe { + +template <typename T> +OptionalRef<T>::OptionalRef(T & ref) { +	this->ref = &ref; +} + +template <typename T> +OptionalRef<T>::operator T &() const { +	if (this->ref == nullptr) +		throw std::runtime_error("OptionalRef: attempt to dereference nullptr"); +	return *this->ref; +} + +template <typename T> +OptionalRef<T> & OptionalRef<T>::operator=(T & ref) { +	this->ref = &ref; +	return *this; +} + +template <typename T> +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..5f34446 --- /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", Vector2{0, 0}, 0, 1); +		GameObject object2 = mgr.new_object("object2", "tag_my_scene", Vector2{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/example/CMakeLists.txt b/src/example/CMakeLists.txt index 045f4d4..560e2bc 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,15 +16,8 @@ function(add_example target_name)  	add_dependencies(examples ${target_name})  endfunction() -add_example(audio_internal) -add_example(script) -add_example(log) -add_example(rendering)  add_example(asset_manager)  add_example(savemgr) -add_example(proxy) -add_example(db) -add_example(game) -add_example(events) +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 661161a..0000000 --- a/src/example/audio_internal.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/** \file - *  - * Standalone example for usage of the internal \c Sound class. - */ - -#include <crepe/api/Config.h> -#include <crepe/facade/Sound.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() { -	// 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/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/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/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_particle.cpp b/src/example/rendering_particle.cpp new file mode 100644 index 0000000..4571afb --- /dev/null +++ b/src/example/rendering_particle.cpp @@ -0,0 +1,71 @@ +#include "api/Camera.h" +#include "system/ParticleSystem.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("", "", Vector2{100, 100}, 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 = Vector2{0, 0}, +		.boundary{ +			.width = 1000, +			.height = 1000, +			.offset = Vector2{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/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/test/AssetTest.cpp b/src/test/AssetTest.cpp new file mode 100644 index 0000000..8aa7629 --- /dev/null +++ b/src/test/AssetTest.cpp @@ -0,0 +1,26 @@ +#include <gtest/gtest.h> + +#include <crepe/api/Asset.h> +#include <crepe/api/Config.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class AssetTest : public Test { +public: +	Config & cfg = Config::get_instance(); +	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, Nonexistant) { ASSERT_ANY_THROW(Asset{"asset/nonexistant"}); } + +TEST_F(AssetTest, Rootless) { +	cfg.asset.root_pattern.clear(); + +	string arbitrary = "\\/this is / /../passed through as-is"; +	Asset asset{arbitrary}; +	ASSERT_EQ(arbitrary, asset.get_path()); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 14c5123..8be8252 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -4,7 +4,13 @@ target_sources(test_main PUBLIC  	PhysicsTest.cpp  	ScriptTest.cpp  	ParticleTest.cpp +	AssetTest.cpp +	OptionalRefTest.cpp +	RenderSystemTest.cpp +	EventTest.cpp  	ECSTest.cpp  	SceneManagerTest.cpp +	ValueBrokerTest.cpp +	DBTest.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/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 new file mode 100644 index 0000000..83f7b23 --- /dev/null +++ b/src/test/OptionalRefTest.cpp @@ -0,0 +1,42 @@ +#include <gtest/gtest.h> + +#include <crepe/util/OptionalRef.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +TEST(OptionalRefTest, Normal) { +	string value = "foo"; +	OptionalRef<string> ref = value; + +	EXPECT_TRUE(ref); +	ASSERT_NO_THROW({ +		string & value_ref = ref; +		EXPECT_EQ(value_ref, value); +	}); + +	ref.clear(); +	EXPECT_FALSE(ref); +	ASSERT_THROW({ string & value_ref = ref; }, runtime_error); +} + +TEST(OptionalRefTest, Empty) { +	string value = "foo"; +	OptionalRef<string> ref; + +	EXPECT_FALSE(ref); +	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 4e655a9..eee022f 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -28,8 +28,8 @@ public:  			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, +			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{ diff --git a/src/test/SceneManagerTest.cpp b/src/test/SceneManagerTest.cpp index 69e1171..1efcfb2 100644 --- a/src/test/SceneManagerTest.cpp +++ b/src/test/SceneManagerTest.cpp @@ -20,6 +20,8 @@ public:  		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);  	} + +	string get_name() const { return "scene1"; }  };  class ConcreteScene2 : public Scene { @@ -33,6 +35,8 @@ public:  		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);  	} + +	string get_name() const { return "scene2"; }  };  class SceneManagerTest : public ::testing::Test { @@ -42,8 +46,8 @@ public:  };  TEST_F(SceneManagerTest, loadScene) { -	scene_mgr.add_scene<ConcreteScene1>("scene1"); -	scene_mgr.add_scene<ConcreteScene2>("scene2"); +	scene_mgr.add_scene<ConcreteScene1>(); +	scene_mgr.add_scene<ConcreteScene2>();  	scene_mgr.load_next_scene(); 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); +} |