Eo: Implement eo_override() to enable overriding functions of objects.

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
This commit is contained in:
Tom Hacohen 2016-05-19 11:33:17 +01:00
parent c450efdcde
commit 06f65ab2b1
6 changed files with 214 additions and 44 deletions

View 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)

View 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
@ -385,6 +386,18 @@ typedef struct _Eo_Op_Description
Eo_Op_Type op_type; /**< The type of the Op. */
} 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.
@ -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. */
@ -426,6 +436,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
@ -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

View 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 EO_CALL_CACHE_SIZE > 1
int i;
for (i = 0; i < EO_CALL_CACHE_SIZE; i++)
# else
const int i = 0;
# endif
if (!is_override)
{
if ((const void *)inputklass == cache->index[i].klass)
# if EO_CALL_CACHE_SIZE > 1
int i;
for (i = 0; i < EO_CALL_CACHE_SIZE; i++)
# else
const int i = 0;
# endif
{
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;
@ -798,6 +834,13 @@ eo_class_name_get(const Eo_Class *eo_id)
return klass->desc->name;
}
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)
{
@ -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;
}
@ -1301,6 +1343,25 @@ eo_class_new(const Eo_Class_Description *desc, const Eo_Class *parent_id, ...)
return _eo_class_id_get(klass);
}
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)
{

View File

@ -0,0 +1,4 @@
abstract Eo.Override ()
{
data: null;
}

View 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);

View 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);