mirror of
https://github.com/utkumaden/libmx.git
synced 2025-01-22 21:46:33 +01:00
220 lines
6.4 KiB
C
220 lines
6.4 KiB
C
#ifndef _MX_TRAIT_H_
|
|
#define _MX_TRAIT_H_
|
|
|
|
/**
|
|
* @file trait.h Interface Traits and Fat Pointers
|
|
*
|
|
* This is the MX interface library. Interfaces define common traits C and C++
|
|
* objects can have. This functionality is exposed through a fat pointer, which
|
|
* is just two pointers: the object reference and its virtual table (vtable).
|
|
*
|
|
* @section new_interface Creating an Interface
|
|
* The convention in the library is to prefix an interface with an I, and use
|
|
* Pascal case naming conventions for the interface structure.
|
|
*
|
|
* Define the interface like any other C structure, except, only include function
|
|
* pointers and other interfaces in it. Then use `fatptr_define` to create a fat
|
|
* pointer for the given interface.
|
|
*
|
|
* @code
|
|
* typedef struct IFoo {
|
|
* IObject Object; // Base interface.
|
|
* void (*bar)(void *self); // Virtual method objects can implement.
|
|
* } IFoo;
|
|
*
|
|
* fatptr_define(IFoo);
|
|
* @endcode
|
|
*
|
|
* Now, in order to create a fat pointer with this interface, you can use the
|
|
* generic type fatptr_t(T). Notice the parenthesis, because this is a C
|
|
* library.
|
|
*
|
|
* A suggestion is to create inline static functions right after this interface
|
|
* declaration to aid calling the virtual methods of the trait defined.
|
|
*
|
|
* @code
|
|
* MX_INLINE void IFoo_bar(fatptr_t(IFoo) ptr) {
|
|
* fatptr_vcall(ptr, bar);
|
|
* }
|
|
* @endcode
|
|
*
|
|
* In order to implement these traits, create a source code file, and implement
|
|
* the trait functions as you like. These functions can be static. Make sure
|
|
* the virtual methods and the implementations have the same function signature.
|
|
*
|
|
* Then, create a vtable for the object using the fat_vtable(type, trait), and
|
|
* refrerence the implemented functions.
|
|
*
|
|
* @code
|
|
* typedef struct MyFoo
|
|
* {
|
|
* // Private implementation data.
|
|
* } MyFoo;
|
|
*
|
|
* static void MyFoo_IFoo_bar(void *self)
|
|
* {
|
|
* printf("Hello World!\n");
|
|
* }
|
|
*
|
|
* IFoo fat_vtable(MyFoo, IFoo) = { MyFoo_IFoo_bar };
|
|
* @endcode
|
|
*
|
|
* Now you have sucessfully implemented this trait. Now let's see an example
|
|
* of how this is used.
|
|
*
|
|
* @code
|
|
* void FooConsumer(fatptr_t(IFoo) foo)
|
|
* {
|
|
* fat_vcall(foo, bar);
|
|
* }
|
|
*
|
|
* int main(void)
|
|
* {
|
|
* MyFoo foo = {};
|
|
* fatptr_t(IFoo) foo_trait = fat_cast(foo, MyFoo, IFoo);
|
|
* FooConsumer(foo_trait);
|
|
* }
|
|
* @endcode
|
|
*
|
|
* If everything went right, this should print <code>Hello World!</code> and exit.
|
|
*
|
|
* @section fat_functions "fat" Functions
|
|
* The structure fatptr_t(T) serves as the main type for this library, where
|
|
* T is the interface that defines the object traits. Ideally all objects should
|
|
* implement the IObject trait, but this isn't enforced at all.
|
|
*
|
|
* You can use fat_new(ptr, vtable, trait) in your code to create a fat pointer
|
|
* to the object "ptr", with a "vtable" (the implementation of the traits
|
|
* defined by the interface) for the interface "trait".
|
|
*
|
|
* You can use fat_cast(object, type, trait) to cast the value "object" of
|
|
* "type" to a fat pointer with traits "trait". This will look up the object
|
|
* vtable and create a fat pointer automatically.
|
|
*
|
|
* fat_reinterpret(value, trait) will reinterpret the fatptr "value" a
|
|
* fatptr_t(trait). This type of cast is only valid as long as the first member
|
|
* of the source fat pointer interface is also an interface.
|
|
*
|
|
* fat_vnill(fatptr, vmethod) checks if a virtual method "vmethod" of the fat
|
|
* pointer is nill. This is mostly useful for optional traits.
|
|
*
|
|
* fat_vcall(fatptr, vmethod, ...) will invoke the "vmethod" of "fatptr". This
|
|
* is a GNU-C macro so your mileage may vary using it on niche compilers.
|
|
*/
|
|
|
|
#include "mx/base.h"
|
|
|
|
/**
|
|
* Fat pointer to any object.
|
|
*/
|
|
#define fatptr_t(trait) struct __mx_fatptr_##trait##_t
|
|
|
|
/**
|
|
* @brief Define the fat pointer structure.
|
|
* @param base Base pointer type
|
|
*/
|
|
#define fatptr_define(trait) fatptr_t(trait) { void *ptr; const trait *traits; }
|
|
|
|
/**
|
|
* Fat pointer vtable for object #type for traits #trait
|
|
*/
|
|
#define fat_vtable(type, trait) __mx_fat_vtable_##type##_##trait
|
|
|
|
/**
|
|
* Create a fat pointer to #ptr, with #vtable of #trait.
|
|
*/
|
|
#define fat_new(ptr, vtable, trait) ((fatptr_t(trait)){ (ptr), &(vtable)})
|
|
|
|
/**
|
|
* Cast a value #object of type #type into fat pointer of #trait.
|
|
*/
|
|
#define fat_cast(object, type, trait) fat_new(&(object), fat_vtable(type, trait), trait)
|
|
|
|
/**
|
|
* Reinterpret a fatptr into another fatptr.
|
|
*/
|
|
#define fat_reinterpret(value, trait) (*((fatptr_t(trait)*)&(value)))
|
|
|
|
/**
|
|
* True if vmethod is null.
|
|
*/
|
|
#define fat_vnil(fatptr, vmethod) ((fatptr)->traits.vmethod == NULL)
|
|
|
|
/**
|
|
* Base traits of all MX objects.
|
|
*/
|
|
typedef struct IObject
|
|
{
|
|
/**
|
|
* Get the size of this object in bytes.
|
|
* @param self Instance object.
|
|
* @return The size of the object in bytes.
|
|
*/
|
|
size_t (*get_size)(void *self);
|
|
/**
|
|
* Get the type object for this.
|
|
* @param self Instance object.
|
|
*/
|
|
void * (*get_type)(void *self);
|
|
/**
|
|
* Convert this object to a string.
|
|
* @param self Instance object.
|
|
* @param[out] buffer The buffer to write the string into.
|
|
* @param[in] max The maximum number of bytes to write.
|
|
* @return The size of the written string.
|
|
*/
|
|
size_t (*to_string)(void *self, char *buffer, size_t max);
|
|
/**
|
|
* Destructor function for this object.
|
|
* @param self Instance object.
|
|
*/
|
|
void (*destruct)(void* self);
|
|
} IObject;
|
|
fatptr_define(IObject);
|
|
|
|
// This is GNU C :/
|
|
|
|
#define fatptr_vcall(fatptr, vmethod, ...) ((fatptr).traits->vmethod(fatptr.ptr, ## __VA_ARGS__))
|
|
|
|
/**
|
|
* Get the size of this object in bytes.
|
|
* @param self Instance object.
|
|
* @return The size of the object in bytes.
|
|
*/
|
|
MX_INLINE size_t IObject_get_size(fatptr_t(IObject) obj)
|
|
{
|
|
return fatptr_vcall(obj, get_size);
|
|
}
|
|
|
|
/**
|
|
* Get the type object for this.
|
|
* @param self Instance object.
|
|
*/
|
|
MX_INLINE void* IObject_get_type(fatptr_t(IObject) obj)
|
|
{
|
|
return fatptr_vcall(obj, get_type);
|
|
}
|
|
|
|
/**
|
|
* Convert this object to a string.
|
|
* @param self Instance object.
|
|
* @param[out] buffer The buffer to write the string into.
|
|
* @param[in] max The maximum number of bytes to write.
|
|
* @return The size of the written string.
|
|
*/
|
|
MX_INLINE size_t IObject_to_string(fatptr_t(IObject) obj, char *buffer, size_t max)
|
|
{
|
|
return fatptr_vcall(obj, to_string, buffer, max);
|
|
}
|
|
|
|
/**
|
|
* Destructor function for this object.
|
|
* @param self Instance object.
|
|
*/
|
|
MX_INLINE void IObject_destruct(fatptr_t(IObject) obj)
|
|
{
|
|
return fatptr_vcall(obj, destruct);
|
|
}
|
|
|
|
#endif
|