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