aboutsummaryrefslogtreecommitdiff
path: root/src/crepe/api/Script.h
blob: 8356fd252f608adb7003a01b39482faf299ff0ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#pragma once

#include <vector>

#include "../types.h"

#include "EventManager.h"

namespace crepe {

class ScriptSystem;
class BehaviorScript;
class ComponentManager;

/**
 * \brief Script interface
 *
 * This class is used as a base class for user-defined scripts that can be added to game
 * objects using the \c BehaviorScript component.
 *
 * \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:
	/**
	 * \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
	 * holding this script instance is active.
	 */
	virtual void init() {}
	/**
	 * \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:
	/**
	 * \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 std::runtime_error if this game object does not have a component matching type \c
	 * T
	 */
	template <typename T>
	T & get_component() const;
	// TODO: make get_component calls for component types that can have more than 1 instance
	// cause compile-time errors

	/**
	 * \brief Get all components of type \c T on this game object
	 *
	 * \tparam T Type of component
	 *
	 * \returns List of component references
	 */
	template <typename T>
	std::vector<std::reference_wrapper<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: 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:
	/**
	 * \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;
	//! 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"