Efl_Object: Add integration with Eina_Future.

This commit adds the EO support for the new future infra.
From now on there's no need to efl_future_link()/efl_future_unlink()
object and futures since the new API already handles that internally.
This commit is contained in:
Guilherme Iscaro 2017-08-25 19:53:15 -03:00
parent d30c0d4f03
commit 0dd2c1a530
3 changed files with 488 additions and 19 deletions

View File

@ -340,6 +340,239 @@ EOAPI Eina_Bool efl_event_callback_legacy_call(Eo *obj, const Efl_Event_Descript
*/
EOAPI Eina_Bool efl_future_link(Eo *obj, Efl_Future *link);
/**
* @struct _Efl_Future_Cb_Desc
*
* A struct with callbacks to be used by efl_future_cb_from_desc() and efl_future_chain_from_array()
*
* @see efl_future_cb_from_desc()
* @see efl_future_chain_from_array()
*/
typedef struct _Efl_Future_Cb_Desc {
/**
* Called on success (value.type is not @c EINA_VALUE_TYPE_ERROR).
*
* if @c success_type is not NULL, then the value is guaranteed to be of that type,
* if it's not, then it will trigger @c error with @c EINVAL.
*
* After this function returns, @c free callback is called if provided.
*
* @note This function is always called from a safe context (main loop or some platform defined safe context).
*
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_from_array().
* @param value The operation result
* @return An Eina_Value to pass to the next Eina_Future in the chain (if any).
* If there is no need to convert the received value, it's @b recommended
* to pass-thru @p value argument. If you need to convert to a different type
* or generate a new value, use @c eina_value_setup() on @b another Eina_Value
* and return it. By returning an promise Eina_Value (eina_promise_as_value()) the
* whole chain will wait until the promise is resolved in
* order to continue its execution.
* Note that the value contents must survive this function scope,
* that is, do @b not use stack allocated blobs, arrays, structures or types that
* keeps references to memory you give. Values will be automatically cleaned up
* using @c eina_value_flush() once they are unused (no more future or futures
* returned a new value).
*/
Eina_Value (*success)(Eo *o, const Eina_Value value);
/**
* Called on error (value.type is @c EINA_VALUE_TYPE_ERROR).
*
* This function can return another error, propagating or converting it. However it
* may also return a non-error, in this case the next future in chain will receive a regular
* value, which may call its @c success.
*
* If this function is not provided, then it will pass thru the error to the next error handler.
*
* It may be called with @c EINVAL if @c success_type is provided and doesn't
* match the received type.
*
* It may be called with @c ECANCELED if future was canceled.
*
* It may be called with @c ENOMEM if memory allocation failed during callback creation.
*
* After this function returns, @c free callback is called if provided.
*
* @note On future creation errors and future cancellation this function will be called
* from the current context with the following errors respectitally: `EINVAL`, `ENOMEM` and `ECANCELED`.
* Otherwise this function is called from a safe context.
*
*
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_from_array().
* @param error The operation error
* @return An Eina_Value to pass to the next Eina_Future in the chain (if any).
* If you need to convert to a different type or generate a new value,
* use @c eina_value_setup() on @b another Eina_Value
* and return it. By returning an promise Eina_Value (eina_promise_as_value()) the
* whole chain will wait until the promise is resolved in
* order to continue its execution.
* Note that the value contents must survive this function scope,
* that is, do @b not use stack allocated blobs, arrays, structures or types that
* keeps references to memory you give. Values will be automatically cleaned up
* using @c eina_value_flush() once they are unused (no more future or futures
* returned a new value).
*/
Eina_Value (*error)(Eo *o, Eina_Error error);
/**
* Called on @b all situations to notify future destruction.
*
* This is called after @c success or @c error, as well as it's called if none of them are
* provided. Thus can be used as a "weak ref" mechanism.
*
* @note On future creation errors and future cancellation this function will be called
* from the current context with the following errors respectitally: `EINVAL`, `ENOMEM` and `ECANCELED`.
* Otherwise this function is called from a safe context.
*
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_from_array().
* @param dead_future The future that was freed.
*/
void (*free)(Eo *o, const Eina_Future *dead_future);
/**
* If provided, then @c success will only be called if the value type matches the given pointer.
*
* If provided and doesn't match, then @c error will be called with @c EINVAL. If no @c error,
* then it will be propagated to the next future in the chain.
*/
const Eina_Value_Type *success_type;
/**
* This is used by Eo to cancel a pending futures in case
* an Eo object is deleted. It can be @c NULL.
*/
Eina_Future **storage;
} Efl_Future_Cb_Desc;
/**
* Creates an Eina_Future_Desc for an EO object.
*
* This function creates an Eina_Future_Desc based on an Efl_Future_Cb_Desc.
* The main purpose of this function is create a "link" between the future
* and the object. In case the object is deleted before the future is resolved/rejected,
* the object destructor will cancel the future.
*
* @note In case context info are needed for the #Efl_Future_Desc callbacks efl_key_data_set()
* can be used.
*
* The example below shows a file download using an Eo object, if the download
* lasts more than 30 seconds the Eo object will be deleted, causing the
* future to also be deleted.
* Usually this would be done with an eina_future_race() of the download promise and a timeout promise,
* however we provide the following example to illustrate efl_key_data_set() usage.
*
* @code
*
* static Eina_Bool
* _timeout(void *data)
* {
* Eo *downloader = data;
* //In case the download is not completed yet.
* //Delete the downloader (which in cancel the file download and the future)
* efl_key_data_set(downloader, "timer", NULL);
* efl_unref(downloader);
* return EINA_FALSE;
* }
*
* static Eina_Value
* _file_ok(Eo *o EINA_UNUSED, const Eina_Value value)
* {
* const char *data;
* //There's no need to check the value type since EO infra already did that for us
* eina_value_get(&value, &data);
* //Deliver the data to the user
* data_deliver(data);
* return v;
* }
*
* static Eina_Value
* _file_err(Eo *o EINA_UNUSED, Eina_Error error)
* {
* //In case the downloader is deleted before the future is resolved, the future will be canceled thus this callback will be called.
* fprintf(stderr, "Could not download the file. Reason: %s\n", eina_error_msg_get(error));
* return EINA_VALUE_EMPTY;
* }
*
* static void
* _downlader_free(Eo *o, const Eina_Future *dead_future EINA_UNUSED)
* {
* Ecore_Timer *t = efl_key_data_get(o, "timer");
* //The download was finished before the timer expired. Cancel it...
* if (t)
* {
* ecore_timer_del(t);
* efl_unref(o); //Delete the object
* } //else - In this case the future was canceled due efl_unref() in _timeout - No need to call efl_unref()
* }
*
* void download_file(const char *file)
* {
* //This could be rewritten using eina_future_race()
* Eo *downloader = efl_add(MY_DOWNLOADER_CLASS, NULL);
* Eina_Future *f = downloader_download_file(downloader, file);
* timer = ecore_timer_add(30, _timeout, downloader);
* //Usually this would be done with an eina_future_race() of the download promise and a timeout promise,
* //however we provide the following example to illustrate efl_key_data_set() usage.
* efl_key_data_set(downloader, "timer", timer);
* eina_future_then_from_desc(f, efl_future_cb(.success = _file_ok, .error = _file_err, .success_type = EINA_VALUE_TYPE_STRING, .free = downloader_free));
* }
* @endcode
*
* @param obj The object to create the link.
* @param desc An Efl_Future_Cb_Desc
* @return An Eina_Future_Desc to be used by eina_future_then(), eina_future_chain() and friends.
* @see efl_future_chain_from_array()
* @see efl_future_cb()
* @see #Efl_Future_Cb_Desc
* @see efl_key_data_set()
*/
EOAPI Eina_Future_Desc efl_future_cb_from_desc(Eo *obj, const Efl_Future_Cb_Desc desc);
/**
* Syntax suger over efl_future_cb_from_desc()
*
* Usage:
* @code
* eina_future_then_from_desc(future, efl_future_cb(my_object, .succes = success, .success_type = EINA_VALUE_TYPE_INT));
* @endcode
*
* @see efl_future_cb_from_desc()
*/
#define efl_future_cb(_eo, ...) efl_future_cb_from_desc(_eo, (Efl_Future_Cb_Desc){__VA_ARGS__})
/**
* Creates an Future chain based on #Efl_Future_Cb_Desc
*
* This function is an wrapper around efl_future_cb_from_desc() and eina_future_then_from_desc()
*
* For more information about them, check their documentations.
*
*
* @param obj An EO object to link against the future
* @param prev The previous future
* @param descs An array of Efl_Future_Cb_Desc
* @return An Eina_Future or @c NULL on error.
* @note If an error happens the whole future chain will be CANCELED, causing
* desc.error to be called passing `ENOMEM` or `EINVAL` and desc.free
* to free the @p obj if necessary.
*
* @see efl_future_chain()
* @see efl_future_cb()
* @see eina_future_then_from_desc()
* @see #Efl_Future_Cb_Desc
*/
EOAPI Eina_Future *efl_future_chain_from_array(Eo *obj, Eina_Future *prev, const Efl_Future_Cb_Desc descs[]);
/**
* Syntax suger over efl_future_chain_from_array()
*
* Usage:
* @code
* Eina_Future *f = efl_future_chain(my_object, prev_future, {}, {});
* @endcode
*
* @see efl_future_chain_from_array()
*/
#define efl_future_chain(_eo, _prev, ...) efl_future_chain_from_array(_eo, _prev, (Efl_Future_Cb_Desc []){__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}})
/**
* @addtogroup Eo_Debug_Information Eo's Debug information helper.
* @{
@ -1806,6 +2039,8 @@ efl_replace(Eo **storage, Eo *new_obj)
*storage = new_obj;
}
EOAPI extern const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT;
/**
* @}
*/

View File

@ -3182,3 +3182,98 @@ eo_objects_iterator_new(void)
return (Eina_Iterator *)it;
}
static Eina_Bool
_eo_value_setup(const Eina_Value_Type *type EINA_UNUSED, void *mem)
{
Eo **tmem = mem;
*tmem = NULL;
return EINA_TRUE;
}
static Eina_Bool
_eo_value_flush(const Eina_Value_Type *type EINA_UNUSED, void *mem)
{
Eo **tmem = mem;
if (*tmem)
{
efl_unref(*tmem);
*tmem = NULL;
}
return EINA_TRUE;
}
static void
_eo_value_replace(Eo **dst, Eo * const *src)
{
if (*src == *dst) return;
//ref *src first, since efl_unref(*dst) may trigger *src unref()
efl_ref(*src);
efl_unref(*dst);
*dst = *src;
}
static Eina_Bool
_eo_value_vset(const Eina_Value_Type *type EINA_UNUSED, void *mem, va_list args)
{
Eo **dst = mem;
Eo **src = va_arg(args, Eo **);
_eo_value_replace(dst, src);
return EINA_TRUE;
}
static Eina_Bool
_eo_value_pset(const Eina_Value_Type *type EINA_UNUSED,
void *mem, const void *ptr)
{
Eo **dst = mem;
Eo * const *src = ptr;
_eo_value_replace(dst, src);
return EINA_TRUE;
}
static Eina_Bool
_eo_value_pget(const Eina_Value_Type *type EINA_UNUSED,
const void *mem, void *ptr)
{
Eo * const *src = mem;
Eo **dst = ptr;
*dst = *src;
return EINA_TRUE;
}
static Eina_Bool
_eo_value_convert_to(const Eina_Value_Type *type EINA_UNUSED, const Eina_Value_Type *convert, const void *type_mem, void *convert_mem)
{
Eo * const *eo = type_mem;
if (convert == EINA_VALUE_TYPE_STRINGSHARE ||
convert == EINA_VALUE_TYPE_STRING)
{
const char *other_mem;
char buf[256];
snprintf(buf, sizeof(buf), "Object id: %p, class: %s, name: %s",
*eo, efl_class_name_get(efl_class_get(*eo)),
efl_debug_name_get(*eo));
other_mem = buf;
return eina_value_type_pset(convert, convert_mem, &other_mem);
}
return EINA_FALSE;
}
static const Eina_Value_Type _EINA_VALUE_TYPE_OBJECT = {
.version = EINA_VALUE_TYPE_VERSION,
.value_size = sizeof(Eo *),
.name = "Efl_Object",
.setup = _eo_value_setup,
.flush = _eo_value_flush,
.copy = NULL,
.compare = NULL,
.convert_to = _eo_value_convert_to,
.convert_from = NULL,
.vset = _eo_value_vset,
.pset = _eo_value_pset,
.pget = _eo_value_pget
};
EOAPI const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT = &_EINA_VALUE_TYPE_OBJECT;

View File

@ -8,6 +8,7 @@
#include "Eo.h"
#include "eo_ptr_indirection.h"
#include "eo_private.h"
#include "eina_promise_private.h"
#define EFL_EVENT_SPECIAL_SKIP 1
@ -45,6 +46,7 @@ typedef struct
Efl_Event_Callback_Frame *event_frame;
Eo_Callback_Description **callbacks;
Eina_Inlist *pending_futures;
unsigned int callbacks_count;
unsigned short event_freeze_count;
@ -79,6 +81,15 @@ typedef struct
Eo_Generic_Data_Node_Type d_type;
} Eo_Generic_Data_Node;
typedef struct _Efl_Future_Pending
{
EINA_INLIST;
Eo *o;
Eina_Future *future;
Efl_Future_Cb_Desc desc;
} Efl_Future_Pending;
typedef struct
{
EINA_INLIST;
@ -972,48 +983,77 @@ struct _Eo_Callback_Description
static int _eo_callbacks = 0;
static Eina_Mempool *_eo_callback_mempool = NULL;
static int _efl_pending_futures = 0;
static Eina_Mempool *_efl_pending_future_mempool = NULL;
static void
_eo_callback_free(Eo_Callback_Description *cb)
_mempool_data_free(Eina_Mempool **mp, int *usage, void *data)
{
if (!cb) return;
eina_mempool_free(_eo_callback_mempool, cb);
_eo_callbacks--;
if (_eo_callbacks == 0)
if (!data) return;
eina_mempool_free(*mp, data);
(*usage)--;
if (*usage == 0)
{
eina_mempool_del(_eo_callback_mempool);
_eo_callback_mempool = NULL;
eina_mempool_del(*mp);
*mp = NULL;
}
}
static Eo_Callback_Description *
_eo_callback_new(void)
static void *
_mempool_data_alloc(Eina_Mempool **mp, int *usage, size_t size)
{
Eo_Callback_Description *cb;
// very unlikely that the mempool isnt initted, so take all the init code
// and move it out of l1 instruction cache space so we dont pollute the
// l1 cache with unused code 99% of the time
if (!_eo_callback_mempool) goto init_mempool;
if (!*mp) goto init_mempool;
init_mempool_back:
cb = eina_mempool_calloc(_eo_callback_mempool,
sizeof(Eo_Callback_Description));
cb = eina_mempool_calloc(*mp, size);
if (cb)
{
_eo_callbacks++;
(*usage)++;
return cb;
}
if (_eo_callbacks != 0) return NULL;
eina_mempool_del(_eo_callback_mempool);
_eo_callback_mempool = NULL;
if (*usage != 0) return NULL;
eina_mempool_del(*mp);
*mp = NULL;
return NULL;
init_mempool:
_eo_callback_mempool = eina_mempool_add
("chained_mempool", NULL, NULL, sizeof(Eo_Callback_Description), 256);
if (!_eo_callback_mempool) return NULL;
*mp = eina_mempool_add
("chained_mempool", NULL, NULL, size, 256);
if (!*mp) return NULL;
goto init_mempool_back;
}
static void
_eo_callback_free(Eo_Callback_Description *cb)
{
_mempool_data_free(&_eo_callback_mempool, &_eo_callbacks, cb);
}
static Eo_Callback_Description *
_eo_callback_new(void)
{
return _mempool_data_alloc(&_eo_callback_mempool, &_eo_callbacks,
sizeof(Eo_Callback_Description));
}
static void
_efl_pending_future_free(Efl_Future_Pending *pending)
{
_mempool_data_free(&_efl_pending_future_mempool,
&_efl_pending_futures, pending);
}
static Efl_Future_Pending *
_efl_pending_future_new(void)
{
return _mempool_data_alloc(&_efl_pending_future_mempool,
&_efl_pending_futures,
sizeof(Efl_Future_Pending));
}
#ifdef EFL_EVENT_SPECIAL_SKIP
#define CB_COUNT_INC(cnt) do { if ((cnt) != 0xffff) (cnt)++; } while(0)
@ -1858,6 +1898,104 @@ EAPI const Eina_Value_Type *EFL_DBG_INFO_TYPE = &_EFL_DBG_INFO_TYPE;
/* EFL_OBJECT_CLASS stuff */
#define MY_CLASS EFL_OBJECT_CLASS
static void
_efl_pending_futures_clear(Efl_Object_Data *pd)
{
while (pd->pending_futures)
{
Efl_Future_Pending *pending = EINA_INLIST_CONTAINER_GET(pd->pending_futures, Efl_Future_Pending);
Eina_Future *future = *pending->desc.storage;
assert(future);
eina_future_cancel(future);
}
}
static Eina_Value
_efl_future_cb(void *data, const Eina_Value value, const Eina_Future *dead_future)
{
Efl_Future_Pending *pending = data;
Eina_Value ret = value;
Eo *o;
Efl_Object_Data *pd;
EINA_SAFETY_ON_NULL_GOTO(pending, err);
o = pending->o;
pd = efl_data_scope_get(o, EFL_OBJECT_CLASS);
EINA_SAFETY_ON_NULL_GOTO(pd, err);
pd->pending_futures = eina_inlist_remove(pd->pending_futures,
EINA_INLIST_GET(pending));
efl_ref(o);
EASY_FUTURE_DISPATCH(ret, value, dead_future, &pending->desc, o);
efl_unref(o);
_efl_pending_future_free(pending);
return ret;
err:
eina_value_setup(&ret, EINA_VALUE_TYPE_ERROR);
eina_value_set(&ret, ENOMEM);
return ret;
}
EOAPI Eina_Future_Desc
efl_future_cb_from_desc(Eo *o, const Efl_Future_Cb_Desc desc)
{
Efl_Future_Pending *pending = NULL;
Eina_Future **storage = NULL;
Efl_Object_Data *pd;
EINA_SAFETY_ON_NULL_GOTO(o, end);
pd = efl_data_scope_get(o, EFL_OBJECT_CLASS);
EINA_SAFETY_ON_NULL_GOTO(pd, end);
pending = _efl_pending_future_new();
EINA_SAFETY_ON_NULL_GOTO(pending, end);
memcpy(&pending->desc, &desc, sizeof(Efl_Future_Cb_Desc));
pending->o = o;
pending->future = NULL;
if (!pending->desc.storage) pending->desc.storage = &pending->future;
pd->pending_futures = eina_inlist_append(pd->pending_futures,
EINA_INLIST_GET(pending));
storage = pending->desc.storage;
end:
return (Eina_Future_Desc){ .cb = _efl_future_cb, .data = pending, .storage = storage };
}
EOAPI Eina_Future *
efl_future_chain_from_array(Eo *obj,
Eina_Future *prev,
const Efl_Future_Cb_Desc descs[])
{
size_t i = -1;
Eina_Future *f = prev;
for (i = 0; descs[i].success || descs[i].error || descs[i].free || descs[i].success_type; i++)
{
Eina_Future_Desc eina_desc = efl_future_cb_from_desc(obj, descs[i]);
f = eina_future_then_from_desc(f, eina_desc);
EINA_SAFETY_ON_NULL_GOTO(f, err);
}
return f;
err:
/*
There's no need to cancel the futures, since eina_future_then_from_desc()
will cancel the whole chain in case of failure.
All we need to do is to free the remaining descs
*/
for (i = i + 1; descs[i].error || descs[i].free; i++)
{
if (descs[i].error)
{
Eina_Value r = descs[i].error(obj, ENOMEM);
if (r.type) eina_value_flush(&r);
}
if (descs[i].free) descs[i].free(obj, NULL);
}
return NULL;
}
EOLIAN static Eo *
_efl_object_constructor(Eo *obj, Efl_Object_Data *pd EINA_UNUSED)
{
@ -1905,6 +2043,7 @@ composite_obj_back:
if (pd->parent) goto err_parent;
err_parent_back:
_efl_pending_futures_clear(pd);
_eo_generic_data_del_all(obj, pd);
_wref_destruct(pd);
_eo_callback_remove_all(pd);