diff options
author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-11-21 09:43:13 +0100 |
---|---|---|
committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-11-21 09:43:13 +0100 |
commit | 502fb8e8d1dcfe10f55fdef2cdfb71afec806204 (patch) | |
tree | 8b967076cf0212f00d811e57264e3e0878c059c4 | |
parent | da379a58033c0ef3c9c854326a3fca25d6e54319 (diff) |
pull script/event changes from `loek/collision-system`
-rw-r--r-- | Doxyfile | 6 | ||||
-rw-r--r-- | src/crepe/api/BehaviorScript.h | 7 | ||||
-rw-r--r-- | src/crepe/api/BehaviorScript.hpp | 11 | ||||
-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 | 6 | ||||
-rw-r--r-- | src/crepe/api/Script.h | 60 | ||||
-rw-r--r-- | src/crepe/api/Script.hpp | 20 | ||||
-rw-r--r-- | src/doc/layout.xml | 2 | ||||
-rw-r--r-- | src/test/ScriptTest.cpp | 60 |
10 files changed, 159 insertions, 29 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 dd1efd5..5b5a418 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -9,14 +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/Event.h b/src/crepe/api/Event.h index 06cf7f3..ab4be2b 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 {}; + +} 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 index 9ebb074..d3fae07 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -9,3 +9,9 @@ Script::~Script() { } } +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 c9eb211..373b253 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -18,8 +18,10 @@ 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: @@ -88,18 +90,40 @@ protected: void logf(Args &&... args); /** - * \brief Subscribe to an event - * + * \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, event_channel_t channel = EventManager::CHANNEL_ALL); + 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; @@ -107,6 +131,7 @@ public: // std::unique_ptr destroys script virtual ~Script(); +private: Script(const Script &) = delete; Script(Script &&) = delete; Script & operator=(const Script &) = delete; @@ -119,10 +144,19 @@ private: * 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 - game_object_id_t game_object_id = -1; + 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; //! Reference to event manager instance @@ -136,6 +170,18 @@ private: 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 42c8f0b..8186bd4 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -22,7 +22,7 @@ template <typename T> std::vector<std::reference_wrapper<T>> Script::get_components() const { ComponentManager & mgr = *this->component_manager_ref; - return mgr.get_components_by_id<T>(this->game_object_id); + return mgr.get_components_by_id<T>(*this->game_object_id_ref); } template <typename... Args> @@ -31,10 +31,24 @@ void Script::logf(Args &&... args) { } template <typename EventType> -void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { +void Script::subscribe_internal(const EventHandler<EventType> & callback, event_channel_t channel) { EventManager & mgr = *this->event_manager_ref; - subscription_t listener = mgr.subscribe<EventType>(callback, channel); + 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/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..875c422 100644 --- a/src/test/ScriptTest.cpp +++ b/src/test/ScriptTest.cpp @@ -9,26 +9,44 @@ #include <crepe/api/GameObject.h> #include <crepe/api/Script.h> #include <crepe/api/Vector2.h> +#include <crepe/api/Event.h> +#include <crepe/api/EventManager.h> #include <crepe/system/ScriptSystem.h> 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 update() { this->update_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 +64,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) { @@ -70,3 +125,4 @@ TEST_F(ScriptTest, ListScripts) { } ASSERT_EQ(1, script_count); } + |