Eo: Implement eo_override() to enable overriding functions of objects.
authorTom Hacohen <tom@stosb.com>
Thu, 19 May 2016 10:33:17 +0000 (11:33 +0100)
committerTom Hacohen <tom@stosb.com>
Fri, 20 May 2016 09:25:00 +0000 (10:25 +0100)
This change lets you override the functions of objects so that those
functions will be called instead of the functions of the class. This
lets you change objects on the fly and makes using the delegate pattern
easier (no need to create a class every time anymore).
You can see the newly added tests (in this commit) for usage examples.

@feature

src/Makefile_Eo.am
src/lib/eo/Eo.h
src/lib/eo/eo.c
src/lib/eo/eo_override.eo [new file with mode: 0644]
src/lib/eo/eo_private.h
src/tests/eo/suite/eo_test_general.c

index 69c4466..6cf0b3a 100644 (file)
@@ -4,6 +4,7 @@
 eo_eolian_files = \
        lib/eo/eo_base.eo \
        lib/eo/eo_class.eo \
+       lib/eo/eo_override.eo \
        lib/eo/eo_interface.eo
 
 eo_eolian_c = $(eo_eolian_files:%.eo=%.eo.c)
index 566720f..a241d4f 100644 (file)
@@ -169,6 +169,7 @@ typedef enum _Eo_Op_Type Eo_Op_Type;
  */
 typedef void (*Eo_Del_Intercept) (Eo *obj_id);
 
+#include "eo_override.eo.h"
 #include "eo_base.eo.h"
 #define EO_CLASS EO_BASE_CLASS
 
@@ -386,6 +387,18 @@ typedef struct _Eo_Op_Description
 } Eo_Op_Description;
 
 /**
+ * @struct _Eo_Ops
+ *
+ * This struct holds the ops and the size of the ops.
+ * Please use the #EO_CLASS_DESCRIPTION_OPS macro when populating it.
+ */
+typedef struct _Eo_Ops
+{
+   const Eo_Op_Description *descs; /**< The op descriptions array of size count. */
+   size_t count; /**< Number of op descriptions. */
+} Eo_Ops;
+
+/**
  * @struct _Eo_Class_Description
  * This struct holds the description of a class.
  * This description should be passed to eo_class_new.
@@ -396,10 +409,7 @@ struct _Eo_Class_Description
    unsigned int version; /**< The current version of eo, use #EO_VERSION */
    const char *name; /**< The name of the class. */
    Eo_Class_Type type; /**< The type of the class. */
-   struct {
-        const Eo_Op_Description *descs; /**< The op descriptions array of size count. */
-        size_t count; /**< Number of op descriptions. */
-   } ops; /**< The ops description, should be filled using #EO_CLASS_DESCRIPTION_OPS (later sorted by Eo). */
+   Eo_Ops ops;  /**< The ops description, should be filled using #EO_CLASS_DESCRIPTION_OPS (later sorted by Eo). */
    const Eo_Event_Description **events; /**< The event descriptions for this class. */
    size_t data_size; /**< The size of data (private + protected + public) this class needs per object. */
    void (*class_constructor)(Eo_Class *klass); /**< The constructor of the class. */
@@ -427,6 +437,21 @@ typedef struct _Eo_Class_Description Eo_Class_Description;
 EAPI const Eo_Class *eo_class_new(const Eo_Class_Description *desc, const Eo_Class *parent, ...);
 
 /**
+ * @brief Override Eo functions of this object.
+ * @param ops The op description to override with.
+ * @return true on success, false otherwise.
+ *
+ * This lets you override all of the Eo functions of this object (this
+ * one included) and repalce them with ad-hoc implementation.
+ * The contents of the array are copied so they can for example reside
+ * on the stack.
+ *
+ * You are only allowed to override functions that are defined in the
+ * class or any of its interfaces (that is, eo_isa returning true).
+ */
+EAPI Eina_Bool eo_override(Eo *obj, Eo_Ops ops);
+
+/**
  * @brief Check if an object "is a" klass.
  * @param obj The object to check
  * @param klass The klass to check against.
@@ -466,8 +491,8 @@ EAPI Eina_Bool eo_init(void);
 EAPI Eina_Bool eo_shutdown(void);
 
 // Helpers macro to help populating #Eo_Class_Description.
-#define EO_CLASS_DESCRIPTION_NOOPS() { NULL, 0}
-#define EO_CLASS_DESCRIPTION_OPS(op_descs) { op_descs, EINA_C_ARRAY_LENGTH(op_descs) }
+#define EO_CLASS_DESCRIPTION_NOOPS() ((Eo_Ops) { NULL, 0})
+#define EO_CLASS_DESCRIPTION_OPS(op_descs) ((Eo_Ops) { op_descs, EINA_C_ARRAY_LENGTH(op_descs) })
 
 // to fetch internal function and object data at once
 typedef struct _Eo_Op_Call_Data
index 08de539..cef7bb7 100644 (file)
@@ -15,6 +15,8 @@
 #include "eo_private.h"
 #include "eo_add_fallback.h"
 
+#include "eo_override.eo.c"
+
 #define EO_CLASS_IDS_FIRST 1
 #define EO_OP_IDS_FIRST 1
 
@@ -130,11 +132,11 @@ _eo_op_class_get(Eo_Op op)
 }
 
 static inline Eina_Bool
-_vtable_func_set(_Eo_Class *klass, Eo_Op op, eo_op_func_type func)
+_vtable_func_set(Eo_Vtable *vtable, const _Eo_Class *klass, Eo_Op op, eo_op_func_type func)
 {
    op_type_funcs *fsrc;
    size_t idx1 = DICH_CHAIN1(op);
-   Dich_Chain1 *chain1 = &klass->vtable.chain[idx1];
+   Dich_Chain1 *chain1 = &vtable->chain[idx1];
    _vtable_chain_alloc(chain1);
    fsrc = &chain1->funcs[DICH_CHAIN_LAST(op)];
    if (fsrc->src == klass)
@@ -151,19 +153,19 @@ _vtable_func_set(_Eo_Class *klass, Eo_Op op, eo_op_func_type func)
    return EINA_TRUE;
 }
 
-static inline void
-_vtable_func_clean_all(_Eo_Class *klass)
+void
+_vtable_func_clean_all(Eo_Vtable *vtable)
 {
    size_t i;
-   Dich_Chain1 *chain1 = klass->vtable.chain;
+   Dich_Chain1 *chain1 = vtable->chain;
 
-   for (i = 0 ; i < klass->vtable.size ; i++, chain1++)
+   for (i = 0 ; i < vtable->size ; i++, chain1++)
      {
         if (chain1->funcs)
            free(chain1->funcs);
      }
-   free(klass->vtable.chain);
-   klass->vtable.chain = NULL;
+   free(vtable->chain);
+   vtable->chain = NULL;
 }
 
 /* END OF DICH */
@@ -270,8 +272,10 @@ _eo_call_resolve(Eo *eo_id, const char *func_name, Eo_Op_Call_Data *call, Eo_Cal
    const _Eo_Class *klass, *inputklass, *main_klass;
    const _Eo_Class *cur_klass = NULL;
    _Eo_Object *obj = NULL;
+   const Eo_Vtable *vtable = NULL;
    const op_type_funcs *func;
    Eina_Bool is_obj;
+   Eina_Bool is_override = EINA_FALSE;
 
    if (((Eo_Id) eo_id) & MASK_SUPER_TAG)
      {
@@ -301,6 +305,19 @@ _eo_call_resolve(Eo *eo_id, const char *func_name, Eo_Op_Call_Data *call, Eo_Cal
         EO_OBJ_POINTER_RETURN_VAL(eo_id, _obj, EINA_FALSE);
         obj = _obj;
         klass = _obj->klass;
+        vtable = obj->vtable;
+
+        if (_obj_is_override(obj) && cur_klass &&
+              (_eo_class_id_get(cur_klass) == EO_OVERRIDE_CLASS))
+          {
+             /* Doing a eo_super(obj, EO_OVERRIDE_CLASS) should result in calling
+              * as if it's a normal class. */
+             vtable = &klass->vtable;
+             cur_klass = NULL;
+          }
+
+        is_override = _obj_is_override(obj) && (cur_klass == NULL);
+
         call->obj = obj;
         _eo_ref(_obj);
      }
@@ -308,6 +325,7 @@ _eo_call_resolve(Eo *eo_id, const char *func_name, Eo_Op_Call_Data *call, Eo_Cal
      {
         EO_CLASS_POINTER_RETURN_VAL(eo_id, _klass, EINA_FALSE);
         klass = _klass;
+        vtable = &klass->vtable;
         call->obj = NULL;
         call->data = NULL;
      }
@@ -337,28 +355,31 @@ _eo_call_resolve(Eo *eo_id, const char *func_name, Eo_Op_Call_Data *call, Eo_Cal
    else
      {
 # if EO_CALL_CACHE_SIZE > 0
+        if (!is_override)
+          {
 # if EO_CALL_CACHE_SIZE > 1
-        int i;
+             int i;
 
-        for (i = 0; i < EO_CALL_CACHE_SIZE; i++)
+             for (i = 0; i < EO_CALL_CACHE_SIZE; i++)
 # else
-        const int i = 0;
+                const int i = 0;
 # endif
-          {
-             if ((const void *)inputklass == cache->index[i].klass)
                {
-                  func = (const op_type_funcs *)cache->entry[i].func;
-                  call->func = func->func;
-                  if (is_obj)
+                  if ((const void *)inputklass == cache->index[i].klass)
                     {
-                       call->data = (char *) obj + cache->off[i].off;
+                       func = (const op_type_funcs *)cache->entry[i].func;
+                       call->func = func->func;
+                       if (is_obj)
+                         {
+                            call->data = (char *) obj + cache->off[i].off;
+                         }
+                       return EINA_TRUE;
                     }
-                  return EINA_TRUE;
                }
           }
 #endif
 
-        func = _vtable_func_get(&klass->vtable, cache->op);
+        func = _vtable_func_get(vtable, cache->op);
 
         if (!func)
           goto end;
@@ -374,7 +395,7 @@ _eo_call_resolve(Eo *eo_id, const char *func_name, Eo_Op_Call_Data *call, Eo_Cal
           }
 
 # if EO_CALL_CACHE_SIZE > 0
-        if (!cur_klass)
+        if (!cur_klass && !is_override)
           {
 # if EO_CALL_CACHE_SIZE > 1
              const int slot = cache->next_slot;
@@ -535,8 +556,10 @@ _eo_api_op_id_get(const void *api_func)
    return op;
 }
 
+/* klass is the klass we are working on. hierarchy_klass is the class whe should
+ * use when validating. */
 static Eina_Bool
-_eo_class_funcs_set(_Eo_Class *klass)
+_eo_class_funcs_set(Eo_Vtable *vtable, const Eo_Ops *ops, const _Eo_Class *hierarchy_klass, const _Eo_Class *klass, Eina_Bool override_only)
 {
    unsigned int i;
    int op_id;
@@ -544,15 +567,15 @@ _eo_class_funcs_set(_Eo_Class *klass)
    const Eo_Op_Description *op_desc;
    const Eo_Op_Description *op_descs;
 
-   op_id = klass->base_id;
-   op_descs = klass->desc->ops.descs;
+   op_id = hierarchy_klass->base_id;
+   op_descs = ops->descs;
 
    DBG("Set functions for class '%s':%p", klass->desc->name, klass);
 
    if (!op_descs) return EINA_TRUE;
 
    last_api_func = NULL;
-   for (i = 0, op_desc = op_descs; i < klass->desc->ops.count; i++, op_desc++)
+   for (i = 0, op_desc = op_descs; i < ops->count; i++, op_desc++)
      {
         Eo_Op op = EO_NOOP;
 
@@ -565,6 +588,12 @@ _eo_class_funcs_set(_Eo_Class *klass)
 
         if ((op_desc->op_type == EO_OP_TYPE_REGULAR) || (op_desc->op_type == EO_OP_TYPE_CLASS))
           {
+             if (override_only)
+               {
+                  ERR("Creation of new functions is not allowed when overriding an object's vtable.");
+                  return EINA_FALSE;
+               }
+
              if (_eo_api_func_equal(op_desc->api_func, last_api_func))
                {
                   ERR("Class '%s': API previously defined (%p->%p '%s').",
@@ -586,12 +615,19 @@ _eo_class_funcs_set(_Eo_Class *klass)
         else if ((op_desc->op_type == EO_OP_TYPE_REGULAR_OVERRIDE) || (op_desc->op_type == EO_OP_TYPE_CLASS_OVERRIDE))
           {
              const Eo_Op_Description *api_desc;
-             api_desc = _eo_api_desc_get(op_desc->api_func, klass->parent, klass->extensions);
+             if (override_only)
+               {
+                  api_desc = _eo_api_desc_get(op_desc->api_func, hierarchy_klass, NULL);
+               }
+             else
+               {
+                  api_desc = _eo_api_desc_get(op_desc->api_func, hierarchy_klass->parent, hierarchy_klass->extensions);
+               }
 
              if (api_desc == NULL)
                {
                   ERR("Class '%s': Can't find api func description in class hierarchy (%p->%p) (%s).",
-                      klass->desc->name, op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
+                      hierarchy_klass->desc->name, op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
                   return EINA_FALSE;
                }
 
@@ -607,7 +643,7 @@ _eo_class_funcs_set(_Eo_Class *klass)
 
         DBG("%p->%p '%s'", op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
 
-        if (!_vtable_func_set(klass, op, op_desc->func))
+        if (!_vtable_func_set(vtable, klass, op, op_desc->func))
           return EINA_FALSE;
 
         last_api_func = op_desc->api_func;
@@ -799,6 +835,13 @@ eo_class_name_get(const Eo_Class *eo_id)
 }
 
 static void
+_vtable_init(Eo_Vtable *vtable, size_t size)
+{
+   vtable->size = size;
+   vtable->chain = calloc(vtable->size, sizeof(vtable->chain));
+}
+
+static void
 _eo_class_base_op_init(_Eo_Class *klass)
 {
    const Eo_Class_Description *desc = klass->desc;
@@ -807,8 +850,7 @@ _eo_class_base_op_init(_Eo_Class *klass)
 
    _eo_ops_last_id += desc->ops.count + 1;
 
-   klass->vtable.size = DICH_CHAIN1(_eo_ops_last_id) + 1;
-   klass->vtable.chain = calloc(klass->vtable.size, sizeof(*klass->vtable.chain));
+   _vtable_init(&klass->vtable, DICH_CHAIN1(_eo_ops_last_id) + 1);
 }
 
 #ifdef EO_DEBUG
@@ -951,7 +993,7 @@ eo_class_free(_Eo_Class *klass)
         if (klass->desc->class_destructor)
            klass->desc->class_destructor(_eo_class_id_get(klass));
 
-        _vtable_func_clean_all(klass);
+        _vtable_func_clean_all(&klass->vtable);
      }
 
    EINA_TRASH_CLEAN(&klass->objects.trash, data)
@@ -1258,26 +1300,26 @@ eo_class_new(const Eo_Class_Description *desc, const Eo_Class *parent_id, ...)
           {
              const _Eo_Class *extn = *extn_itr;
              /* Set it in the dich. */
-             _vtable_func_set(klass, extn->base_id +
+             _vtable_func_set(&klass->vtable, klass, extn->base_id +
                    extn->desc->ops.count, _eo_class_isa_func);
           }
 
-        _vtable_func_set(klass, klass->base_id + klass->desc->ops.count,
+        _vtable_func_set(&klass->vtable, klass, klass->base_id + klass->desc->ops.count,
               _eo_class_isa_func);
 
         if (klass->parent)
           {
-             _vtable_func_set(klass,
+             _vtable_func_set(&klass->vtable, klass,
                    klass->parent->base_id + klass->parent->desc->ops.count,
                    _eo_class_isa_func);
           }
      }
 
-   if (!_eo_class_funcs_set(klass))
+   if (!_eo_class_funcs_set(&klass->vtable, &(klass->desc->ops), klass, klass, EINA_FALSE))
      {
         eina_spinlock_free(&klass->objects.trash_lock);
         eina_spinlock_free(&klass->iterators.trash_lock);
-        _vtable_func_clean_all(klass);
+        _vtable_func_clean_all(&klass->vtable);
         free(klass);
         return NULL;
      }
@@ -1302,6 +1344,25 @@ eo_class_new(const Eo_Class_Description *desc, const Eo_Class *parent_id, ...)
 }
 
 EAPI Eina_Bool
+eo_override(Eo *eo_id, Eo_Ops ops)
+{
+   EO_OBJ_POINTER_RETURN_VAL(eo_id, obj, EINA_FALSE);
+   EO_CLASS_POINTER_RETURN_VAL(EO_OVERRIDE_CLASS, klass, EINA_FALSE);
+   Eo_Vtable *previous = obj->vtable;
+   obj->vtable = calloc(1, sizeof(*obj->vtable));
+   _vtable_init(obj->vtable, previous->size);
+   _vtable_copy_all(obj->vtable, previous);
+
+   if (!_eo_class_funcs_set(obj->vtable, &ops, obj->klass, klass, EINA_TRUE))
+     {
+        ERR("Failed to override functions for %p", eo_id);
+        return EINA_FALSE;
+     }
+
+   return EINA_TRUE;
+}
+
+EAPI Eina_Bool
 eo_isa(const Eo *eo_id, const Eo_Class *klass_id)
 {
    EO_OBJ_POINTER_RETURN_VAL(eo_id, obj, EINA_FALSE);
diff --git a/src/lib/eo/eo_override.eo b/src/lib/eo/eo_override.eo
new file mode 100644 (file)
index 0000000..77cc05f
--- /dev/null
@@ -0,0 +1,4 @@
+abstract Eo.Override ()
+{
+    data: null;
+}
index 62f9b73..dd5176c 100644 (file)
@@ -85,6 +85,9 @@ typedef struct _Eo_Vtable
    unsigned int size;
 } Eo_Vtable;
 
+/* Clean the vtable. */
+void _vtable_func_clean_all(Eo_Vtable *vtable);
+
 struct _Eo_Header
 {
 #ifndef HAVE_EO_ID
@@ -249,6 +252,12 @@ _eo_del_internal(const char *file, int line, _Eo_Object *obj)
    obj->refcount--;
 }
 
+static inline Eina_Bool
+_obj_is_override(_Eo_Object *obj)
+{
+   return (obj->vtable != &obj->klass->vtable);
+}
+
 static inline void
 _eo_free(_Eo_Object *obj)
 {
@@ -260,6 +269,13 @@ _eo_free(_Eo_Object *obj)
         ERR("Object %p data still referenced %d time(s).", obj, obj->datarefcount);
      }
 #endif
+   if (_obj_is_override(obj))
+     {
+        _vtable_func_clean_all(obj->vtable);
+        free(obj->vtable);
+        obj->vtable = &klass->vtable;
+     }
+
    _eo_id_release((Eo_Id) _eo_obj_id_get(obj));
 
    eina_spinlock_take(&klass->objects.trash_lock);
index 65d9ae2..1ae539d 100644 (file)
@@ -50,6 +50,68 @@ START_TEST(eo_singleton)
 }
 END_TEST
 
+#define OVERRIDE_A_SIMPLE 100859
+#define OVERRIDE_A 324000
+static int
+_simple_obj_override_a_get(Eo *obj, void *class_data EINA_UNUSED)
+{
+   return OVERRIDE_A + simple_a_get(eo_super(obj, EO_OVERRIDE_CLASS));
+}
+
+static void
+_simple_obj_override_a_double_set(Eo *obj, void *class_data EINA_UNUSED, int a)
+{
+   simple_a_set(eo_super(obj, EO_OVERRIDE_CLASS), 2 * a);
+}
+
+START_TEST(eo_override_tests)
+{
+   eo_init();
+
+   Eo_Op_Description override_descs[] = {
+        EO_OP_FUNC_OVERRIDE(simple_a_get, _simple_obj_override_a_get),
+   };
+
+   Eo *obj = eo_add(SIMPLE_CLASS, NULL);
+   fail_if(!obj);
+
+   /* First get the value before the override to make sure it works and to
+    * make sure we don't cache. */
+   ck_assert_int_eq(simple_a_get(obj), 0);
+
+   fail_if(!eo_override(obj, EO_CLASS_DESCRIPTION_OPS(override_descs)));
+
+   ck_assert_int_eq(simple_a_get(obj), OVERRIDE_A);
+
+   /* Check super works. */
+   simple_a_set(obj, OVERRIDE_A_SIMPLE);
+   ck_assert_int_eq(simple_a_get(obj), OVERRIDE_A + OVERRIDE_A_SIMPLE);
+
+
+   /* Override again. */
+   Eo_Op_Description override_descs2[] = {
+        EO_OP_FUNC_OVERRIDE(simple_a_set, _simple_obj_override_a_double_set),
+   };
+
+   fail_if(!eo_override(obj, EO_CLASS_DESCRIPTION_OPS(override_descs2)));
+
+   simple_a_set(obj, OVERRIDE_A_SIMPLE);
+   ck_assert_int_eq(simple_a_get(obj), OVERRIDE_A + (OVERRIDE_A_SIMPLE * 2));
+
+
+   /* Try introducing a new function */
+   Eo_Op_Description override_descs3[] = {
+        EO_OP_FUNC(simple2_class_beef_get, _simple_obj_override_a_double_set),
+   };
+
+   fail_if(eo_override(obj, (Eo_Ops) EO_CLASS_DESCRIPTION_OPS(override_descs3)));
+
+   eo_unref(obj);
+
+   eo_shutdown();
+}
+END_TEST
+
 static int _eo_signals_cb_current = 0;
 static int _eo_signals_cb_flag = 0;
 
@@ -1178,6 +1240,7 @@ void eo_test_general(TCase *tc)
 {
    tcase_add_test(tc, eo_simple);
    tcase_add_test(tc, eo_singleton);
+   tcase_add_test(tc, eo_override_tests);
    tcase_add_test(tc, eo_signals);
    tcase_add_test(tc, eo_data_fetch);
    tcase_add_test(tc, eo_isa_tests);