diff options
| -rw-r--r-- | Doxyfile | 6 | ||||
| -rw-r--r-- | src/crepe/api/BehaviorScript.h | 7 | ||||
| -rw-r--r-- | src/crepe/api/BehaviorScript.hpp | 12 | ||||
| -rw-r--r-- | src/crepe/api/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/crepe/api/Event.h | 12 | ||||
| -rw-r--r-- | src/crepe/api/EventManager.h | 4 | ||||
| -rw-r--r-- | src/crepe/api/Script.cpp | 16 | ||||
| -rw-r--r-- | src/crepe/api/Script.h | 125 | ||||
| -rw-r--r-- | src/crepe/api/Script.hpp | 34 | ||||
| -rw-r--r-- | src/crepe/util/Log.h | 10 | ||||
| -rw-r--r-- | src/doc/layout.xml | 2 | ||||
| -rw-r--r-- | src/test/ScriptTest.cpp | 55 | 
12 files changed, 247 insertions, 37 deletions
@@ -19,15 +19,17 @@ TAB_SIZE = 2  HTML_INDEX_NUM_ENTRIES = 2  HTML_EXTRA_STYLESHEET = src/doc/style.css +SHOW_HEADERFILE = NO  USE_MDFILE_AS_MAINPAGE = ./readme.md  REPEAT_BRIEF = NO -INTERNAL_DOCS = YES -EXTRACT_PRIVATE = YES  EXTRACT_STATIC = YES  HIDE_UNDOC_NAMESPACES = YES  HIDE_UNDOC_CLASSES = YES  QUIET = YES +# set these to NO for user-only docs +INTERNAL_DOCS = YES +EXTRACT_PRIVATE = YES diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h index 9d85d4c..d556fe5 100644 --- a/src/crepe/api/BehaviorScript.h +++ b/src/crepe/api/BehaviorScript.h @@ -39,11 +39,14 @@ public:  	 * \brief Set the concrete script of this component  	 *  	 * \tparam T Concrete script type (derived from \c crepe::Script) +	 * \tparam Args Arguments for concrete script constructor +	 * +	 * \param args Arguments for concrete script constructor (forwarded using perfect forwarding)  	 *  	 * \returns Reference to BehaviorScript component (`*this`)  	 */ -	template <class T> -	BehaviorScript & set_script(); +	template <class T, typename... Args> +	BehaviorScript & set_script(Args &&... args);  protected:  	//! Script instance diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index d80321d..5b5a418 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -9,13 +9,17 @@  namespace crepe { -template <class T> -BehaviorScript & BehaviorScript::set_script() { +template <class T, typename... Args> +BehaviorScript & BehaviorScript::set_script(Args &&... args) {  	dbg_trace();  	static_assert(std::is_base_of<Script, T>::value); -	Script * s = new T(); -	s->game_object_id = this->game_object_id; +	Script * s = new T(std::forward<Args>(args)...); + +	s->game_object_id_ref = &this->game_object_id; +	s->active_ref = &this->active;  	s->component_manager_ref = &this->component_manager; +	s->event_manager_ref = &EventManager::get_instance(); +  	this->script = std::unique_ptr<Script>(s);  	return *this;  } diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index d6b6801..602ab52 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -24,6 +24,7 @@ target_sources(crepe PUBLIC  	LoopTimer.cpp  	Asset.cpp  	EventHandler.cpp +	Script.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index 06cf7f3..b267e3e 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -1,10 +1,12 @@ -// TODO discussing the location of these events  #pragma once +// TODO discussing the location of these events  #include <string>  #include "KeyCodes.h" +namespace crepe { +  /**   * \brief Base class for all event types in the system.   */ @@ -91,11 +93,7 @@ public:  /**   * \brief Event triggered during a collision between objects.   */ -class CollisionEvent : public Event { -public: -	//! Data describing the collision (currently not implemented). -	// Collision collisionData; -}; +class CollisionEvent : public Event {};  /**   * \brief Event triggered when text is submitted, e.g., from a text input. @@ -110,3 +108,5 @@ public:   * \brief Event triggered to indicate the application is shutting down.   */  class ShutDownEvent : public Event {}; + +} // namespace crepe diff --git a/src/crepe/api/EventManager.h b/src/crepe/api/EventManager.h index 348a04d..1a33023 100644 --- a/src/crepe/api/EventManager.h +++ b/src/crepe/api/EventManager.h @@ -77,7 +77,7 @@ public:  	 * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels).  	 */  	template <typename EventType> -	void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); +	void trigger_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL);  	/**  	 * \brief Queue an event for later processing. @@ -89,7 +89,7 @@ public:  	 * \param channel The channel to associate with the event (default is CHANNEL_ALL).  	 */  	template <typename EventType> -	void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); +	void queue_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL);  	/**  	 * \brief Process all queued events. diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..0e73848 --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,16 @@ +#include "Script.h" + +using namespace crepe; + +Script::~Script() { +	EventManager & evmgr = *this->event_manager_ref; +	for (auto id : this->listeners) { +		evmgr.unsubscribe(id); +	} +} + +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback) { +	const game_object_id_t & game_object_id = *this->game_object_id_ref; +	this->subscribe_internal(callback, game_object_id); +} diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 839d937..43efd15 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -4,6 +4,8 @@  #include "../types.h" +#include "EventManager.h" +  namespace crepe {  class ScriptSystem; @@ -16,13 +18,19 @@ class ComponentManager;   * This class is used as a base class for user-defined scripts that can be added to game   * objects using the \c BehaviorScript component.   * - * \note Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as + * \info Additional *events* (like Unity's OnDisable and OnEnable) should be implemented as   * member or lambda methods in derivative user script classes and registered in \c init(). + * + * \see feature_script   */  class Script {  protected:  	/** -	 * \brief Script initialization function +	 * \name Interface functions +	 * \{ +	 */ +	/** +	 * \brief Script initialization function (empty by default)  	 *  	 * This function is called during the ScriptSystem::update() routine *before*  	 * Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component @@ -30,24 +38,32 @@ protected:  	 */  	virtual void init() {}  	/** -	 * \brief Script update function +	 * \brief Script update function (empty by default)  	 *  	 * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript  	 * component holding this script instance is active.  	 */  	virtual void update() {} +	//! \} +  	//! ScriptSystem calls \c init() and \c update()  	friend class crepe::ScriptSystem;  protected:  	/** -	 * \brief Get single component of type \c T on this game object (utility) +	 * \name Utility functions +	 * \{ +	 */ + +	/** +	 * \brief Get single component of type \c T on this game object  	 *  	 * \tparam T Type of component  	 *  	 * \returns Reference to component  	 * -	 * \throws nullptr if this game object does not have a component matching type \c T +	 * \throws std::runtime_error if this game object does not have a component matching type \c +	 * T  	 */  	template <typename T>  	T & get_component() const; @@ -55,7 +71,7 @@ protected:  	// cause compile-time errors  	/** -	 * \brief Get all components of type \c T on this game object (utility) +	 * \brief Get all components of type \c T on this game object  	 *  	 * \tparam T Type of component  	 * @@ -64,25 +80,110 @@ protected:  	template <typename T>  	RefVector<T> get_components() const; +	/** +	 * \brief Log a message using Log::logf +	 * +	 * \tparam Args Log::logf parameters +	 * \param args  Log::logf parameters +	 */ +	template <typename... Args> +	void logf(Args &&... args); + +	/** +	 * \brief Subscribe to an event with an explicit channel +	 * \see EventManager::subscribe +	 */ +	template <typename EventType> +	void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); +	/** +	 * \brief Subscribe to an event on EventManager::CHANNEL_ALL +	 * \see EventManager::subscribe +	 */ +	template <typename EventType> +	void subscribe(const EventHandler<EventType> & callback); + +	//! \} + +private: +	/** +	 * \brief Internal subscribe function +	 * +	 * This function exists so certain template specializations of Script::subscribe can be +	 * explicitly deleted, and does the following: +	 * - Wrap the user-provided callback in a check that tests if the parent BehaviorScript +	 *   component is still active +	 * - Store the subscriber handle returned by the event manager so this listener is +	 *   automatically unsubscribed at the end of this Script instance's life +	 * +	 * \tparam EventType concrete Event class +	 * \param callback User-provided callback function +	 * \param channel Event channel (may have been overridden by template specializations) +	 */ +	template <typename EventType> +	void subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel); +  protected: -	// NOTE: Script must have a constructor without arguments so the game programmer doesn't need -	// to manually add `using Script::Script` to their concrete script class. +	// NOTE: This must be the only constructor on Script, see "Late references" below  	Script() = default;  	//! Only \c BehaviorScript instantiates Script  	friend class BehaviorScript; +public: +	// std::unique_ptr destroys script +	virtual ~Script(); + +private: +	Script(const Script &) = delete; +	Script(Script &&) = delete; +	Script & operator=(const Script &) = delete; +	Script & operator=(Script &&) = delete; +  private: -	// These references are set by BehaviorScript immediately after calling the constructor of -	// Script. -	game_object_id_t game_object_id = -1; +	/** +	 * \name Late references +	 * +	 * These references are set by BehaviorScript immediately after calling the constructor of +	 * Script. +	 * +	 * \note Script must have a constructor without arguments so the game programmer doesn't need +	 * to manually add `using Script::Script` to their concrete script class if they want to +	 * implement a non-default constructor (e.g. for passing references to their own concrete +	 * Script classes). +	 * +	 * \todo These should be converted to OptionalRef<> once `loek/util` is merged +	 * +	 * \{ +	 */ +	//! Game object ID of game object parent BehaviorScript is attached to +	const game_object_id_t * game_object_id_ref = nullptr; +	//! Reference to parent component +	bool * active_ref = nullptr; +	//! Reference to component manager instance  	ComponentManager * component_manager_ref = nullptr; -	// TODO: use OptionalRef instead of pointer +	//! Reference to event manager instance +	EventManager * event_manager_ref = nullptr; +	//! \}  private:  	//! Flag to indicate if \c init() has been called already  	bool initialized = false; +	//! List of subscribed events +	std::vector<subscription_t> listeners;  }; +/** + * \brief Subscribe to CollisionEvent for the current GameObject + * + * This is a template specialization for Script::subscribe which automatically sets the event + * channel so the callback handler is only called for CollisionEvent events that apply to the + * current GameObject the parent BehaviorScript is attached to. + */ +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback); +template <> +void Script::subscribe(const EventHandler<CollisionEvent> & callback, event_channel_t) +	= delete; +  } // namespace crepe  #include "Script.hpp" diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a85d814..e94278d 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -20,8 +20,38 @@ T & Script::get_component() const {  template <typename T>  RefVector<T> Script::get_components() const { -	auto & mgr = *this->component_manager_ref; -	return mgr.get_components_by_id<T>(this->game_object_id); +	ComponentManager & mgr = *this->component_manager_ref; + +	return mgr.get_components_by_id<T>(*this->game_object_id_ref); +} + +template <typename... Args> +void Script::logf(Args &&... args) { +	Log::logf(std::forward<Args>(args)...); +} + +template <typename EventType> +void Script::subscribe_internal(const EventHandler<EventType> & callback, +								event_channel_t channel) { +	EventManager & mgr = *this->event_manager_ref; +	subscription_t listener = mgr.subscribe<EventType>( +		[this, callback](const EventType & data) -> bool { +			bool & active = *this->active_ref; +			if (!active) return false; +			return callback(data); +		}, +		channel); +	this->listeners.push_back(listener); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { +	this->subscribe_internal(callback, channel); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback) { +	this->subscribe_internal(callback, EventManager::CHANNEL_ALL);  }  } // namespace crepe diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index d55b11e..fc0bb3a 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -34,11 +34,11 @@ class Log {  public:  	//! Log message severity  	enum Level { -		TRACE, //< Include (internal) function calls -		DEBUG, //< Include dbg_logf output -		INFO, //< General-purpose messages -		WARNING, //< Non-fatal errors -		ERROR, //< Fatal errors +		TRACE, //!< Include (internal) function calls +		DEBUG, //!< Include dbg_logf output +		INFO, //!< General-purpose messages +		WARNING, //!< Non-fatal errors +		ERROR, //!< Fatal errors  	};  	/** diff --git a/src/doc/layout.xml b/src/doc/layout.xml index 2244fa7..7f514d4 100644 --- a/src/doc/layout.xml +++ b/src/doc/layout.xml @@ -42,6 +42,7 @@  	</navindex>  	<class>  		<briefdescription visible="yes"/> +		<detaileddescription title=""/>  		<includes visible="$SHOW_HEADERFILE"/>  		<inheritancegraph visible="yes"/>  		<collaborationgraph visible="yes"/> @@ -79,7 +80,6 @@  			<related title="" subtitle=""/>  			<membergroups visible="yes"/>  		</memberdecl> -		<detaileddescription title=""/>  		<memberdef>  			<inlineclasses title=""/>  			<typedefs title=""/> diff --git a/src/test/ScriptTest.cpp b/src/test/ScriptTest.cpp index 19fef6d..c35c3e2 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -6,6 +6,8 @@  #include <crepe/ComponentManager.h>  #include <crepe/api/BehaviorScript.h> +#include <crepe/api/Event.h> +#include <crepe/api/EventManager.h>  #include <crepe/api/GameObject.h>  #include <crepe/api/Script.h>  #include <crepe/api/Vector2.h> @@ -15,20 +17,34 @@ using namespace std;  using namespace crepe;  using namespace testing; +class MyEvent : public Event {}; +  class ScriptTest : public Test {  public:  	ComponentManager component_manager{};  	ScriptSystem system{component_manager}; +	EventManager & evmgr = EventManager::get_instance();  	class MyScript : public Script {  		// NOTE: default (private) visibility of init and update shouldn't cause  		// issues! -		void init() { this->init_count++; } +		void init() { +			this->init_count++; + +			subscribe<MyEvent>([this](const MyEvent &) { +				this->event_count++; +				return true; +			}); + +			// init should never be called more than once +			EXPECT_LE(this->init_count, 1); +		}  		void update() { this->update_count++; }  	public:  		unsigned init_count = 0;  		unsigned update_count = 0; +		unsigned event_count = 0;  	};  	BehaviorScript * behaviorscript_ref = nullptr; @@ -46,21 +62,58 @@ public:  		this->script_ref = (MyScript *) this->behaviorscript_ref->script.get();  		ASSERT_NE(this->script_ref, nullptr); + +		// sanity +		ASSERT_EQ(script_ref->init_count, 0); +		ASSERT_EQ(script_ref->update_count, 0); +		ASSERT_EQ(script_ref->event_count, 0);  	}  };  TEST_F(ScriptTest, Default) {  	EXPECT_EQ(0, this->script_ref->init_count);  	EXPECT_EQ(0, this->script_ref->update_count); +	EXPECT_EQ(0, this->script_ref->event_count);  }  TEST_F(ScriptTest, UpdateOnce) { +	this->system.update(); +	EXPECT_EQ(1, this->script_ref->init_count); +	EXPECT_EQ(1, this->script_ref->update_count); +	EXPECT_EQ(0, this->script_ref->event_count); +} + +TEST_F(ScriptTest, UpdateInactive) { +	this->behaviorscript_ref->active = false; +	this->system.update();  	EXPECT_EQ(0, this->script_ref->init_count);  	EXPECT_EQ(0, this->script_ref->update_count); +	EXPECT_EQ(0, this->script_ref->event_count); +	this->behaviorscript_ref->active = true;  	this->system.update();  	EXPECT_EQ(1, this->script_ref->init_count);  	EXPECT_EQ(1, this->script_ref->update_count); +	EXPECT_EQ(0, this->script_ref->event_count); +} + +TEST_F(ScriptTest, EventInactive) { +	this->system.update(); +	this->behaviorscript_ref->active = false; +	EXPECT_EQ(1, this->script_ref->init_count); +	EXPECT_EQ(1, this->script_ref->update_count); +	EXPECT_EQ(0, this->script_ref->event_count); + +	this->evmgr.trigger_event<MyEvent>(); +	EXPECT_EQ(1, this->script_ref->init_count); +	EXPECT_EQ(1, this->script_ref->update_count); +	EXPECT_EQ(0, this->script_ref->event_count); + +	this->behaviorscript_ref->active = true; +	this->evmgr.trigger_event<MyEvent>(); +	EXPECT_EQ(1, this->script_ref->init_count); +	EXPECT_EQ(1, this->script_ref->update_count); +	EXPECT_EQ(1, this->script_ref->event_count);  }  TEST_F(ScriptTest, ListScripts) {  |