libGimbal 0.1.0
C17-Based Extended Standard Library and Cross-Language Runtime Framework
Loading...
Searching...
No Matches
gimbal_interface.h
Go to the documentation of this file.
1/*! \file
2 * \brief GblInterface and related functions.
3 * \ingroup meta
4 *
5 * This file contains the GblInterface structure and its
6 * type system API.
7 *
8 * \author 2023, 2025 Falco Girgis
9 * \copyright MIT License
10 */
11#ifndef GIMBAL_INTERFACE_H
12#define GIMBAL_INTERFACE_H
13
14#include "../classes/gimbal_class.h"
15
16#define GBL_INTERFACE_TYPE GBL_BUILTIN_TYPE(INTERFACE)
17
18#define GBL_INTERFACE(klass) ((GblInterface*)GblClass_cast(GBL_CLASS(klass), GBL_INTERFACE_TYPE))
19#define GBL_INTERFACE_TRY(klass) ((GblInterface*)GblClass_as(GBL_CLASS(klass), GBL_INTERFACE_TYPE))
20#define GBL_INTERFACE_OUTER_CLASS(iface) (GblInterface_outerClass(GBL_INTERFACE(iface)))
21#define GBL_INTERFACE_OUTER_MOST_CLASS(iface) (GblInterface_outerMostClass(GBL_INTERFACE(iface)))
22
23#define GBL_SELF_TYPE GblInterface
24
26
27//! Base struct for all interfaces, inherits from GblClass
28typedef struct GblInterface {
29 GblClass base; //!< inherited GblClass base info
30 size_t outerClassOffset_; //!< offset from the interface to the class containing it (private, managed by internals)
31} GblInterface;
32
35
37
38/*! \def GBL_INTERFACE_TYPE
39 * Builtin type ID associated with GblInterface
40 * \ingroup metaBuiltinTypes
41 * \sa GblInterface
42 */
43
44/*! \fn GBL_INTERFACE(klass)
45 * Convenience function-style cast operator that returns the
46 * given class as a GblInterface, provided it is actually one,
47 * erroring out upon failure.
48 * \relatedalso GblInterface
49 * \param klass pointer to a GblClass or derived
50 * \returns GblInterface pointer or NULL if klass isn't one
51 * \sa GBL_INTERFACE_TRY()
52 */
53
54/*! \fn GBL_INTERFACE_TRY(klass)
55 * Convenience function-style cast operator that returns the
56 * given class as a GblInterface, provided it is actually one.
57 * \relatedalso GblInterface
58 * \param klass pointer to a GblClass or derived
59 * \returns GblInterface pointer or NULL if klass isn't one
60 * \sa GBL_INTERFACE()
61 */
62
63/*! \fn GBL_INTERFACE_OUTER_CLASS(iface)
64 * Convenience macro wrapping GblInterface_outerClass(),
65 * automatically casting the input parameter.
66 * \relatedalso GblInterface
67 * \param iface pointer to a GblInterface or derived
68 * \returns GblClass pointer or NULL if it hasn't beenm mapped
69 * to one
70 * \sa GblInterface_outerClass
71 */
72
73/*! \fn GBL_INTERFACE_OUTER_MOST_CLASS(iface)
74 * Convenience macro wrapping GblInterface_outerMostClass(),
75 * automatically casting the input parameter.
76 * \relatedalso GblInterface
77 * \param iface pointer to a GblInterface or derived
78 * \returns GblClass pointer or NULL if it hasn't beenm mapped
79 * to one
80 * \sa GblInterface_outerMostClass
81 */
82
83/*! \struct GblInterface
84 * \ingroup meta
85 * \details
86 * \extends GblClass
87 *
88 * An interface is a special type of GblClass which represents an
89 * abstract collection of data which may be "implemented" by
90 * another class and then queried for later. This data is
91 * typically in the form of function pointers, which may or may
92 * not have default values, which can then be set by a the class
93 * constructor of another type.
94 *
95 *\note
96 * Since a GblInterface inherits from GblClass, it is compatible
97 * with all of the methods associated with a GblClass, such as
98 * GblClass_check(), GblClass_cast(), and GblClass_as(). These
99 * are used to verify and query for GblInterface objects
100 * which are contained within a given GblClass.
101 *
102 * The main advantage of modeling overriddable methods within
103 * an interface as opposed to just putting them within a class
104 * is that the interface can be implemented by any class,
105 * without having to inherit or derive from it.
106 *
107 * GblInterface is the base structure which is to be inherited by all
108 * interface structures within the meta type system. This means placing
109 * it or a type "inheriting" from it as the first member of an
110 * interface struct, when using C.
111 * \code{.c}
112 * typedef struct ICallableInterface {
113 * GblInterface base;
114 * GBL_RESULT (*pFnVCall)(ICallable* pSelf);
115 * } ICallableInterface
116 * \endcode
117 *
118 * In terms of implementation, a GblInterface is actually implemented
119 * as a type of mappable "subclass" which is then embedded within a
120 * the structure of a GblClass-inheriting structure. When a type
121 * which implements an interface is registered, its location within
122 * the class is provided to the type system via GblTypeInfo::pInterfaceImpls.
123 *\sa GblClass, GblInstance, GblType
124 */
125
126/*!
127 * \fn GblInterface_outerClass(GblInterface* pSelf)
128 * Returns the GblClass implementing the given interface.
129 * \relatesalso GblInterface
130 * \note If the specified interface is a default implementation,
131 * its own GblClass base is returned.
132 * \param pSelf interface
133 * \returns GblClass pointer to the implementing class.
134 * \sa GblClass_outerMost
135 */
136
137/*!
138 \fn GblClass* GblInterface_outerMostClass(GblInterface* pSelf)
139 * Returns the top-level GblClass implementing the interface.
140 *
141 * The top-level class will either be its outer class, in the
142 * case of a regular embedded interface, or it could be multiple
143 * levels out, in the case of an interface mapping an interface.
144 * \relatesalso GblInterface
145 * \param pSelf interface
146 * \returns GblClass pointer to the top-most implementing class.
147 * \sa GblClass_as
148 */
149
150/*! \page Interfaces Interfaces
151 * \brief Overview of interfaced types
152 * \tableofcontents
153 *
154 * LibGimbal's object model supports the concept of the C# or
155 * Java-style "Interface," which is a polymorphic type
156 * used to model abstract behavior on a class or object.
157 *
158 * This is fancy object-oriented speak for allowing you
159 * to define a set of methods which can be implemented by
160 * any class which can then be queried for later. The main
161 * advantage of modeling overridable methods with this approach
162 * is that it doesn't require a type to inherit or derive from
163 * a common subtype. Any class inheriting from any other can
164 * implement any number of interfaces and inherit their
165 * implementations from parent classes.
166 *
167 * LibGimbal interfaces support:
168 * - abstract overridable methods
169 * - overridable methods with a default implementation
170 * - static members
171 * - implementing other interfaces
172 * - inheriting from other interfaces
173 *
174 * ## Declaring
175 * All interfaces in libGimbal derive from the base type,
176 * #GBL_INTERFACE_TYPE. This is a class-only, abstract type which
177 * defines the base class which we will use: GblInterface.
178 *
179 * ### Structures
180 * We can create our own interface class sructure by
181 * deriving from GblInterface:
182 * \code{.c}
183 * // lets create an interface to implement saving/loading
184 * GBL_INTERFACE_DERIVE(ISerializable)
185 * // declare virtual methods for implementing save/load
186 * GBL_RESULT (*pFnSave)(const ISerializable* pSelf, GblStringBuffer* pBuffer);
187 * GBL_RESULT (*pFnLoad)(ISerializable* pSelf, const GblStringBuffer* pBuffer);
188 * GBL_INTERFACE_END
189 * \endcode
190 * \note
191 * It is not a hard requirement that interface methods must return a GBL_RESULT type;
192 * however, this can be extremely convenient for propagating errors should one occur
193 * within the implementation. Since these methods are not what are typically called
194 * by a user, it doesn't make your API any uglier to do so (see the next section).
195 *
196 * ### Macro Utilities
197 * As is typical with most libGimbal types, it is often most convenient on
198 * the user (and you) to define a set of common macro operators for working with your
199 * type.
200 *
201 * For our serializable interface, we will use the following:
202 * \code{.c}
203 * // convenience macro returning type ID from registration
204 * #define ISERIALIZABLE_TYPE (GBL_TYPEID(ISerializable))
205 *
206 * // function-style cast operator from an generic instance to our instance type
207 * #define ISERIALIZABLE(instance) (GBL_CAST(instance, ISerializable))
208 *
209 * // function-style cast operator from a generic class to our class/interface type
210 * #define ISERIALIZABLE_CLASS(klass) (GBL_CLASS_CAST(klass, ISerializable))
211 *
212 * // convenience macro to extract our interface from a generic instance
213 * #define ISERIALIZABLE_GET_CLASS(instance) (GBL_CLASSOF(instance, ISerializable))
214 * \endcode
215 *
216 * ### Public Methods
217 * Typically, when working with interface methods, we would rather provide a
218 * user-friendly API function wrapping the virtual method and handling any
219 * errors, rather than making a user reach into an interface and call a
220 * function pointer directly.
221 *
222 * We do this for our virtual save method:
223 * \code{.c}
224 * GBL_RESULT ISerializable_save(const ISerializable* pSelf, GblStringBuffer* pBuffer) {
225 * GBL_CTX_BEGIN(NULL);
226 *
227 * // exttract our interface from the given instance
228 * ISerializableClass* pClass = ISERIALIZABLE_GET_CLASS(pSelf);
229 *
230 * // check whether we managed to find the interface
231 * if(pClass) {
232 *
233 * // check whether the virtual method has been implemented
234 * if(pClass->pFnSave) {
235 *
236 * // call virtual method implementation,
237 * // propagating any error code returned
238 * GBL_CTX_VERIFY_CALL(pClass->pFnSave(pSelf, pBuffer);
239 * }
240 * }
241 * GBL_CTX_END();
242 * }
243 * \endcode
244 * As you can see, when we expose our virtual methods via a public API wrapper,
245 * the entry-point becomes prettier than calling directly into a function pointer,
246 * and we are able to do type checking and error handling. We can check to see
247 * whether the given instance was even compatible with our interface or whether
248 * the class implementing the interface actually overrode the method or not.
249 *
250 * ## Registering
251 * Once we've defined our structures, created our utility macros, and created
252 * a public API around our virtual methods, it's time to register our type.
253 * To do this, we implement the ISerializable_type() function declared
254 * earlier to register a new meta type if we haven't already:
255 * \code{.c}
256 * GblType ISerializable_type(void) {
257 * // declare static variable, so we store the value
258 * static GblType type = GBL_INVALID_TYPE;
259 *
260 * // only register if this is our first time getting called
261 * if(type == GBL_INVALID_TYPE) {
262 *
263 * // register our interface's class, deriving from GBL_INTERFACE_TYPE
264 * type = GblType_register("ISerializable",
265 * GBL_INTERFACE_TYPE,
266 * &(const GblTypeInfo) {
267 * .classSize = sizeof(ISerializableClass)
268 * },
269 * GBL_TYPE_FLAG_ABSTRACT);
270 * }
271 *
272 * return type;
273 * }
274 * \endcode
275 * \note
276 * If we wish to provide a default implementation of our virtual methods, we would
277 * also set GblTypeInfo::pFnClassInit to a ::GblClassInitFn function
278 * where we would initialize our class structure with some default values.
279 *
280 * ## Implementing
281 * In order to use your interface with a given type, the type must
282 * implement the interface and then provide the meta type system with
283 * a mapping during type registration.
284 *
285 * ### Structures
286 * If we wish to implement our interface on another type, we embed it
287 * within that type's class structure. Here we will use the libGimbal
288 * macro DSL which will handle generating our structures for us.
289 *
290 * \code{.c}
291 * // Class structure (base class with one interface being implemented)
292 * GBL_CLASS_BASE(IntSerializable, ISerializable)
293 * // any extra static variables or virtual functions here
294 * GBL_CLASS_END
295 *
296 * // could've used GBL_CLASS_BASE_EMPTY() if we had nothing extra to add
297 *
298 * // Instance structure
299 * GBL_INSTANCE_BASE(IntSerializable)
300 * int integer; //single instance member
301 * GBL_INSTANCE_END
302 * \endcode
303 *
304 * ### Overrides
305 *
306 * Finally, lets create an implementation of the save and load functions
307 * from the ISerializableIFace class, along with a class constructor for
308 * initializing IntSerializableClass:
309 *
310 * \code{.c}
311 * // IntSerializable's implementation of ISerializable_load()
312 * GBL_RESULT IntSerializable_load_(ISerializable* pSelf, const GblStringBuffer* pBuffer) {
313 * IntSerializable* pInt = (IntSerializable*)pSelf;
314 *
315 * // load an integer from the string buffer
316 * pInt->integer = GblStringView_toInt(GblStringBuffer_view(pBuffer));
317 *
318 * }
319 *
320 * // IntSerializable's implementation of ISerialiable_save();
321 * GBL_RESULT IntSerializable_save_(const ISerializable* pSelf, GblStringBuffer* pBuffer) {
322 * IntSerializable* pInt = (IntSerializable*)pSelf;
323 *
324 * // save the integer to the string buffer
325 * return GblStringBuffer_appendInt(pBuffer, pInt->integer);
326 * }
327 *
328 * // IntSerializable's class initializer, called when the class is created
329 * GBL_RESULT IntSerializable_initializeClass(GblClass* pClass, const void* pUd, GblContext* pCtx) {
330 * IntSerializableClass* pSelfClass = (IntSerializableClass*)pClass;
331 *
332 * // override the interface implementation for the class
333 * pSelfClass->iSerializable.pFnLoad = IntSerializable_load_;
334 * pSelfClass->iSerializable.pFnSave = IntSerializable_save_;
335 * }
336 * \endcode
337 * ### Registration
338 *
339 * In order to register a type as having implemented an interface, we have to tell the
340 * meta type system how to "map" between the interface and the class. In order to achieve
341 * this, we pass an array of interface mappings to GblType_register() via
342 * GblTypeInfo.pInterfaceImpls:
343 * \code{.c}
344 * GblType IntSerializableType_type(void) {
345 * static GblType type = GBL_INVALID_TYPE;
346 *
347 * if(type == GBL_INVALID_TYPE) {
348 *
349 * type = GblType_register("IntSerializable",
350 * GBL_INSTANCE_TYPE,
351 * &(const GblTypeInfo) {
352 * .classSize = sizeof(IntSerializableClass),
353 * .pFnClassInit = IntSerializable_initializeClass,
354 * .instanceSize = sizeof(IntSerializable),
355 * .interfaceCount = 1,
356 * .pInterfaceImpls = (const GblInterfaceImpl[]) {
357 * {
358 * .interfaceType = ISERIALIZABLE_TYPE,
359 * .classOffset = offsetof(IntSerializableClass, iSerializable)
360 * }
361 * },
362 * GBL_TYPE_FLAGS_NONE);
363 * }
364 * return type;
365 * }
366 * \endcode
367 * As you can see, we provided a single entry into the interface mapping, which we used to
368 * associate our interface type with the given class offset. Now the meta type system
369 * knows everything it needs to be able to cast to and from your interface!
370 *
371 * ## Querying
372 *
373 * Querying for interfaces is extremely simple. Since they're essentially a type of GblClass,
374 * you use the same set of functions you would use to cast between class types. Lets create
375 * an instance of the IntSerializable type and try to serialize it with our interface using
376 * the utility macros defined earlier to handle casting:
377 * \code{.c}
378 * // create an instance
379 * IntSerializable* pIntInstance = GblInstance_create(intSerializeType);
380 *
381 * // create a new string buffer to hold the data
382 * GblStringBuffer buffer;
383 * GblStringBuffer_construct(&buffer, GBL_STRV("7"));
384 *
385 * // cast our type to an ISerializable and call the virtual function
386 * ISerializable_load(ISERIALIZABLE(pIntInstance), &buffer);
387 *
388 * // look, we loaded the initial value of 7!
389 * assert(pIntInstance->integer == 7);
390 *
391 * pIntInstance->integer = -7;
392 *
393 * // we can now save a value back too!
394 * GblStringBuffer_clear(&buffer);
395 * ISerializable_save(ISERIALIZABLE(pIntInstance), &buffer));
396 *
397 * // look, it saved the new value!
398 * assert(GblStringView_toInt(GblStringBuffer_view(&buffer)) == -7);
399 *
400 * // destroy out instance when we're done
401 * GblInstance_destroy(pIntInstance);
402 * \endcode
403 * Querying for the GblInterface structure without having defined the convenience
404 * macros from the previous section is still possible, but it's much uglier and
405 * more verbose.
406 * \code{.c}
407 * // A. Cast to an ISerializable instance from IntSerializable
408 * ISerializable* pSerializable = (ISerializable*)GblInstance_cast((GblInstance*)pIntInstance,
409 * GBL_ISERIALIZABLE_TYPE);
410 *
411 * // B. Extract the ISerializableIFace class from IntSerializable
412 *
413 * // 1. retrieve the class from the instance
414 * GblClass* pClass = GblInstance_class((GblInstance*)pSerializable);
415 *
416 * // 2. retrieve the interface from the class
417 * ISerializableIFace* pIFace = (ISerializableIFace*)GblClass_cast(pClass, GBL_ISERIALIZABLE_TYPE);
418 * \endcode
419 */
420
421#undef GBL_SELF_TYPE
422
423#endif // GIMBAL_INTERFACE_H
#define GBL_BUILTIN_TYPE(prefix)
Returns a type from the macro prefix of a builtin type.
#define GBL_NOEXCEPT
#define GBL_DECLS_BEGIN
#define GBL_EXPORT
GblClass * GblInterface_outerMostClass(GblInterface *pSelf)
Returns the top-level GblClass implementing the interface.
GblClass * GblInterface_outerClass(GblInterface *pSelf)
Returns the GblClass implementing the given interface.
#define GBL_INTERFACE_TYPE
Builtin type ID associated with GblInterface.
Base struct for all interfaces, inherits from GblClass.
GblClass base
inherited GblClass base info
size_t outerClassOffset_
offset from the interface to the class containing it (private, managed by internals)