aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doxyfile6
-rw-r--r--src/crepe/api/BehaviorScript.h7
-rw-r--r--src/crepe/api/BehaviorScript.hpp12
-rw-r--r--src/crepe/api/CMakeLists.txt1
-rw-r--r--src/crepe/api/Event.h12
-rw-r--r--src/crepe/api/EventManager.h4
-rw-r--r--src/crepe/api/Script.cpp16
-rw-r--r--src/crepe/api/Script.h125
-rw-r--r--src/crepe/api/Script.hpp34
-rw-r--r--src/crepe/util/Log.h10
-rw-r--r--src/doc/layout.xml2
-rw-r--r--src/test/ScriptTest.cpp55
12 files changed, 247 insertions, 37 deletions
diff --git a/Doxyfile b/Doxyfile
index e0a31df..f2714cd 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -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) {