libGimbal 0.1.0
C17-Based Extended Standard Library and Cross-Language Runtime Framework
Loading...
Searching...
No Matches
gimbal_signal.h
Go to the documentation of this file.
1/*! \file
2 * \brief Signals, connections, and related API.
3 * \ingroup signals
4 * \sa signals
5 *
6 * This file contains the routines used for creating, managing, and
7 * emitting signals. For detailed information on how to use signals,
8 * refer to the \ref signals overview.
9 *
10 * \author 2023, 2025 Falco Girgis
11 * \copyright MIT License
12 *
13 * \todo
14 * - GBL_SIGNALS() DSL
15 * - GblSignal_next() for iteration
16 * - GblSignal_uninstallAll(GblType type)
17 * - thread-safety
18 */
19#ifndef GIMBAL_SIGNAL_H
20#define GIMBAL_SIGNAL_H
21
22#include "../types/gimbal_type.h"
23#include "gimbal_marshal.h"
24
25/*! \name DSL Macros
26 * \brief Helper macros for declaring and managing signals.
27 * @{
28 */
29//! Declares a list of signals to be associated with the given instanceStruct.
30#define GBL_SIGNALS(instanceStruct, /* signals */...)
31//! Registers the list of signals which has been associated with the given instanceStruct.
32#define GBL_SIGNALS_REGISTER(instanceStruct, /* marshals */...)
33//! Emits a signal from the given emitter with the given name and arguments.
34#define GBL_EMIT(emitter, /* name, */...) (GblSignal_emit(GBL_INSTANCE(emitter), __VA_ARGS__))
35//! Connects the signal with the given name from the given emitter to a receiver with the given callback function and optional userdata.
36#define GBL_CONNECT(/* emitter, name*/...) (GBL_VA_OVERLOAD_CALL_ARGC(GBL_CONNECT, __VA_ARGS__))
37//! @}
38
39/*! \defgroup signals Signals
40 * \brief Signal emission mechanism and its types and API.
41 * \ingroup meta
42 *
43 * # Overview
44 * libGimbal's signal system implements a powerful cross-language
45 * event callback mechanism which decouples event emitting types
46 * from those types which receive or listen for events. It is based
47 * upon both GTk's "signals" and Qt's "signals and slots" and is
48 * expecially powerful for GUI/widget programming.
49 *
50 * # Declaration
51 * It is a good practice to use DSL macros to predeclare any signals
52 * which are associated with a GblType as part of their public API
53 * declaration within their respective header file. As an example,
54 * the signals associated with the GblOptionGroup are declared by:
55 *
56 * \code{.c}
57 * GBL_SIGNALS(GblOptionGroup,
58 * (parsePrePass, (GBL_POINTER_TYPE, stringList),
59 * (parsePostPass, (GBL_POINTER_TYPE, stringList),
60 * (parseError, (GBL_ENUM_TYPE, errorCode)
61 * )
62 * \endcode
63 *
64 * This declares GblOptionGroup as being able to emit 3 different signals,
65 * with `parsePrePass` and `parsePostPass` sending pointers to GblStringList
66 * as their parameters, and `parseError` passing along an error code as a
67 * GblEnum as its parameter.
68 *
69 * # Registration
70 * The first thing that must happen before signals can be used is that they
71 * get registered at runtime with an actual type. This is typically done
72 * within the GblTypeInfo::pFnClassInit routine for a given type, only when
73 * its reference count is 0, meaning it is being instantiated for the first
74 * time:
75 *
76 * \code{.c}
77 * static GBL_RESULT GblOptionGroupClass_init_(GblClass* pClass, const void* pData) {
78 * if(!GblType_classRefCount(GBL_OPTION_GROUP_TYPE))
79 * GBL_SIGNALS_REGISTER(GblOptionGroup);
80 * // ...
81 * }
82 * \endcode
83 *
84 * # Basic Usage
85 * In order to get notified when a signal gets fired from a particular
86 * GblInstance subtype, we must first connect a closure to it, so that it
87 * can be invoked later when the signal fires. The simplest way is to use a
88 * C function pointer as the callback:
89 *
90 * \code{.c}
91 * // C callback to be called when a GblOptionGroupClass::parseError signal is fired.
92 * static void GblOptionGroup_onParseError_(GblInstance* pReciever, GblEnum error) {
93 * // Retrieve our emission counter from the associated closure's userdata pointer.
94 * size_t* pEmittedCounter = (size_t*)GblClosure_currentUserdata();
95 * // Increment our counter.
96 * *pEmittedCounter++;
97 *
98 * // Retrieve a pointer to the instance that emitted the current signal.
99 * GblInstance* pEmitter = GblSignal_emitter();
100 *
101 * GBL_LOG_ERROR("signal_test", "%s emitted parseError %u to %s!",
102 * GblObject_name(GBL_OBJECT(pEmitter)),
103 * error,
104 * GblObject_name(GBL_OBJECT(pReceiver)));
105 * }
106 *
107 * int main(int argc, const char* argv[]) {
108 * // Create the emitting object.
109 * GblOptionGroup* pSender = GBL_NEW(GblOptionGroupClass, "name", "emitter");
110 * // Create the receiving object.
111 * GblObject* pReceiver = GBL_NEW(GblObject, "name", "recipient");
112 * // Counter for number of times signal was emitted
113 * size_t emittedCounter = 0;
114 *
115 * // Connect the receiver's callback to the emitter's signal, plus give it a counter pointer.
116 * GBL_CONNECT(pSender,
117 * "parseError",
118 * pReceiver,
119 * GblOptionGroup_onParseError_,
120 * &emittedCounter);
121 *
122 * // Manually fire the signal ourselves (usually done internally)
123 * GBL_EMIT(pSender, "parseError", GBL_RESULT_ERROR_INVALID_TYPE);
124 *
125 * // Counter should've been incremented from the signal handler!
126 * GBL_ASSERT(emittedCounter == 1);
127 * return 0;
128 * }
129 * \endcode
130 */
131
133
134/*! \name Installing
135 * \brief Methods for installing and uninstalling signals from a GblType.
136 * @{
137 */
138//! Installs a named signal onto the given type, taking a number of arguments and an associated list of GblTypes representing each, along with a marshal which can handle forwarding such an invocation to a C callback function.
139GBL_EXPORT GBL_RESULT GblSignal_install (GblType instanceType,
140 const char* pName,
141 GblMarshalFn pFnCMarshal,
142 size_t argCount,
143 /*GblType,*/ ...) GBL_NOEXCEPT;
144//! Uninstalls the signal with the given name which was previously installed onto the given \p instanceType.
145GBL_EXPORT GBL_RESULT GblSignal_uninstall (GblType instanceType,
146 const char* pName) GBL_NOEXCEPT;
147//! Uninstalls all signals which have been installed onto the associated \p instanceType.
149//! @}
150
151/*! \name Connecting
152 * \brief Methods for connecting/disconnecting signals and closures.
153 * @{
154 */
155//! Connects the given C callback function, \p pFnCCallback, to the given \p pSignalName, which is emitted from the given \p pEmitter. \p pUserdata gets added to the associated closure and can be retrieved from within the callback with GblClosure_currentUserdata().
156GBL_EXPORT GBL_RESULT GblSignal_connect (GblInstance* pEmitter,
157 const char* pSignalName,
158 GblInstance* pReceiver,
159 GblFnPtr pFnCCallback,
160 void* pUserdata) GBL_NOEXCEPT;
161//! Forwards the signal, \p pSignalName on \p pEmitter, to the virtual function at \p methodOffset located on the GblClass structure on \p classType.
162GBL_EXPORT GBL_RESULT GblSignal_connectClass (GblInstance* pEmitter,
163 const char* pSignalName,
164 GblInstance* pReceiver,
165 GblType classType,
166 size_t methodOffset) GBL_NOEXCEPT;
167//! Creates a forwarding signal by reemitting the \p pSignalName coming from \p pEmitter as \p pDstSignalName coming from \p pDstEmitter.
168GBL_EXPORT GBL_RESULT GblSignal_connectSignal (GblInstance* pEmitter,
169 const char* pSignalName,
170 GblInstance* pDstEmitter,
171 const char* pDstSignalName) GBL_NOEXCEPT;
172//! Connects the generic closure instance, \p GblClosure, on the given \p pReceiver, from the given \p pSignalName on the given \p pemitter.
173GBL_EXPORT GBL_RESULT GblSignal_connectClosure (GblInstance* pEmitter,
174 const char* pSignalName,
175 GblInstance* pReceiver,
176 GblClosure* pClosure) GBL_NOEXCEPT;
177//! Disconnects the given \p pClosure on the given \p pReceiver from the given \p pSignalName on the given \p pEmitter, returning the number of closure which were uninstalled.
178GBL_EXPORT size_t GblSignal_disconnect (GblInstance* pEmitter,
179 const char* pSignalName,
180 GblInstance* pReceiver,
181 GblClosure* pClosure) GBL_NOEXCEPT;
182//! @}
183
184/*! \name Blocking
185 * \brief Routines for temporarily disabling and enabling signal emission.
186 * @{
187 */
188//! Blocks \p pSignalName from being emitted from \p pInstance if \p blocked is `true`, otherwise enables its emission.
189GBL_EXPORT GblBool GblSignal_block (GblInstance* pInstance,
190 const char* pSignalName,
191 GblBool blocked) GBL_NOEXCEPT;
192//! Blocks all signals from being emitted from \p pinstance if \p block is `true`, otherwise enables them.
193GBL_EXPORT GblBool GblSignal_blockAll (GblInstance* pInstance,
194 GblBool blocked) GBL_NOEXCEPT;
195//! Returns true if the signal is blocked on the given instance or if all signals are blocked, if no signal name is provided.
196GBL_EXPORT GblBool GblSignal_blocked (GblInstance* pInstance,
197 const char* pSignalName) GBL_NOEXCEPT;
198//! @}
199
200
201/*! \name Emitting
202 * \brief Routines for firing signals to associated closures.
203 * @{
204 */
205//! Fires \p pSignalName from \p pEmitter, with the given variadic argument list.
206GBL_EXPORT GBL_RESULT GblSignal_emit (GblInstance* pEmitter,
207 const char* pSignalName,
208 ...) GBL_NOEXCEPT;
209//! Equivalent to GblSignal_emit(), except taking arguments as a va_list* rather than variadic args.
210GBL_EXPORT GBL_RESULT GblSignal_emitVaList (GblInstance* pEmitter,
211 const char* pSignalName,
212 va_list* pVarArgs) GBL_NOEXCEPT;
213//! Equivalent to GblSignal_emit(), except taking arguments as an array of GblVariants rather than variadic args.
214GBL_EXPORT GBL_RESULT GblSignal_emitVariants (GblInstance* pEmitter,
215 const char* pSignalName,
216 GblVariant* pArgs) GBL_NOEXCEPT;
217//! @}
218
219/*! \name Querying
220 * \brief Routines for retrieving signal state information.
221 * @{
222 */
223//! Returns the number of connections which have been registered for \p pSignalName on \p pInstance.
224GBL_EXPORT size_t GblSignal_connectionCount (GblInstance* pInstance,
225 const char* pSignalName) GBL_NOEXCEPT;
226//! Returns a pointer to the GblInstance which emitted the acive signal or `NULL` if there isn't an active one.
228//! Returns a pointer to the GblInstance which received the active signal or `NULL` if there isn't an active one.
230//! @}
231
232#define GblSignal_connect(...) GBL_VA_OVERLOAD_CALL_ARGC(GblSignal_connect, __VA_ARGS__)
233
234// ===== IMPLEMENTATION =====
235
236///\cond
237#define GblSignal_connect_3(emitter, signal, callback)
238 (GblSignal_connect_4(emitter, signal, emitter, callback))
239#define GblSignal_connect_4(emitter, signal, receiver, callback)
240 (GblSignal_connect_5(emitter, signal, receiver, callback, GBL_NULL))
241#define GblSignal_connect_5(emitter, signal, receiver, callback, userdata)
242 ((GblSignal_connect)(emitter, signal, receiver, callback, userdata))
243
244#define GBL_CONNECT_3(emitter, signal, callback)
245 (GBL_CONNECT_4(emitter, signal, emitter, callback))
246#define GBL_CONNECT_4(emitter, signal, receiver, callback)
247 (GBL_CONNECT_5(emitter, signal, receiver, callback, GBL_NULL))
248#define GBL_CONNECT_5(emitter, signal, receiver, callback, userdata)
249 ((GblSignal_connect)(GBL_INSTANCE(emitter), signal, GBL_INSTANCE(receiver), GBL_CALLBACK(callback), (void*)userdata))
250
251#define GBL_SIGNALS_(instance, ...)
252 GBL_INLINE GBL_RESULT instance##_registerSignals_(instance* pSelf, GblMarshalFn* pMarshals) {
253 GBL_UNUSED(pSelf, pMarshals);
254 GBL_CTX_BEGIN(NULL);
255 GBL_TUPLE_FOREACH(GBL_SIGNAL_INSTALL_, instance, (__VA_ARGS__))
256 GBL_CTX_END();
257 }
258
259
260#define GBL_SIGNAL_INSTALL_(instance, signal)
261
262#if 0
263GBL_CTX_VERIFY_CALL(GblSignal_install(GBL_TYPEID(instance),
264 GBL_STRINGIFY(GBL_TUPLE_FIRST signal),
265 pMarshals++,
266 GBL_NARG signal - 1,
267 GBL_TUPLE_FOREACH(GBL_SIGNAL_ARG_TYPE_, instance, GBL_TUPLE_REST(signal) )));
268#endif
269
270#define GBL_SIGNAL_ARG_TYPE_(instance, pair)
271 GBL_TUPLE_FIRST(pair),
272
273#define GBL_SIGNALS_REGISTER_(instance, marshals)
274 instance##_registerSignals_(instance, marshals)
275///\endcond
276
278
279#endif // GIMBAL_SIGNAL_H
#define GBL_NULL
#define GBL_NOEXCEPT
#define GBL_INLINE
#define GBL_DECLS_BEGIN
#define GBL_UNUSED(...)
#define GBL_EXPORT
#define GBL_TUPLE_FIRST(...)
#define GBL_TUPLE_FOREACH(MACRO_, DATA_, TUPLE_)
#define GBL_VA_OVERLOAD_CALL_ARGC(BASE,...)
GblBool GblSignal_block(GblInstance *pInstance, const char *pSignalName, GblBool blocked)
Blocks pSignalName from being emitted from pInstance if blocked is true, otherwise enables its emissi...
GBL_RESULT GblSignal_uninstallAll(GblType instanceType)
Uninstalls all signals which have been installed onto the associated instanceType.
GBL_RESULT GblSignal_install(GblType instanceType, const char *pName, GblMarshalFn pFnCMarshal, size_t argCount,...)
Installs a named signal onto the given type, taking a number of arguments and an associated list of G...
GblBool GblSignal_blockAll(GblInstance *pInstance, GblBool blocked)
Blocks all signals from being emitted from pinstance if block is true, otherwise enables them.
#define GblSignal_connect(...)
GBL_RESULT GblSignal_emitVaList(GblInstance *pEmitter, const char *pSignalName, va_list *pVarArgs)
Equivalent to GblSignal_emit(), except taking arguments as a va_list* rather than variadic args.
size_t GblSignal_connectionCount(GblInstance *pInstance, const char *pSignalName)
Returns the number of connections which have been registered for pSignalName on pInstance.
GBL_RESULT GblSignal_emit(GblInstance *pEmitter, const char *pSignalName,...)
Fires pSignalName from pEmitter, with the given variadic argument list.
#define GBL_CONNECT(...)
Connects the signal with the given name from the given emitter to a receiver with the given callback ...
GblInstance * GblSignal_receiver(void)
Returns a pointer to the GblInstance which received the active signal or NULL if there isn't an activ...
GBL_RESULT GblSignal_connect(GblInstance *pEmitter, const char *pSignalName, GblInstance *pReceiver, GblFnPtr pFnCCallback, void *pUserdata)
Connects the given C callback function, pFnCCallback, to the given pSignalName, which is emitted from...
GblBool GblSignal_blocked(GblInstance *pInstance, const char *pSignalName)
Returns true if the signal is blocked on the given instance or if all signals are blocked,...
GBL_RESULT GblSignal_connectSignal(GblInstance *pEmitter, const char *pSignalName, GblInstance *pDstEmitter, const char *pDstSignalName)
Creates a forwarding signal by reemitting the pSignalName coming from pEmitter as pDstSignalName comi...
GBL_RESULT GblSignal_uninstall(GblType instanceType, const char *pName)
Uninstalls the signal with the given name which was previously installed onto the given instanceType.
GBL_RESULT GblSignal_connectClass(GblInstance *pEmitter, const char *pSignalName, GblInstance *pReceiver, GblType classType, size_t methodOffset)
Forwards the signal, pSignalName on pEmitter, to the virtual function at methodOffset located on the ...
GBL_RESULT GblSignal_emitVariants(GblInstance *pEmitter, const char *pSignalName, GblVariant *pArgs)
Equivalent to GblSignal_emit(), except taking arguments as an array of GblVariants rather than variad...
size_t GblSignal_disconnect(GblInstance *pEmitter, const char *pSignalName, GblInstance *pReceiver, GblClosure *pClosure)
Disconnects the given pClosure on the given pReceiver from the given pSignalName on the given pEmitte...
GblInstance * GblSignal_emitter(void)
Returns a pointer to the GblInstance which emitted the acive signal or NULL if there isn't an active ...
GBL_RESULT GblSignal_connectClosure(GblInstance *pEmitter, const char *pSignalName, GblInstance *pReceiver, GblClosure *pClosure)
Connects the generic closure instance, GblClosure, on the given pReceiver, from the given pSignalName...
void(* GblFnPtr)()
Type used for holding an untyped function pointer.
uint8_t GblBool
Basic boolean type, standardized to sizeof(char)
#define GBL_CALLBACK(fn)
Casts a C function pointer to a generic callback type.
uintptr_t GblType
Meta Type UUID.
Definition gimbal_type.h:52