aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLoek Le Blansch <loek@pipeframe.xyz>2024-11-21 09:43:13 +0100
committerLoek Le Blansch <loek@pipeframe.xyz>2024-11-21 09:43:13 +0100
commit502fb8e8d1dcfe10f55fdef2cdfb71afec806204 (patch)
tree8b967076cf0212f00d811e57264e3e0878c059c4 /src
parentda379a58033c0ef3c9c854326a3fca25d6e54319 (diff)
pull script/event changes from `loek/collision-system`
Diffstat (limited to 'src')
-rw-r--r--src/crepe/api/BehaviorScript.h7
-rw-r--r--src/crepe/api/BehaviorScript.hpp11
-rw-r--r--src/crepe/api/Event.h12
-rw-r--r--src/crepe/api/EventManager.h4
-rw-r--r--src/crepe/api/Script.cpp6
-rw-r--r--src/crepe/api/Script.h60
-rw-r--r--src/crepe/api/Script.hpp20
-rw-r--r--src/doc/layout.xml2
-rw-r--r--src/test/ScriptTest.cpp60
9 files changed, 155 insertions, 27 deletions
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);
}
+