Efl_Object: Add integration with Eina_Future.
authorGuilherme Iscaro <iscaro@profusion.mobi>
Fri, 25 Aug 2017 22:53:15 +0000 (19:53 -0300)
committerGuilherme Iscaro <iscaro@profusion.mobi>
Mon, 4 Sep 2017 13:24:00 +0000 (10:24 -0300)
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.

src/lib/eo/Eo.h
src/lib/eo/eo.c
src/lib/eo/eo_base_class.c

index 3757392..f548381 100644 (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_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.
  * @{
@@ -1806,6 +2039,8 @@ efl_replace(Eo **storage, Eo *new_obj)
    *storage = new_obj;
 }
 
+EOAPI extern const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT;
+
 /**
  * @}
  */
index e47778e..489d042 100644 (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;
index da33114..60ceaf6 100644 (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_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)
 {
@@ -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);