*/
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_array()
+ *
+ * @see efl_future_cb_from_desc()
+ * @see efl_future_chain_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_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_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_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_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) EINA_ARG_NONNULL(1);
+
+/**
+ * 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_array(Eo *obj, Eina_Future *prev, const Efl_Future_Cb_Desc descs[]) EINA_ARG_NONNULL(1, 2);
+
+/**
+ * Syntax suger over efl_future_chain_array()
+ *
+ * Usage:
+ * @code
+ * Eina_Future *f = efl_future_chain(my_object, prev_future, {}, {});
+ * @endcode
+ *
+ * @see efl_future_chain_array()
+ */
+#define efl_future_chain(_eo, _prev, ...) efl_future_chain_array(_eo, _prev, (Efl_Future_Cb_Desc []){__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}})
+
/**
* @addtogroup Eo_Debug_Information Eo's Debug information helper.
* @{
*storage = new_obj;
}
+EOAPI extern const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT;
+
/**
* @}
*/
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;
#include "Eo.h"
#include "eo_ptr_indirection.h"
#include "eo_private.h"
+#include "eina_promise_private.h"
#define EFL_EVENT_SPECIAL_SKIP 1
Efl_Event_Callback_Frame *event_frame;
Eo_Callback_Description **callbacks;
+ Eina_Inlist *pending_futures;
unsigned int callbacks_count;
unsigned short event_freeze_count;
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;
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)
/* 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_array(Eo *obj,
+ Eina_Future *prev,
+ const Efl_Future_Cb_Desc descs[])
+{
+ ssize_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);
+ 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)
{
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);