diff options
author | Guilherme Iscaro <iscaro@profusion.mobi> | 2017-08-08 18:10:36 -0300 |
---|---|---|
committer | Guilherme Iscaro <iscaro@profusion.mobi> | 2017-09-04 10:24:00 -0300 |
commit | 5bd8c9a78d7bc21a956b775fb250e8e14711d5b9 (patch) | |
tree | 5a3b270ecd53a712b66ad32e2989091250418423 | |
parent | c9a0237770a7fb0f1d94c9f99b7cab68399a922f (diff) | |
download | efl-5bd8c9a78d7bc21a956b775fb250e8e14711d5b9.tar.gz |
Eina: Add Eina_Promise/Eina_Future.
This commit adds a new promise/future API which aims to replace
efl_future.
-rw-r--r-- | src/Makefile_Eina.am | 3 | ||||
-rw-r--r-- | src/lib/ecore/ecore.c | 1 | ||||
-rw-r--r-- | src/lib/ecore/ecore_events.c | 111 | ||||
-rw-r--r-- | src/lib/ecore/ecore_main.c | 7 | ||||
-rw-r--r-- | src/lib/ecore/ecore_private.h | 4 | ||||
-rw-r--r-- | src/lib/ecore/efl_loop.eo | 13 | ||||
-rw-r--r-- | src/lib/eina/Eina.h | 1 | ||||
-rw-r--r-- | src/lib/eina/eina_main.c | 2 | ||||
-rw-r--r-- | src/lib/eina/eina_promise.c | 1355 | ||||
-rw-r--r-- | src/lib/eina/eina_promise.h | 1674 | ||||
-rw-r--r-- | src/lib/eina/eina_promise_private.h | 38 | ||||
-rw-r--r-- | src/lib/eo/eina_types.eot | 81 |
12 files changed, 3290 insertions, 0 deletions
diff --git a/src/Makefile_Eina.am b/src/Makefile_Eina.am index 544ce0feda..bd74cdcb64 100644 --- a/src/Makefile_Eina.am +++ b/src/Makefile_Eina.am @@ -10,6 +10,7 @@ lib/eina/eina_config.h installed_einaheadersdir = $(includedir)/eina-@VMAJ@/eina dist_installed_einaheaders_DATA = \ +lib/eina/eina_promise.h \ lib/eina/eina_safety_checks.h \ lib/eina/eina_error.h \ lib/eina/eina_debug.h \ @@ -150,6 +151,8 @@ lib/eina/eina_mempool.c \ lib/eina/eina_mmap.c \ lib/eina/eina_module.c \ lib/eina/eina_prefix.c \ +lib/eina/eina_promise.c \ +lib/eina/eina_promise_private.h \ lib/eina/eina_quad.c \ lib/eina/eina_quadtree.c \ lib/eina/eina_rbtree.c \ diff --git a/src/lib/ecore/ecore.c b/src/lib/ecore/ecore.c index aa5e4e4f3c..ec733b20c6 100644 --- a/src/lib/ecore/ecore.c +++ b/src/lib/ecore/ecore.c @@ -251,6 +251,7 @@ ecore_init(void) if (getenv("ECORE_FPS_DEBUG")) _ecore_fps_debug = 1; if (_ecore_fps_debug) _ecore_fps_debug_init(); if (!ecore_mempool_init()) goto shutdown_mempool; + if (!_ecore_event_init()) goto shutdown_mempool; _ecore_main_loop_init(); vpath = efl_add(EFL_VPATH_CORE_CLASS, NULL); diff --git a/src/lib/ecore/ecore_events.c b/src/lib/ecore/ecore_events.c index 3b13ef9015..ed405edb39 100644 --- a/src/lib/ecore/ecore_events.c +++ b/src/lib/ecore/ecore_events.c @@ -48,6 +48,15 @@ struct _Ecore_Event }; GENERIC_ALLOC_SIZE_DECLARE(Ecore_Event); +typedef struct _Ecore_Future_Schedule_Entry +{ + Eina_Future_Schedule_Entry base; + Eina_Future_Scheduler_Cb cb; + Eina_Future *future; + Ecore_Event *event; + Eina_Value value; +} Ecore_Future_Schedule_Entry; + static int events_num = 0; static Ecore_Event *events = NULL; static Ecore_Event *event_current = NULL; @@ -68,6 +77,10 @@ static int event_filters_delete_me = 0; static int event_id_max = ECORE_EVENT_COUNT; static int ecore_raw_event_type = ECORE_EVENT_NONE; static void *ecore_raw_event_event = NULL; +static Ecore_Event_Handler *future_handler = NULL; +static int ECORE_EV_FUTURE_ID = -1; +static Eina_Mempool *mp_future_schedule_entry = NULL; +static Eina_Bool shutting_down = EINA_FALSE; static void _ecore_event_purge_deleted(void); static void *_ecore_event_del(Ecore_Event *event); @@ -268,6 +281,100 @@ _ecore_event_handler_del(Ecore_Event_Handler *event_handler) return event_handler->data; } +static Eina_Bool +ecore_future_dispatched(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) +{ + Ecore_Future_Schedule_Entry *entry = event; + entry->event = NULL; + entry->cb(entry->future, entry->value); + return EINA_FALSE; +} + +static void +ecore_future_free(void *user_data, void *func_data EINA_UNUSED) +{ + Ecore_Future_Schedule_Entry *entry = user_data; + /* + In case entry->event is not NULL, it means + that ecore is shutting down. In this case, + we must cancel the future otherwise Eina may + try to use it and lead to crashes. + */ + if (entry->event) + { + eina_future_cancel(entry->future); + eina_value_flush(&entry->value); + } + eina_mempool_free(mp_future_schedule_entry, entry); +} + +static Eina_Future_Schedule_Entry * +ecore_future_schedule(Eina_Future_Scheduler *sched, Eina_Future_Scheduler_Cb cb, Eina_Future *future, Eina_Value value) +{ + Ecore_Future_Schedule_Entry *entry = eina_mempool_malloc(mp_future_schedule_entry, + sizeof(Ecore_Future_Schedule_Entry)); + EINA_SAFETY_ON_NULL_RETURN_VAL(entry, NULL); + entry->base.scheduler = sched; + entry->cb = cb; + entry->future = future; + entry->value = value; + entry->event = ecore_event_add(ECORE_EV_FUTURE_ID, entry, ecore_future_free, entry); + EINA_SAFETY_ON_NULL_GOTO(entry->event, err); + return &entry->base; + + err: + eina_mempool_free(mp_future_schedule_entry, entry); + return NULL; +} + +static void +ecore_future_recall(Eina_Future_Schedule_Entry *s_entry) +{ + if (shutting_down) return; + Ecore_Future_Schedule_Entry *entry = (Ecore_Future_Schedule_Entry *)s_entry; + EINA_SAFETY_ON_NULL_RETURN(entry->event); + ecore_event_del(entry->event); + eina_value_flush(&entry->value); + entry->event = NULL; +} + +static Eina_Future_Scheduler ecore_future_scheduler = { + .schedule = ecore_future_schedule, + .recall = ecore_future_recall, +}; + +Eina_Future_Scheduler * +_ecore_event_future_scheduler_get(void) +{ + return &ecore_future_scheduler; +} + +Eina_Bool +_ecore_event_init(void) +{ + const char *choice = getenv("EINA_MEMPOOL"); + if ((!choice) || (!choice[0])) choice = "chained_mempool"; + + shutting_down = EINA_FALSE; + ECORE_EV_FUTURE_ID = ecore_event_type_new(); + future_handler = ecore_event_handler_add(ECORE_EV_FUTURE_ID, ecore_future_dispatched, NULL); + EINA_SAFETY_ON_NULL_GOTO(future_handler, err_handler); + //FIXME: Is 512 too high? + mp_future_schedule_entry = eina_mempool_add(choice, "Ecore_Future_Event", + NULL, sizeof(Ecore_Future_Schedule_Entry), + 512); + EINA_SAFETY_ON_NULL_GOTO(mp_future_schedule_entry, err_pool); + + return EINA_TRUE; + + err_pool: + ecore_event_handler_del(future_handler); + future_handler = NULL; + err_handler: + ECORE_EV_FUTURE_ID = -1; + return EINA_FALSE; +} + void _ecore_event_shutdown(void) { @@ -275,6 +382,9 @@ _ecore_event_shutdown(void) Ecore_Event_Handler *eh; Ecore_Event_Filter *ef; + shutting_down = EINA_TRUE; + ecore_event_handler_del(future_handler); + future_handler = NULL; while (events) _ecore_event_del(events); event_current = NULL; for (i = 0; i < event_handlers_num; i++) @@ -301,6 +411,7 @@ _ecore_event_shutdown(void) event_filters_delete_me = 0; event_filter_current = NULL; event_filter_event_current = NULL; + ECORE_EV_FUTURE_ID = -1; } int diff --git a/src/lib/ecore/ecore_main.c b/src/lib/ecore/ecore_main.c index 12e8cf450f..0bb15e0c02 100644 --- a/src/lib/ecore/ecore_main.c +++ b/src/lib/ecore/ecore_main.c @@ -3286,5 +3286,12 @@ _efl_loop_efl_version_get(Eo *obj EINA_UNUSED, Efl_Loop_Data *pd EINA_UNUSED) return &version; } +EOLIAN static Eina_Future_Scheduler * +_efl_loop_future_scheduler_get(Eo *obj EINA_UNUSED, + Efl_Loop_Data *pd EINA_UNUSED) +{ + return _ecore_event_future_scheduler_get(); +} + #include "efl_loop.eo.c" diff --git a/src/lib/ecore/ecore_private.h b/src/lib/ecore/ecore_private.h index de6838b8b2..6437b41fb2 100644 --- a/src/lib/ecore/ecore_private.h +++ b/src/lib/ecore/ecore_private.h @@ -187,6 +187,10 @@ void _ecore_idle_enterer_call(Eo *loop); void _ecore_idle_exiter_call(Eo *loop); + +Eina_Future_Scheduler *_ecore_event_future_scheduler_get(void); + +Eina_Bool _ecore_event_init(void); void _ecore_event_shutdown(void); int _ecore_event_exist(void); Ecore_Event *_ecore_event_add(int type, diff --git a/src/lib/ecore/efl_loop.eo b/src/lib/ecore/efl_loop.eo index aa8ae08d4c..e34deb586f 100644 --- a/src/lib/ecore/efl_loop.eo +++ b/src/lib/ecore/efl_loop.eo @@ -1,4 +1,5 @@ import efl_types; +import eina_types; struct Efl.Loop.Arguments { [[EFL loop arguments data structure]] @@ -69,6 +70,18 @@ class Efl.Loop (Efl.Object) @in exit_code: ubyte; [[Returned value by begin()]] } } + @property future_scheduler { + [[Gets the Eina_Future_Scheduler for a given mainloop. + + The Eina_Future_Scheduler returned by this function + should be used for creating promises (eina_promise_new()) + so then can properly schedule resolve/reject events. + ]] + get {} + values { + scheduler: ptr(Eina.Future.Scheduler); [[The scheduler.]] + } + } job { [[Will execute that promise in the near future.]] params { diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h index ee659117ea..76b6d0adf6 100644 --- a/src/lib/eina/Eina.h +++ b/src/lib/eina/Eina.h @@ -273,6 +273,7 @@ extern "C" { #include <eina_freeq.h> #include <eina_slstr.h> #include <eina_debug.h> +#include <eina_promise.h> #undef EAPI #define EAPI diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c index 36177158c0..38865eb57d 100644 --- a/src/lib/eina/eina_main.c +++ b/src/lib/eina/eina_main.c @@ -159,6 +159,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL; S(file); S(safepointer); S(slstr); + S(promise); #undef S struct eina_desc_setup @@ -205,6 +206,7 @@ static const struct eina_desc_setup _eina_desc_setup[] = { S(file), S(safepointer), S(slstr), + S(promise), #undef S }; static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) / diff --git a/src/lib/eina/eina_promise.c b/src/lib/eina/eina_promise.c new file mode 100644 index 0000000000..596149fb10 --- /dev/null +++ b/src/lib/eina/eina_promise.c @@ -0,0 +1,1355 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "eina_private.h" +#include "eina_promise.h" +#include "eina_mempool.h" +#include "eina_promise_private.h" +#include <errno.h> +#include <stdarg.h> +#include <assert.h> + +#define EINA_FUTURE_DISPATCHED ((Eina_Future_Cb)(0x01)) + +#define EFL_MEMPOOL_CHECK_RETURN(_type, _mp, _p) \ + if (!eina_mempool_from((_mp), (_p))) \ + { \ + ERR("The %s %p is not alive at mempool %p", (_type), (_p), (_mp)); \ + return; \ + } + +#define EFL_MEMPOOL_CHECK_RETURN_VAL(_type, _mp, _p, _val) \ + if (!eina_mempool_from((_mp), (_p))) \ + { \ + ERR("The %s %p is not alive at mempool %p", (_type),(_p), (_mp)); \ + return (_val); \ + } + +#define EFL_MEMPOOL_CHECK_GOTO(_type, _mp, _p, _goto) \ + if (!eina_mempool_from((_mp), (_p))) \ + { \ + ERR("The %s %p is not alive at mempool %p", (_type), (_p), (_mp)); \ + goto _goto; \ + } + +#define EINA_PROMISE_CHECK_RETURN(_p) \ + do { \ + EINA_SAFETY_ON_NULL_RETURN((_p)); \ + EFL_MEMPOOL_CHECK_RETURN("promise", _promise_mp, (_p)); \ + } while (0); + +#define EINA_PROMISE_CHECK_RETURN_VAL(_p, _val) \ + do { \ + EINA_SAFETY_ON_NULL_RETURN_VAL((_p), (_val)); \ + EFL_MEMPOOL_CHECK_RETURN_VAL("promise", _promise_mp, (_p), (_val)); \ + } while (0); + +#define EINA_PROMISE_CHECK_GOTO(_p, _goto) \ + do { \ + EINA_SAFETY_ON_NULL_GOTO((_p), _goto); \ + EFL_MEMPOOL_CHECK_GOTO("promise", _promise_mp, (_p), _goto); \ + } while (0); + +#define EINA_FUTURE_CHECK_GOTO(_p, _goto) \ + do { \ + EINA_SAFETY_ON_NULL_GOTO((_p), _goto); \ + EFL_MEMPOOL_CHECK_GOTO("future", _future_mp, (_p), _goto); \ + } while (0); + +#define EINA_FUTURE_CHECK_RETURN(_p) \ + do { \ + EINA_SAFETY_ON_NULL_RETURN((_p)); \ + EFL_MEMPOOL_CHECK_RETURN("future", _future_mp, (_p)); \ + if (_p->cb == EINA_FUTURE_DISPATCHED) \ + { \ + ERR("Future %p already dispatched", _p); \ + return; \ + } \ + } while (0); + +#define EINA_FUTURE_CHECK_RETURN_VAL(_p, _val) \ + do { \ + EINA_SAFETY_ON_NULL_RETURN_VAL((_p), (_val)); \ + EFL_MEMPOOL_CHECK_RETURN_VAL("future", _future_mp, (_p), (_val)); \ + if (_p->cb == EINA_FUTURE_DISPATCHED) \ + { \ + ERR("Future %p already dispatched", _p); \ + return (_val); \ + } \ + } while (0); + +#undef ERR +#define ERR(...) EINA_LOG_DOM_ERR(_promise_log_dom, __VA_ARGS__) + +#undef DBG +#define DBG(...) EINA_LOG_DOM_DBG(_promise_log_dom, __VA_ARGS__) + +#undef INF +#define INF(...) EINA_LOG_DOM_INFO(_promise_log_dom, __VA_ARGS__) + +#undef WRN +#define WRN(...) EINA_LOG_DOM_WARN(_promise_log_dom, __VA_ARGS__) + +#undef CRI +#define CRI(...) EINA_LOG_DOM_CRIT(_promise_log_dom, __VA_ARGS__) + +#define _eina_promise_value_dbg(_msg, _p, _v) __eina_promise_value_dbg(_msg, _p, _v, __LINE__, __FUNCTION__) + +struct _Eina_Promise { + Eina_Future *future; + Eina_Future_Scheduler *scheduler; + Eina_Promise_Cancel_Cb cancel; + const void *data; +}; + +struct _Eina_Future { + Eina_Promise *promise; + Eina_Future *next; + Eina_Future *prev; + Eina_Future_Cb cb; + const void *data; + Eina_Future **storage; + Eina_Future_Schedule_Entry *scheduled_entry; +}; + +static Eina_Mempool *_promise_mp = NULL; +static Eina_Mempool *_future_mp = NULL; +static Eina_List *_pending_futures = NULL; +static int _promise_log_dom = -1; + +static void _eina_promise_cancel(Eina_Promise *p); + +static Eina_Value_Struct_Member RACE_STRUCT_MEMBERS[] = { + EINA_VALUE_STRUCT_MEMBER(NULL, Eina_Future_Race_Result, value), + EINA_VALUE_STRUCT_MEMBER(NULL, Eina_Future_Race_Result, index), + EINA_VALUE_STRUCT_MEMBER_SENTINEL +}; + +static const Eina_Value_Struct_Desc RACE_STRUCT_DESC = { + .version = EINA_VALUE_STRUCT_DESC_VERSION, + .ops = NULL, + .members = RACE_STRUCT_MEMBERS, + .member_count = 2, + .size = sizeof(Eina_Future_Race_Result) +}; + +EAPI const Eina_Value_Struct_Desc *EINA_PROMISE_RACE_STRUCT_DESC = &RACE_STRUCT_DESC; + +static inline void +__eina_promise_value_dbg(const char *msg, + const Eina_Promise *p, + const Eina_Value v, + int line, + const char *fname) +{ + if (EINA_UNLIKELY(eina_log_domain_level_check(_promise_log_dom, + EINA_LOG_LEVEL_DBG))) + { + if (!v.type) + { + eina_log_print(_promise_log_dom, EINA_LOG_LEVEL_DBG, + __FILE__, fname, line, "%s: %p with no value", + msg, p); + } + else + { + char *str = eina_value_to_string(&v); + eina_log_print(_promise_log_dom, EINA_LOG_LEVEL_DBG, + __FILE__, fname, line, + "%s: %p - Value Type: %s Content: %s", msg, p, + eina_value_type_name_get(eina_value_type_get(&v)), + str); + free(str); + } + } +} + +static Eina_Bool +_promise_setup(const Eina_Value_Type *type EINA_UNUSED, void *mem) +{ + Eina_Promise **tmem = mem; + *tmem = NULL; + return EINA_TRUE; +} + +static Eina_Bool +_promise_flush(const Eina_Value_Type *type EINA_UNUSED, void *mem) +{ + Eina_Promise **tmem = mem; + if (*tmem) + { + _eina_promise_cancel(*tmem); + *tmem = NULL; + } + return EINA_TRUE; +} + +static void +_promise_replace(Eina_Promise **dst, Eina_Promise * const *src) +{ + if (*src == *dst) return; + if (*dst) _eina_promise_cancel(*dst); + *dst = *src; +} + +static Eina_Bool +_promise_vset(const Eina_Value_Type *type EINA_UNUSED, void *mem, va_list args) +{ + Eina_Promise **dst = mem; + Eina_Promise **src = va_arg(args, Eina_Promise **); + _promise_replace(dst, src); + return EINA_TRUE; +} + +static Eina_Bool +_promise_pset(const Eina_Value_Type *type EINA_UNUSED, + void *mem, const void *ptr) +{ + Eina_Promise **dst = mem; + Eina_Promise * const *src = ptr; + _promise_replace(dst, src); + return EINA_TRUE; +} + +static Eina_Bool +_promise_pget(const Eina_Value_Type *type EINA_UNUSED, + const void *mem, void *ptr) +{ + Eina_Promise * const *src = mem; + Eina_Promise **dst = ptr; + *dst = *src; + return EINA_TRUE; +} + +static Eina_Bool +_promise_convert_to(const Eina_Value_Type *type EINA_UNUSED, const Eina_Value_Type *convert, const void *type_mem, void *convert_mem) +{ + Eina_Promise * const *p = type_mem; + + if (convert == EINA_VALUE_TYPE_STRINGSHARE || + convert == EINA_VALUE_TYPE_STRING) + { + const char *other_mem; + char buf[128]; + snprintf(buf, sizeof(buf), "Promise %p (cancel: %p, data: %p)", + *p, (*p)->cancel, (*p)->data); + other_mem = buf; + return eina_value_type_pset(convert, convert_mem, &other_mem); + } + return EINA_FALSE; +} + +static const Eina_Value_Type EINA_VALUE_TYPE_PROMISE = { + .version = EINA_VALUE_TYPE_VERSION, + .value_size = sizeof(Eina_Promise *), + .name = "Eina_Promise", + .setup = _promise_setup, + .flush = _promise_flush, + .copy = NULL, + .compare = NULL, + .convert_to = _promise_convert_to, + .convert_from = NULL, + .vset = _promise_vset, + .pset = _promise_pset, + .pget = _promise_pget +}; + +static Eina_Promise * +_eina_value_promise_steal(Eina_Value *value) +{ + Eina_Promise **p, *r; + /* + Do not use eina_value_flush()/eina_value_pset() in here, + otherwise it would cancel the promise! + */ + p = eina_value_memory_get(value); + r = *p; + *p = NULL; + return r; +} + +static Eina_Future * +_eina_future_free(Eina_Future *f) +{ + DBG("Free future %p", f); + Eina_Future *next = f->next; + Eina_Future *prev = f->prev; + if (next) next->prev = NULL; + if (prev) prev->next = NULL; + eina_mempool_free(_future_mp, f); + return next; +} + +static void +_eina_promise_unlink(Eina_Promise *p) +{ + if (p->future) + { + DBG("Unliking promise %p and future %p", p, p->future); + p->future->promise = NULL; + p->future = NULL; + } +} + +static void +_eina_promise_link(Eina_Promise *p, Eina_Future *f) +{ + assert(f != NULL); + if (p) p->future = f; + f->promise = p; + DBG("Linking future %p with promise %p", f, p); +} + +static void +_eina_promise_cancel(Eina_Promise *p) +{ + DBG("Cancelling promise: %p, data: %p, future: %p", p, p->data, p->future); + _eina_promise_unlink(p); + p->cancel((void *)p->data, p); + eina_mempool_free(_promise_mp, p); +} + +static void +_eina_promise_value_steal_and_link(Eina_Value value, Eina_Future *f) +{ + Eina_Promise *p = _eina_value_promise_steal(&value); + DBG("Promise %p stolen from value", p); + eina_value_flush(&value); + if (f) _eina_promise_link(p, f); + else _eina_promise_unlink(p); +} + +static Eina_Value +_eina_future_cb_dispatch(Eina_Future *f, const Eina_Value value) +{ + Eina_Future_Cb cb = f->cb; + + f->cb = EINA_FUTURE_DISPATCHED; + if (f->storage) *f->storage = NULL; + + if (EINA_UNLIKELY(eina_log_domain_level_check(_promise_log_dom, EINA_LOG_LEVEL_DBG))) + { + if (!value.type) DBG("Distach cb: %p, data: %p with no value", cb, f->data); + else + { + char *str = eina_value_to_string(&value); + DBG("Dispatch cb: %p, data: %p, value type: %s content: '%s'", + cb, f->data, + eina_value_type_name_get(eina_value_type_get(&value)), str); + free(str); + } + } + return cb((void *)f->data, value, f); +} + +static Eina_Value +_eina_future_dispatch_internal(Eina_Future **f, + const Eina_Value value) +{ + Eina_Value next_value = EINA_VALUE_EMPTY; + + assert(value.type != &EINA_VALUE_TYPE_PROMISE); + while ((*f) && (!(*f)->cb)) *f = _eina_future_free(*f); + if (!*f) + { + _eina_promise_value_dbg("No future to deliver value", NULL, value); + return value; + } + next_value = _eina_future_cb_dispatch(*f, value); + *f = _eina_future_free(*f); + return next_value; +} + +static Eina_Bool +_eina_value_is(const Eina_Value v1, const Eina_Value v2) +{ + if (v1.type != v2.type) return EINA_FALSE; + //Both types are NULL at this point... so they are equal... + if (!v1.type) return EINA_TRUE; + return !memcmp(eina_value_memory_get(&v1), + eina_value_memory_get(&v2), + v1.type->value_size); +} + +static void +_eina_future_dispatch(Eina_Future *f, Eina_Value value) +{ + Eina_Value next_value = _eina_future_dispatch_internal(&f, value); + if (!_eina_value_is(next_value, value)) eina_value_flush(&value); + if (!f) + { + if (next_value.type == &EINA_VALUE_TYPE_PROMISE) + { + DBG("There are no more futures, but next_value is a promise setting p->future to NULL."); + _eina_promise_value_steal_and_link(next_value, NULL); + } + else eina_value_flush(&next_value); + return; + } + + if (next_value.type == &EINA_VALUE_TYPE_PROMISE) + { + if (EINA_UNLIKELY(eina_log_domain_level_check(_promise_log_dom, EINA_LOG_LEVEL_DBG))) + { + Eina_Promise *p = NULL; + + eina_value_pget(&next_value, &p); + DBG("Future %p will wait for a new promise %p", f, p); + } + _eina_promise_value_steal_and_link(next_value, f); + } + else _eina_future_dispatch(f, next_value); + } + +static void +_scheduled_entry_cb(Eina_Future *f, Eina_Value value) +{ + _pending_futures = eina_list_remove(_pending_futures, f); + f->scheduled_entry = NULL; + _eina_future_dispatch(f, value); +} + +void +eina_future_schedule_entry_recall(Eina_Future_Schedule_Entry *entry) +{ + entry->scheduler->recall(entry); +} + +static void +_eina_future_cancel(Eina_Future *f, int err) +{ + Eina_Value value = EINA_VALUE_EMPTY; + + DBG("Cancelling future %p, cb: %p data: %p with error: %d - msg: '%s'", + f, f->cb, f->data, err, eina_error_msg_get(err)); + + for (; f->prev != NULL; f = f->prev) + { + assert(f->promise == NULL); /* intermediate futures shouldn't have a promise */ + assert(f->scheduled_entry == NULL); /* intermediate futures shouldn't have pending dispatch */ + } + + if (f->scheduled_entry) + { + eina_future_schedule_entry_recall(f->scheduled_entry); + f->scheduled_entry = NULL; + _pending_futures = eina_list_remove(_pending_futures, f); + } + + if (f->promise) + { + _eina_promise_cancel(f->promise); + f->promise = NULL; + } + + eina_value_setup(&value, EINA_VALUE_TYPE_ERROR); + eina_value_set(&value, err); + + while (f) + { + if (f->cb) + { + Eina_Value r = _eina_future_cb_dispatch(f, value); + if (!_eina_value_is(value, r)) eina_value_flush(&r); + } + f = _eina_future_free(f); + } + eina_value_flush(&value); +} + +static void +_eina_future_schedule(Eina_Promise *p, + Eina_Future *f, + Eina_Value value) +{ + f->scheduled_entry = p->scheduler->schedule(p->scheduler, + _scheduled_entry_cb, + f, value); + EINA_SAFETY_ON_NULL_GOTO(f->scheduled_entry, err); + assert(f->scheduled_entry->scheduler != NULL); + _pending_futures = eina_list_append(_pending_futures, f); + DBG("The promise %p schedule the future %p with cb: %p and data: %p", + p, f, f->cb, f->data); + return; + err: + _eina_future_cancel(p->future, ENOMEM); + eina_value_flush(&value); +} + +static void +_eina_promise_deliver(Eina_Promise *p, + Eina_Value value) +{ + if (p->future) + { + Eina_Future *f = p->future; + _eina_promise_unlink(p); + if (value.type == &EINA_VALUE_TYPE_PROMISE) _eina_promise_value_steal_and_link(value, f); + else _eina_future_schedule(p, f, value); + } + else + { + DBG("Promise %p has no future", p); + eina_value_flush(&value); + } + eina_mempool_free(_promise_mp, p); +} + +Eina_Bool +eina_promise_init(void) +{ + const char *choice = getenv("EINA_MEMPOOL"); + if ((!choice) || (!choice[0])) choice = "chained_mempool"; + + RACE_STRUCT_MEMBERS[0].type = EINA_VALUE_TYPE_VALUE; + RACE_STRUCT_MEMBERS[1].type = EINA_VALUE_TYPE_UINT; + + _promise_log_dom = eina_log_domain_register("eina_promise", EINA_COLOR_CYAN); + if (_promise_log_dom < 0) + { + EINA_LOG_ERR("Could not creae the Eina_Promise domain"); + return EINA_FALSE; + } + + //FIXME: Is 512 too high? + _promise_mp = eina_mempool_add(choice, "Eina_Promise", + NULL, sizeof(Eina_Promise), 512); + EINA_SAFETY_ON_NULL_GOTO(_promise_mp, err_promise); + + _future_mp = eina_mempool_add(choice, "Eina_Future", + NULL, sizeof(Eina_Future), 512); + EINA_SAFETY_ON_NULL_GOTO(_future_mp, err_future); + + return EINA_TRUE; + + err_future: + eina_mempool_del(_promise_mp); + _promise_mp = NULL; + err_promise: + eina_log_domain_unregister(_promise_log_dom); + _promise_log_dom = -1; + return EINA_FALSE; +} + +Eina_Bool +eina_promise_shutdown(void) +{ + while (_pending_futures) _eina_future_cancel(_pending_futures->data, ECANCELED); + eina_mempool_del(_future_mp); + eina_mempool_del(_promise_mp); + eina_log_domain_unregister(_promise_log_dom); + _promise_log_dom = -1; + _promise_mp = NULL; + _future_mp = NULL; + return EINA_TRUE; +} + +EAPI Eina_Value +eina_promise_as_value(Eina_Promise *p) +{ + Eina_Value v = EINA_VALUE_EMPTY; + Eina_Bool r; + EINA_PROMISE_CHECK_RETURN_VAL(p, v); + r = eina_value_setup(&v, &EINA_VALUE_TYPE_PROMISE); + EINA_SAFETY_ON_FALSE_GOTO(r, err_setup); + r = eina_value_pset(&v, &p); + EINA_SAFETY_ON_FALSE_GOTO(r, err_pset); + DBG("Created value from promise %p", p); + return v; + + err_pset: + eina_value_flush(&v); + memset(&v, 0, sizeof(Eina_Value)); + err_setup: + if (p->future) _eina_future_cancel(p->future, ENOMEM); + else _eina_promise_cancel(p); + return v; +} + +static void +_eina_promise_clean_dispatch(Eina_Promise *p, Eina_Value v) +{ + Eina_Future *f = p->future; + + if (f) + { + _eina_promise_value_dbg("Clean contenxt - Resolving promise", p, v); + _eina_promise_unlink(p); + _eina_future_dispatch(f, v); + } + eina_mempool_free(_promise_mp, p); +} + +static Eina_Value +_future_proxy(void *data, const Eina_Value v, + const Eina_Future *dead_future EINA_UNUSED) +{ + Eina_Value copy; + //We're in a safe context (from mainloop), so we can avoid scheduling a new dispatch + if (!v.type) copy = v; + else if (!eina_value_copy(&v, ©)) + { + ERR("Value cannot be copied - unusable with Eina_Future: %p (%s)", v.type, v.type->name); + eina_value_setup(©, EINA_VALUE_TYPE_ERROR); + eina_value_set(©, ENOTSUP); + } + _eina_promise_clean_dispatch(data, copy); + return v; +} + +static void +_proxy_cancel(void *data EINA_UNUSED, const Eina_Promise *dead_ptr EINA_UNUSED) +{ +} + +static Eina_Future_Scheduler * +_scheduler_get(Eina_Future *f) +{ + for (; f->prev != NULL; f = f->prev); + assert(f->promise != NULL); + return f->promise->scheduler; +} + +EAPI Eina_Value +eina_future_as_value(Eina_Future *f) +{ + Eina_Value v = EINA_VALUE_EMPTY; + Eina_Promise *p; + Eina_Future *r_future; + + EINA_FUTURE_CHECK_RETURN_VAL(f, v); + p = eina_promise_new(_scheduler_get(f), _proxy_cancel, NULL); + EINA_SAFETY_ON_NULL_GOTO(p, err_promise); + r_future = eina_future_then(f, _future_proxy, p); + //If eina_future_then() fails f will be cancelled + EINA_SAFETY_ON_NULL_GOTO(r_future, err_future); + + v = eina_promise_as_value(p); + if (v.type == &EINA_VALUE_TYPE_PROMISE) + { + DBG("Creating future proxy for future: %p - promise %p", f, p); + return v; + } + + //The promise was freed by eina_promise_as_value() + ERR("Could not create a Eina_Value for future %p", f); + //Futures will be unlinked + _eina_future_free(r_future); + _eina_future_cancel(f, ENOMEM); + return v; + + err_future: + /* + Do not cancel the _eina_future_cancel(f,..) here, it + was already canceled by eina_future_then(). + */ + _eina_promise_cancel(p); + return v; + + err_promise: + _eina_future_cancel(f, ENOMEM); + return v; +} + +EAPI Eina_Promise * +eina_promise_new(Eina_Future_Scheduler *scheduler, + Eina_Promise_Cancel_Cb cancel_cb, const void *data) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(cancel_cb, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(scheduler, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(scheduler->schedule, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(scheduler->recall, NULL); + + Eina_Promise *p = eina_mempool_calloc(_promise_mp, sizeof(Eina_Promise)); + EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL); + p->cancel = cancel_cb; + p->data = data; + p->scheduler = scheduler; + DBG("Creating new promise - Promise:%p, cb: %p, data:%p", p, + p->cancel, p->data); + return p; +} + +EAPI void +eina_future_cancel(Eina_Future *f) +{ + EINA_FUTURE_CHECK_RETURN(f); + _eina_future_cancel(f, ECANCELED); +} + +EAPI void +eina_promise_resolve(Eina_Promise *p, Eina_Value value) +{ + EINA_PROMISE_CHECK_GOTO(p, err); + _eina_promise_value_dbg("Resolve promise", p, value); + _eina_promise_deliver(p, value); + return; + err: + eina_value_flush(&value); +} + +EAPI void +eina_promise_reject(Eina_Promise *p, Eina_Error err) +{ + Eina_Value value; + Eina_Bool r; + + EINA_PROMISE_CHECK_RETURN(p); + r = eina_value_setup(&value, EINA_VALUE_TYPE_ERROR); + EINA_SAFETY_ON_FALSE_GOTO(r, err_setup); + r = eina_value_set(&value, err); + EINA_SAFETY_ON_FALSE_GOTO(r, err_set); + DBG("Reject promise %p - Error msg: '%s' - Error code: %d", p, + eina_error_msg_get(err), err); + _eina_promise_deliver(p, value); + return; + + err_set: + eina_value_flush(&value); + err_setup: + if (p->future) _eina_future_cancel(p->future, ENOMEM); + else _eina_promise_cancel(p); +} + +static void +_fake_future_dispatch(const Eina_Future_Desc desc, int err) +{ + /* + This function is used to dispatch the Eina_Future_Cb in case, + the future creation fails. By calling the Eina_Future_Cb + the user has a chance to free allocated resources. + */ + Eina_Value v, r; + + if (desc.storage) *desc.storage = NULL; + if (!desc.cb) return; + eina_value_setup(&v, EINA_VALUE_TYPE_ERROR); + eina_value_set(&v, err); + //Since the future was not created the dead_ptr is NULL. + r = desc.cb((void *)desc.data, v, NULL); + eina_value_flush(&r); + eina_value_flush(&v); +} + +static Eina_Future * +_eina_future_new(Eina_Promise *p, const Eina_Future_Desc desc) +{ + Eina_Future *f; + + f = eina_mempool_calloc(_future_mp, sizeof(Eina_Future)); + EINA_SAFETY_ON_NULL_GOTO(f, err_future); + _eina_promise_link(p, f); + f->cb = desc.cb; + f->data = desc.data; + if (desc.storage) + { + *desc.storage = f; + f->storage = desc.storage; + } + DBG("Creating new future - Promise:%p, Future:%p, cb: %p, data: %p ", + p, f, f->cb, f->data); + return f; + + err_future: + _fake_future_dispatch(desc, ENOMEM); + if (p) _eina_promise_cancel(p); + return NULL; +} + +EAPI Eina_Future * +eina_future_new(Eina_Promise *p) +{ + static const Eina_Future_Desc desc = { + .cb = NULL, + .data = NULL + }; + + EINA_PROMISE_CHECK_RETURN_VAL(p, NULL); + EINA_SAFETY_ON_TRUE_GOTO(p->future != NULL, err_has_future); + + return _eina_future_new(p, desc); + + err_has_future: + //_eina_future_cancel() will also cancel the promise + _eina_future_cancel(p->future, EINVAL); + return NULL; +} + +static Eina_Future * +_eina_future_then(Eina_Future *prev, const Eina_Future_Desc desc) +{ + Eina_Future *next = _eina_future_new(NULL, desc); + EINA_SAFETY_ON_NULL_GOTO(next, err_next); + next->prev = prev; + prev->next = next; + DBG("Linking futures - Prev:%p Next:%p", prev, next); + return next; + + err_next: + //_fake_future_dispatch() already called by _eina_future_new() + _eina_future_cancel(prev, ENOMEM); + return NULL; +} + +EAPI Eina_Future * +eina_future_then_from_desc(Eina_Future *prev, const Eina_Future_Desc desc) +{ + EINA_FUTURE_CHECK_GOTO(prev, err_future); + EINA_SAFETY_ON_TRUE_GOTO(prev->next != NULL, err_next); + return _eina_future_then(prev, desc); + + err_next: + _eina_future_cancel(prev->next, EINVAL); + err_future: + _fake_future_dispatch(desc, EINVAL); + return NULL; +} + +EAPI Eina_Future * +eina_future_chain_array(Eina_Future *prev, const Eina_Future_Desc descs[]) +{ + Eina_Future *f = prev; + ssize_t i = -1; + int err = EINVAL; + + EINA_FUTURE_CHECK_GOTO(prev, err_prev); + EINA_SAFETY_ON_TRUE_GOTO(prev->next != NULL, err_next); + + err = ENOMEM; + for (i = 0; descs[i].cb; i++) + { + f = _eina_future_then(f, descs[i]); + /* + If _eina_future_then() fails the whole chain will be cancelled by it. + All we need to do is free the remaining descs.. + */ + EINA_SAFETY_ON_NULL_GOTO(f, err_prev); + } + + return f; + + err_next: + _eina_future_cancel(f, err); + err_prev: + /* + If i > 0 we'll start to dispatch fake futures + at i + 1, since the descs[i] was already freed + by _eina_future_then() + */ + for (i = i + 1; descs[i].cb; i++) + _fake_future_dispatch(descs[i], err); + return NULL; +} + +EAPI Eina_Future * +eina_future_chain_easy_array(Eina_Future *prev, const Eina_Future_Cb_Easy_Desc descs[]) +{ + size_t i = -1; + Eina_Future *f = prev; + int err = EINVAL; + + EINA_FUTURE_CHECK_GOTO(prev, err_prev); + EINA_SAFETY_ON_TRUE_GOTO(prev->next != NULL, err_next); + + err = ENOMEM; + for (i = 0; descs[i].success || descs[i].error || descs[i].free || descs[i].success_type; i++) + { + Eina_Future_Desc fdesc = eina_future_cb_easy_from_desc(descs[i]); + f = _eina_future_then(f, fdesc); + EINA_SAFETY_ON_NULL_GOTO(f, err_prev); + } + + return f; + + err_next: + _eina_future_cancel(f, err); + err_prev: + /* + If i > 0 we'll start to dispatch fake futures + at i + 1, since the descs[i] was already freed + by _eina_future_then() + */ + for (i = i + 1; descs[i].error || descs[i].free; i++) + { + if (descs[i].error) + { + Eina_Value v = descs[i].error((void *)descs[i].data, err); + eina_value_flush(&v); + } + if (descs[i].free) descs[i].free((void *)descs[i].data, NULL); + } + return NULL; +} + +static Eina_Value +_eina_future_cb_console(void *data, + const Eina_Value value, + const Eina_Future *dead_future EINA_UNUSED) +{ + Eina_Future_Cb_Console_Desc *c = data; + const char *prefix = c ? c->prefix : NULL; + const char *suffix = c ? c->suffix : NULL; + const char *content = "no value"; + char *str = NULL; + + if (value.type) + { + str = eina_value_to_string(&value); + content = str; + } + + if (!prefix) prefix = ""; + if (!suffix) suffix = "\n"; + printf("%s%s%s", prefix, content, suffix); + free(str); + if (c) { + free((void *)c->prefix); + free((void *)c->suffix); + free(c); + } + return value; +} + +EAPI Eina_Future_Desc +eina_future_cb_console_from_desc(const Eina_Future_Cb_Console_Desc desc) +{ + Eina_Future_Cb_Console_Desc *c; + Eina_Future_Desc fdesc = { + .cb = _eina_future_cb_console, + .data = NULL + }; + + if (desc.prefix || desc.suffix) { + fdesc.data = c = calloc(1, sizeof(Eina_Future_Cb_Console_Desc)); + EINA_SAFETY_ON_NULL_GOTO(c, exit); + c->prefix = desc.prefix ? strdup(desc.prefix) : NULL; + c->suffix = desc.suffix ? strdup(desc.suffix) : NULL; + } + exit: + return fdesc; +} + +static Eina_Value +_eina_future_cb_convert_to(void *data, const Eina_Value src, + const Eina_Future *dead_future EINA_UNUSED) +{ + const Eina_Value_Type *type = data; + Eina_Value dst = EINA_VALUE_EMPTY; + + if (!type) + { + DBG("Trying to convert type '%s' to a NULL type - Ignoring (pass thru)", + src.type->name); + return src; // pass thru + } + + if (!eina_value_setup(&dst, type)) + { + ERR("Could not setup an Eina_Value with type named: '%s'", type->name); + eina_value_setup(&dst, EINA_VALUE_TYPE_ERROR); + eina_value_set(&dst, ENOMEM); + } + else if (src.type && !eina_value_convert(&src, &dst)) + { + //Clean the type + ERR("Could not convert the Eina_Value type %s to %s", + src.type->name, dst.type->name); + eina_value_flush(&dst); + eina_value_setup(&dst, EINA_VALUE_TYPE_ERROR); + eina_value_set(&dst, ENOTSUP); + }// otherwise leave initial value for empty + + return dst; +} + +EAPI Eina_Future_Desc +eina_future_cb_convert_to(const Eina_Value_Type *type) +{ + return (Eina_Future_Desc){.cb = _eina_future_cb_convert_to, .data = type}; +} + +EAPI void * +eina_promise_data_get(const Eina_Promise *p) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL); + return (void *)p->data; +} + +static Eina_Value +_eina_future_cb_easy(void *data, const Eina_Value value, + const Eina_Future *dead_future) +{ + Eina_Future_Cb_Easy_Desc *d = data; + Eina_Value ret = EINA_VALUE_EMPTY; + if (!d) + { + if (eina_value_setup(&ret, EINA_VALUE_TYPE_ERROR)) eina_value_set(&ret, ENOMEM); + return ret; + } + EASY_FUTURE_DISPATCH(ret, value, dead_future, d, (void*)d->data); + free(d); + return ret; +} + +EAPI Eina_Future_Desc +eina_future_cb_easy_from_desc(const Eina_Future_Cb_Easy_Desc desc) +{ + Eina_Future_Cb_Easy_Desc *d = calloc(1, sizeof(Eina_Future_Cb_Easy_Desc)); + EINA_SAFETY_ON_NULL_GOTO(d, end); + *d = desc; + end: + return (Eina_Future_Desc){ .cb = _eina_future_cb_easy, .data = d }; +} + +typedef struct _Base_Ctx { + Eina_Promise *promise; + Eina_Future **futures; + unsigned int futures_len; +} Base_Ctx; + +typedef struct _All_Promise_Ctx { + Base_Ctx base; + Eina_Value values; + unsigned int processed; +} All_Promise_Ctx; + +typedef struct _Race_Promise_Ctx { + Base_Ctx base; + Eina_Bool dispatching; +} Race_Promise_Ctx; + +static void +_base_ctx_clean(Base_Ctx *ctx) +{ + unsigned int i; + for (i = 0; i < ctx->futures_len; i++) + if (ctx->futures[i]) _eina_future_cancel(ctx->futures[i], ECANCELED); + free(ctx->futures); +} + +static void +_all_promise_ctx_free(All_Promise_Ctx *ctx) +{ + _base_ctx_clean(&ctx->base); + eina_value_flush(&ctx->values); + free(ctx); +} + +static void +_all_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED) +{ + _all_promise_ctx_free(data); +} + +static void +_race_promise_ctx_free(Race_Promise_Ctx *ctx) +{ + _base_ctx_clean(&ctx->base); + free(ctx); +} + +static void +_race_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED) +{ + _race_promise_ctx_free(data); +} + +static Eina_Bool +_future_unset(Base_Ctx *ctx, unsigned int *pos, const Eina_Future *dead_ptr) +{ + unsigned int i; + + for (i = 0; i < ctx->futures_len; i++) + { + if (ctx->futures[i] == dead_ptr) + { + ctx->futures[i] = NULL; + *pos = i; + return EINA_TRUE; + } + } + return EINA_FALSE; +} + +static Eina_Value +_race_then_cb(void *data, const Eina_Value v, + const Eina_Future *dead_ptr) +{ + Race_Promise_Ctx *ctx = data; + Eina_Promise *p = ctx->base.promise; + Eina_Bool found, r; + Eina_Value result; + unsigned int i; + + //This is not allowed! + assert(v.type != &EINA_VALUE_TYPE_PROMISE); + found = _future_unset(&ctx->base, &i, dead_ptr); + assert(found); + + if (ctx->dispatching) return EINA_VALUE_EMPTY; + ctx->dispatching = EINA_TRUE; + + //By freeing the race_ctx all the other futures will be cancelled. + _race_promise_ctx_free(ctx); + + r = eina_value_struct_setup(&result, &RACE_STRUCT_DESC); + EINA_SAFETY_ON_FALSE_GOTO(r, err_setup); + r = eina_value_struct_set(&result, "value", v); + EINA_SAFETY_ON_FALSE_GOTO(r, err_set); + r = eina_value_struct_set(&result, "index", i); + EINA_SAFETY_ON_FALSE_GOTO(r, err_set); + //We're in a safe context (from mainloop), so we can avoid scheduling a new dispatch + _eina_promise_clean_dispatch(p, result); + return v; + + err_set: + eina_value_flush(&result); + err_setup: + eina_value_setup(&result, EINA_VALUE_TYPE_ERROR); + eina_value_set(&result, ENOMEM); + _eina_promise_clean_dispatch(p, result); + return v; +} + +static Eina_Value +_all_then_cb(void *data, const Eina_Value v, + const Eina_Future *dead_ptr) +{ + All_Promise_Ctx *ctx = data; + unsigned int i = 0; + Eina_Bool found; + + //This is not allowed! + assert(v.type != &EINA_VALUE_TYPE_PROMISE); + + found = _future_unset(&ctx->base, &i, dead_ptr); + assert(found); + + ctx->processed++; + eina_value_array_set(&ctx->values, i, v); + if (ctx->processed == ctx->base.futures_len) + { + //We're in a safe context (from mainloop), so we can avoid scheduling a new dispatch + _eina_promise_clean_dispatch(ctx->base.promise, ctx->values); + ctx->values = EINA_VALUE_EMPTY; /* flushed in _eina_promise_clean_dispatch() */ + _all_promise_ctx_free(ctx); + } + return v; +} + +static void +_future_array_cancel(Eina_Future *array[]) +{ + size_t i; + for (i = 0; array[i]; i++) _eina_future_cancel(array[i], ENOMEM); +} + +static Eina_Bool +promise_proxy_of_future_array_create(Eina_Future *array[], + Base_Ctx *ctx, + Eina_Promise_Cancel_Cb cancel_cb, + Eina_Future_Cb future_cb) +{ + unsigned int i; + + //Count how many futures... + for (i = 0; array[i] != EINA_FUTURE_SENTINEL; i++) + //NULL futures are not allowed. + EINA_SAFETY_ON_NULL_RETURN_VAL(array[i], EINA_FALSE); + + EINA_SAFETY_ON_FALSE_RETURN_VAL(i > 0, EINA_FALSE); + + ctx->promise = eina_promise_new(_scheduler_get(array[0]), cancel_cb, ctx); + EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->promise, EINA_FALSE); + + ctx->futures_len = i; + ctx->futures = calloc(ctx->futures_len, sizeof(Eina_Future *)); + EINA_SAFETY_ON_NULL_GOTO(ctx->futures, err_futures); + + for (i = 0; i < ctx->futures_len; i++) + { + ctx->futures[i] = eina_future_then(array[i], future_cb, ctx); + //Futures will be cancelled by the caller... + EINA_SAFETY_ON_NULL_GOTO(ctx->futures[i], err_then); + } + return EINA_TRUE; + + err_then: + //This will also unlink the prev future... + while (i >= 1) _eina_future_free(ctx->futures[--i]); + free(ctx->futures); + ctx->futures = NULL; + err_futures: + ctx->futures_len = 0; + eina_mempool_free(_promise_mp, ctx->promise); + ctx->promise = NULL; + return EINA_FALSE; +} + +EAPI Eina_Promise * +eina_promise_all_array(Eina_Future *array[]) +{ + All_Promise_Ctx *ctx; + unsigned int i; + Eina_Bool r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(array, NULL); + ctx = calloc(1, sizeof(All_Promise_Ctx)); + EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx); + r = eina_value_array_setup(&ctx->values, EINA_VALUE_TYPE_VALUE, 0); + EINA_SAFETY_ON_FALSE_GOTO(r, err_array); + r = promise_proxy_of_future_array_create(array, &ctx->base, + _all_promise_cancel, _all_then_cb); + EINA_SAFETY_ON_FALSE_GOTO(r, err_promise); + + for (i = 0; i < ctx->base.futures_len; i++) + { + Eina_Bool r; + Eina_Value v; + + //Stub values... + r = eina_value_setup(&v, EINA_VALUE_TYPE_INT); + EINA_SAFETY_ON_FALSE_GOTO(r, err_stub); + r = eina_value_array_append(&ctx->values, v); + eina_value_flush(&v); + EINA_SAFETY_ON_FALSE_GOTO(r, err_stub); + } + + return ctx->base.promise; + + err_stub: + for (i = 0; i < ctx->base.futures_len; i++) _eina_future_free(ctx->base.futures[i]); + free(ctx->base.futures); + eina_mempool_free(_promise_mp, ctx->base.promise); + err_promise: + eina_value_flush(&ctx->values); + err_array: + free(ctx); + err_ctx: + _future_array_cancel(array); + return NULL; +} + +EAPI Eina_Promise * +eina_promise_race_array(Eina_Future *array[]) +{ + Race_Promise_Ctx *ctx; + Eina_Bool r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(array, NULL); + ctx = calloc(1, sizeof(Race_Promise_Ctx)); + EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx); + r = promise_proxy_of_future_array_create(array, &ctx->base, + _race_promise_cancel, + _race_then_cb); + EINA_SAFETY_ON_FALSE_GOTO(r, err_promise); + + return ctx->base.promise; + + err_promise: + free(ctx); + err_ctx: + _future_array_cancel(array); + return NULL; +} + +static Eina_Value +_eina_future_cb_ignore_error(void *data, const Eina_Value value, + const Eina_Future *dead_future EINA_UNUSED) +{ + Eina_Error expected_err = (Eina_Error)(long)data; + + if (value.type == EINA_VALUE_TYPE_ERROR) + { + Eina_Error err; + eina_value_get(&value, &err); + if ((!expected_err) || (expected_err == err)) + { + DBG("ignored error %d (%s)", err, eina_error_msg_get(err)); + return EINA_VALUE_EMPTY; + } + } + return value; +} + +EAPI Eina_Future_Desc +eina_future_cb_ignore_error(Eina_Error err) +{ + return (Eina_Future_Desc){ _eina_future_cb_ignore_error, (void *)(long)err }; +} + +EAPI void +eina_future_desc_flush(Eina_Future_Desc *desc) +{ + if (!desc) return; + _fake_future_dispatch(*desc, ECANCELED); + memset(desc, 0, sizeof(Eina_Future_Desc)); +} + +EAPI void +eina_future_cb_easy_desc_flush(Eina_Future_Cb_Easy_Desc *desc) +{ + if (!desc) return; + + if (desc->error) + { + Eina_Value r; + + r = desc->error((void *)desc->data, ECANCELED); + eina_value_flush(&r); + } + + if (desc->free) desc->free((void *)desc->data, NULL); + memset(desc, 0, sizeof(Eina_Future_Cb_Easy_Desc)); +} + +static Eina_Value +_future_cb_log(void *data, const Eina_Value value, + const Eina_Future *dead EINA_UNUSED) +{ + Eina_Future_Cb_Log_Desc *ctx = data; + const char *content = "no value"; + char *str = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, value); + + if (value.type) + { + str = eina_value_to_string(&value); + content = str; + } + + eina_log_print(ctx->domain, ctx->level, + ctx->file ? ctx->file : "Unknown file", + ctx->func ? ctx->func : "Unknown function", + ctx->line, "%s%s%s", + ctx->prefix ? ctx->prefix : "", + content, + ctx->suffix ? ctx->suffix : ""); + free(str); + free((void *)ctx->func); + free((void *)ctx->file); + free((void *)ctx->prefix); + free((void *)ctx->suffix); + free(ctx); + return value; +} + +EAPI Eina_Future_Desc +eina_future_cb_log_from_desc(const Eina_Future_Cb_Log_Desc desc) +{ + Eina_Future_Cb_Log_Desc *ctx = calloc(1, sizeof(Eina_Future_Cb_Log_Desc)); + EINA_SAFETY_ON_NULL_GOTO(ctx, exit); + + if (desc.prefix) ctx->prefix = strdup(desc.prefix); + if (desc.suffix) ctx->suffix = strdup(desc.suffix); + if (desc.file) ctx->file = strdup(desc.file); + if (desc.func) ctx->func = strdup(desc.func); + ctx->domain = desc.domain; + ctx->level = desc.level; + ctx->line = desc.line; + + exit: + return (Eina_Future_Desc){ .cb = _future_cb_log, .data = ctx }; +} diff --git a/src/lib/eina/eina_promise.h b/src/lib/eina/eina_promise.h new file mode 100644 index 0000000000..43aa90ad16 --- /dev/null +++ b/src/lib/eina/eina_promise.h @@ -0,0 +1,1674 @@ +#ifndef _EINA_PROMISE_H_ +#define _EINA_PROMISE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "eina_safety_checks.h" +#include "eina_types.h" +#include "eina_value.h" + +/** + * @ingroup Eina_Promise + * + * @{ + */ +typedef struct _Eina_Future_Desc Eina_Future_Desc; +typedef struct _Eina_Promise Eina_Promise; +typedef struct _Eina_Future Eina_Future; +typedef struct _Eina_Future_Cb_Easy_Desc Eina_Future_Cb_Easy_Desc; +typedef struct _Eina_Future_Cb_Console_Desc Eina_Future_Cb_Console_Desc; +typedef struct _Eina_Future_Scheduler Eina_Future_Scheduler; +typedef struct _Eina_Future_Schedule_Entry Eina_Future_Schedule_Entry; +typedef struct _Eina_Future_Race_Result Eina_Future_Race_Result; +typedef struct _Eina_Future_Cb_Log_Desc Eina_Future_Cb_Log_Desc; + +/** + * @defgroup Eina_Future_Callbacks Efl Future Callbacks + * @ingroup eina_future + * @typedef Eina_Future_Cb Eina_Future_Cb + * + * A callback used to inform that a future was resolved. + * Usually this callback is called from a clean context, that is, from the + * main loop or some platform defined safe context. However there are + * 2 exceptions: + * + * @li eina_future_cancel() was used, it's called immediately in the + * context that called cancel using `ECANCELED` as error. + * + * @li eina_future_then(), eina_future_then_from_desc(), eina_future_chain(), eina_future_chain_array() + * or similar failed due invalid pointer or memory allocation. Then the callback is called from the + * failed context using `EINVAL` or `ENOMEM` as errors and @p dead_future will be @c NULL. + * + * @param data The data provided by the user + * + * @param value An Eina_Value which contains the operation result. Before using + * the @p value, its type must be checked in order to avoid errors. This is needed, because + * if an operation fails the Eina_Value type will be EINA_VALUE_TYPE_ERROR + * which is a different type than the expected operation result. + * + * @param dead_future A pointer to the future that was completed. + * + * @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). + * + * @note The returned value @b can be an EFL_VALUE_TYPE_PROMISE! (see eina_promise_as_value() and + * eina_future_as_value()) In this case the future chain will wait until the promise is resolved. + * + * @see eina_future_cancel() + * @see eina_future_then() + * @see eina_future_then_from_desc() + * @see eina_future_then_easy() + * @see eina_future_chain() + * @see eina_future_chain_array() + * @see eina_future_as_value() + * @see eina_promise_as_value() + * @{ + */ +typedef Eina_Value (*Eina_Future_Cb)(void *data, const Eina_Value value, const Eina_Future *dead_future); + +/** + * @struct _Eina_Future_Scheduler + * @ingroup eina_promise + * + * A struct that represents an scheduled event. + * This struct may be used by Eina to cancel + * a scheduled future. + * + * @see eina_promise_new() + * + * @see #Eina_Future_Scheduler + * @see #Eina_Future_Scheduler_Cb + */ +struct _Eina_Future_Schedule_Entry { + /** + * The scheduler used to create the entry. + * @note This must not be @c NULL. + */ + Eina_Future_Scheduler *scheduler; +}; + + +/** + * @typedef Eina_Future_Scheduler_Cb + * @ingroup eina_promise + * A callback used by the Eina_Future_Scheduler to deliver + * the future operation result. + * + * @param f The delivered future. + * @param value The future result + * + * + * @see eina_promise_new() + * + * @see #Eina_Future_Schedule_Entry + * @see #Eina_Future_Scheduler + */ +typedef void (*Eina_Future_Scheduler_Cb)(Eina_Future *f, Eina_Value value); + +/** + * @struct _Eina_Future_Scheduler + * @ingroup eina_promise + * This struct is used as a bridge between Eina and the future scheduler. + * By using the functions provided by #_Eina_Future_Scheduler Eina can + * schedule futures resolutions, rejections and cancelations to a safe context. + * + * @see eina_promise_new() + * @see #Eina_Future_Schedule_Entry + * @see #Eina_Future_Scheduler_Cb + */ +struct _Eina_Future_Scheduler { + /** + * Called by @p Eina_Future when a delivery must be scheduled to a safe context. + * ie: after @p eina_promise_resolve() + * + * @note Must not be @c NULL + * + * Must call back from a safe context using @p cb(f,value) + * @param scheduler The scheduler to use. + * @param cb The #Eina_Future_Scheduler_Cb to be called and deliver the @p f and @p value. + * @param f The future to be delivered to @p cb + * @param value The value to be delivered to @p cb + * @return A scheduled entry or @c NULL on error + */ + Eina_Future_Schedule_Entry *(*schedule)(Eina_Future_Scheduler *scheduler, Eina_Future_Scheduler_Cb cb, Eina_Future *f, Eina_Value value); + /** + * Called by @p Eina_Future when a delivery must be cancelled. + * ie: after @p eina_future_cancel() + * + * @note Must not be @c NULL. + * + * @param entry The scheduled event to cancel + */ + void (*recall)(Eina_Future_Schedule_Entry *entry); +}; + +/** + * @typedef Eina_Promise_Cancel_Cb Eina_Promise_Cancel_Cb. + * @ingroup eina_promise + * + * A callback used to inform that a promise was canceled. + * + * A promise may be canceled by the user calling `eina_future_cancel()` + * on any Eina_Future that is part of the chain that uses an Eina_Promise, + * that will cancel the whole chain and then the promise. + * + * It should stop all asynchronous operations or at least mark them to be + * discarded instead of resolved. Actually it can't be resolved once + * cancelled since the given pointer @c dead_promise is now invalid. + * + * @note This callback is @b mandatory for a reason, do not provide an empty + * callback as it'll likely result in memory corruption and invalid access. + * If impossible to cancel an asynchronous task, then create an + * intermediate memory to hold Eina_Promise and make it @c NULL, + * in this callback. Then prior to resolution check if the pointer is set. + * + * @note This callback is @b not called if eina_promise_resolve() or + * eina_promise_reject() are used. If any cleanup is needed, then + * call yourself. It's only meant as cancellation, not a general + * "free callback". + * + * @param data The data provided by the user. + * @param dead_promise The canceled promise. + * @see eina_promise_reject() + * @see eina_promise_resolve() + * @see eina_future_cancel() + */ +typedef void (*Eina_Promise_Cancel_Cb) (void *data, const Eina_Promise *dead_promise); + +/** + * @typedef Eina_Future_Success_Cb Eina_Future_Success_Cb. + * @ingroup eina_future + * + * A callback used to inform that the future completed with success. + * + * Unlike #Eina_Future_Cb this callback only called if the future operation was successful, this is, + * no errors occurred (@p value type differs from EINA_VALUE_TYPE_ERROR) + * and the @p value matches #_Eina_Future_Cb_Easy_Desc::success_type (if given). + * In case #_Eina_Future_Cb_Easy_Desc::success_type was not supplied (it's @c NULL) the @p value type + * must be checked before using it. + * + * @note This function is always called from a safe context (main loop or some platform defined safe context). + * + * @param data The data provided by the user. + * @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). + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + */ +typedef Eina_Value (*Eina_Future_Success_Cb)(void *data, const Eina_Value value); + +/** + * @typedef Eina_Future_Error_Cb Eina_Future_Error_Cb. + * @ingroup eina_future + * + * A callback used to inform that the future completed with failure. + * + * Unlike #Eina_Future_Success_Cb this function is only called when an error + * occurs during the future process or when #_Eina_Future_Cb_Easy_Desc::success_type + * differs from the future result. + * 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. + * + * If it was possible to recover from an error this function should return an empty value + * `return EINA_VALUE_EMPTY;` or any other Eina_Value type that differs from EINA_VALUE_TYPE_ERROR. + * In this case the error will not be reported by the other futures in the chain (if any), otherwise + * if an Eina_Value type EINA_VALUE_TYPE_ERROR is returned the error will continue to be reported + * to the other futures in the chain. + * + * @param data The data provided by the user. + * @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). + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + */ +typedef Eina_Value (*Eina_Future_Error_Cb)(void *data, const Eina_Error error); + +/** + * @typedef Eina_Future_Free_Cb Eina_Future_Free_Cb. + * @ingroup eina_future + * + * A callback used to inform that the future was freed and the user should also @c free the @p data. + * This callback may be called from an unsafe context if the future was canceled or an error + * occurred. + * + * @note This callback is always called, even if #Eina_Future_Error_Cb and/or #Eina_Future_Success_Cb + * were not provided, which can also be used to monitor when a future ends. + * + * @param data The data provided by the user. + * @param dead_future The future that was freed. + * + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + */ +typedef void (*Eina_Future_Free_Cb)(void *data, const Eina_Future *dead_future); + +/** + * @struct _Eina_Future_Cb_Easy_Desc + * @ingroup eina_future + * + * A struct with callbacks to be used by eina_future_cb_easy_from_desc() and eina_future_cb_easy() + * + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + */ +struct _Eina_Future_Cb_Easy_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. + */ + Eina_Future_Success_Cb success; + /** + * 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. + */ + Eina_Future_Error_Cb 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. + */ + Eina_Future_Free_Cb free; + /** + * 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; + /** + * Context data given to every callback. + * + * This must be freed @b only by @c free callback as it's called from every case, + * otherwise it may lead to memory leaks. + */ + const void *data; +}; + +/** + * @struct _Eina_Future_Cb_Console_Desc + * @ingroup eina_future + * + * A struct used to define the prefix and suffix to be printed + * along side the a future value. This struct is used by + * eina_future_cb_console_from_desc() + * + * @see eina_future_cb_console_from_desc() + */ +struct _Eina_Future_Cb_Console_Desc { + /** + * The prefix to be printed. If @c NULL an empty string ("") is used. + */ + const char *prefix; + /** + * The suffix the be printed. If @c NULL '\n' is used. + */ + const char *suffix; +}; + +/** + * @struct _Eina_Future_Cb_Log_Desc + * @ingroup eina_future + * + * This struct is used by eina_future_cb_log_from_desc() and + * its contents will be routed to eina_log_print() along side + * the future value. + * + * @see eina_future_cb_log_from_desc() + */ +struct _Eina_Future_Cb_Log_Desc { + /** + * The prefix to be printed. If @c NULL an empty string ("") is used. + */ + const char *prefix; + /** + * The suffix the be printed. If @c NULL '\n' is used. + */ + const char *suffix; + /** + * The file name to be passed to eina_log_print(). if @c NULL "Unknown file" is used. + */ + const char *file; + /** + * The file name to be passed to eina_log_print(). if @c NULL "Unknown function" is used. + */ + const char *func; + /** + * The Eina_Log_Level to use. + */ + Eina_Log_Level level; + /** + * The log domain to use. + */ + int domain; + /** + * The line number to be passed to eina_log_print(). + */ + int line; +}; + +/** + * @struct _Eina_Future_Desc + * @ingroup eina_future + * A struct used to define a callback and data for a future. + * + * This struct contains a future completion callback and a data to the future + * completion callback which is used by eina_future_then(), eina_future_chain() + * and friends to inform the user about the future result. The #_Eina_Future_Desc::data + * variable should be freed when #_Eina_Future_Desc::cb is called, otherwise it will leak. + * + * @note If eina_future_then(), eina_future_chain() and friends fails to return a valid future + * (in other words: @c NULL is returned) the #_Eina_Future_Desc::cb will be called + * report an error like `EINVAL` or `ENOMEM` so #_Eina_Future_Desc::data can be freed. + * + * @see eina_future_then() + * @see eina_future_chain_array() + * @see eina_future_cb_convert_to() + * @see eina_future_cb_console_from_desc() + * @see eina_future_cb_ignore_error() + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_log_from_desc() + */ +struct _Eina_Future_Desc { + /** + * Called when the future is resolved or rejected. + * + * Once a future is resolved or rejected this function is called passing the future result + * to inform the user that the future operation has ended. Normally this + * function is called from a safe context (main loop or some platform defined safe context), + * however in case of a future cancellation (eina_future_cancel()) or if eina_future_then(), + * eina_future_chain() and friends fails to create a new future, + * this function is called from the current context. + * + * Use this function to free @p data if necessary. + * @see eina_future_chain() + * @see eina_future_then() + * @see eina_future_cancel() + */ + Eina_Future_Cb cb; + /** + * Context data to @p cb. The @p data should be freed inside @p cb, otherwise + * its memory will leak! + */ + const void *data; + + /** + * The storage will be used by Eina to store a pointer to the + * created future. It can be @c NULL. + */ + Eina_Future **storage; +}; + +/** + * @} + */ + +/** + * @defgroup Eina_Promise Eina_Promise + * Creates a new promise. + * + * This function creates a new promise which can be used to create a future + * using eina_future_new(). Everytime a promise is created a #Eina_Promise_Cancel_Cb + * must be provided which is used to free resources that were created. + * + * A promise may be cancelled directly by calling + * @c eina_future_cancel(eina_future_new(eina_promise_new(...))) + * that is, cancelling any future that is chained to receive the results. + * + * However promises can be cancelled indirectly by other entities. + * These other entities will call `eina_future_cancel()` themselves, + * however you may not be aware of that. Some common sources + * of indirect cancellations: + * + * @li A subsystem was shutdown, cancelling all pending futures (ie: ecore_shutdown()) + * + * @li An EO object was linked to the promise or future, then if the object dies (last reference + * is gone), then the pending promises and futures will be cancelled. + * + * @li Some other entity (library provider or library user) chained and cancelled his future, + * which will result in your future being cancelled. + * + * Since a promise may be canceled indirectaly (by code sections that goes beyond your scope) + * you should always provide a cancel callback, even if you think you'll not need it. + * + * Below there's a typical example: + * + * @code + * #include <Ecore.h> + * + * static void + * _promise_cancel(void *data, Eina_Promise *p EINA_UNUSED) + * { + * Ctx *ctx = data; + * //In case the promise is canceled we must stop the timer! + * ecore_timer_del(ctx->timer); + * free(ctx); + * } + * + * static Eina_Bool + * _promise_resolve(void *data) + * { + * Ctx *ctx = data; + * Eina_Value v; + * eina_value_setup(&v, EINA_VALUE_TYPE_STRING); + * eina_value_set(&v, "Promise resolved"); + * eina_promise_resolve(ctx->p, v); + * free(ctx); + * return EINA_FALSE; + * } + * + * Eina_Promise * + * promise_create(Eina_Future_Scheduler *scheduler) + * { + * Ctx *ctx = malloc(sizeof(Ctx)); + * //A timer is scheduled in order to resolve the promise + * ctx->timer = ecore_timer_add(122, _promise_resolve, ctx); + * //The _promise_cancel() will be used to clean ctx if the promise is canceled. + * ctx->p = eina_promise_new(scheduler, _promise_cancel, ctx); + * return ctx->p; + * } + * @endcode + * + * @param cancel_cb A callback used to inform that the promise was canceled. Use + * this callback to @c free @p data. @p cancel_cb must not be @c NULL ! + * @param data Data to @p cancel_cb. + * @return A promise or @c NULL on error. + * @see eina_future_cancel() + * @see eina_future_new() + * @see eina_promise_resolve() + * @see eina_promise_reject() + * @see eina_promise_data_get() + * @see eina_promise_as_value() + * @see #Eina_Future_Scheduler + * @see #Eina_Future_Scheduler_Entry + * @see #Eina_Future_Scheduler_Cb + * @{ + */ +EAPI Eina_Promise *eina_promise_new(Eina_Future_Scheduler *scheduler, Eina_Promise_Cancel_Cb cancel_cb, const void *data) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT; + +/** + * Gets the data attached to the promise. + * + * @return The data passed to eina_promise_new() or @c NULL on error. + * @see eina_promise_new() + * @see eina_promise_resolve() + * @see eina_promise_reject() + * @see eina_promise_as_value() + */ +EAPI void *eina_promise_data_get(const Eina_Promise *p) EINA_ARG_NONNULL(1); + +/** + * Resolves a promise. + * + * + * This function schedules an resolve event in a + * safe context (main loop or some platform defined safe context), + * whenever possible the future callbacks will be dispatched. + * + * @param p A promise to resolve. + * @param value The value to be delivered. 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). + * + * @see eina_promise_new() + * @see eina_promise_reject() + * @see eina_promise_data_get() + * @see eina_promise_as_value() + */ +EAPI void eina_promise_resolve(Eina_Promise *p, Eina_Value value) EINA_ARG_NONNULL(1); +/** + * Rejects a promise. + * + * This function schedules an reject event in a + * safe context (main loop or some platform defined safe context), + * whenever possible the future callbacks will be dispatched. + * + * @param p A promise to reject. + * @param err An Eina_Error value + * @note Internally this function will create an Eina_Value with type #EINA_VALUE_TYPE_ERROR. + * + * @see eina_promise_new() + * @see eina_promise_resolve() + * @see eina_promise_data_get() + * @see eina_promise_as_value() + */ +EAPI void eina_promise_reject(Eina_Promise *p, Eina_Error err) EINA_ARG_NONNULL(1); + + +/** + * @} + */ + +/** + * @defgroup Eina_Future Eina_Future + * Cancels a future. + * + * This function will cancel the whole future chain immediately (it will not be schedule to the next mainloop pass) + * and it will also cancel the promise linked against it. The Eina_Future_Cb will be called + * with an Eina_Value typed as #EINA_VALUE_TYPE_ERROR, which its value will be + * ECANCELED + * @param f The future to cancel. + * @{ + */ +EAPI void eina_future_cancel(Eina_Future *f) EINA_ARG_NONNULL(1); + +/** + * Flushes an #Eina_Future_Desc + * + * This function is mainly used by bindings to flush a #Eina_Future_Desc. + * It will call the #Eina_Future_Cb with `ENOMEM` and zero the @p desc contents. + * + * @param desc The #Eina_Future_Desc to flush, if @c NULL this is a noop. + */ +EAPI void eina_future_desc_flush(Eina_Future_Desc *desc); + +/** + * Flushes an #Eina_Future_Cb_Easy_Desc + * + * This function is mainly used by bindings to flush a #Eina_Future_Cb_Easy_Desc. + * It will call the #Eina_Future_Error_Cb with `ENOMEM`, the #Eina_Future_Free_Cb and + * zero the @p desc contents. + * + * @param desc The #Eina_Future_Cb_Easy_Desc to flush, if @c NULL this is a noop. + */ +EAPI void eina_future_cb_easy_desc_flush(Eina_Future_Cb_Easy_Desc *desc); + +/** + * Creates a new Eina_Value from a promise. + * + * This function creates a new Eina_Value that will store a promise + * in it. This function is useful for dealing with promises inside + * a #Eina_Future_Cb. By returning an Promise Eina_Value the + * whole chain will wait until the promise is resolved in + * order to continue its execution. Example: + * + * @code + * static Eina_Value + * _file_data_ready(const *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead EINA_UNUSED) + * { + * const char *file_data; + * Eina_Promise *p; + * //It was not possible to fetch the file size. + * if (v.type == EINA_VALUE_TYPE_ERROR) + * { + * Eina_Error err; + * eina_value_get(&v, &err); + * fprintf(stderr, "Could get the file data. Reason: %s\n", eina_error_msg_get(err)); + * ecore_main_loop_quit(); + * return v; + * } + * else if (v.type != EINA_VALUE_TYPE_STRING) + * { + * fprintf(stderr, "Expecting type '%s' - received '%s'\n", EINA_VALUE_TYPE_STRING->name, v.type->name); + * ecore_main_loop_quit(); + * return v; + * } + * + * eina_value_get(&v, &file_data); + * //count_words will count the words in the background and resolve the promise once it is over... + * p = count_words(file_data); + * return eina_promise_as_value(p); + * } + * + * static Eina_Value + * _word_count_ready(const *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead EINA_UNUSED) + * { + * //The _word_count_ready will only be called once count_words() resolves/rejects the promise returned by _file_data_ready() + * int count; + * if (v.type == EINA_VALUE_TYPE_ERROR) + * { + * Eina_Error err; + * eina_value_get(&v, &err); + * fprintf(stderr, "Could not count the file words. Reason: %s\n", eina_error_msg_get(err)); + * ecore_main_loop_quit(); + * return v; + * } + * else if (v.type != EINA_VALUE_TYPE_INT) + * { + * fprintf(stderr, "Expecting type '%s' - received '%s'\n", EINA_VALUE_TYPE_INT->name, v.type->name); + * ecore_main_loop_quit(); + * return v; + * } + * eina_value_get(&v, &count); + * printf("File word count %d\n", count); + * return v; + * } + * + * void + * file_operation(void) + * { + * Eina_Future *f = get_file_data("/MyFile.txt"); + * eina_future_chain(f, {.cb = _file_data_ready, .data = NULL}, + * {.cb = _word_count_ready, .data = NULL}); + * } + * @endcode + * + * @return An Eina_Value. On error the value's type will be @c NULL. + * @note If an error happens the promise will be CANCELED. + * @see eina_future_as_value() + * @see eina_promise_reject() + * @see eina_promise_resolve() + */ +EAPI Eina_Value eina_promise_as_value(Eina_Promise *p) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT; +/** + * Creates an Eina_Value from a future. + * + * This function is used for the same purposes as eina_promise_as_value(), + * but receives an Eina_Future instead. + * + * @param f A future to create a Eina_Value from. + * @return An Eina_Value. On error the value's type will be @c NULL. + * @note If an error happens the future @p f will be CANCELED + * @see eina_promise_as_value() + */ +EAPI Eina_Value eina_future_as_value(Eina_Future *f)EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT; + +/** + * Creates a new future. + * + * This function creates a new future and can be used to report + * that an operation has succeded or failed using + * eina_promise_resolve() or eina_promise_reject(). + * + * Futures can also be canceled using eina_future_cancel(), which + * will cause the whole chain to be cancelled alongside with any pending promise. + * + * @note A promise can only have one future attached to it, calling + * eina_future_new() on the same promise twice will + * result in an error (@c NULL is returned) and the future + * attached to the promise will be canceled! + * + * @param p A promise used to attach a future. May not be @c NULL. + * @return The future or @c NULL on error. + * @note If an error happens this function will CANCEL the promise. + * @see eina_promise_new() + * @see eina_promise_reject() + * @see eina_promise_resolve() + * @see eina_future_cancel() + */ +EAPI Eina_Future *eina_future_new(Eina_Promise *p) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT; +/** + * Register an Eina_Future_Desc to be used when the future is resolve/rejected. + * + * With this function a callback and data is attached to the future and then + * once it's resolved or rejected the callback will be informed. + * + * If during the future creation an error happens this function will return @c NULL, + * and the #Eina_Future_Desc::cb will be called reporting an error (`EINVAL` or `ENOMEM`), + * so the user has a chance to free #Eina_Future_Desc::data. + * + * In case a future in the chain is canceled, the whole chain will be canceled immediately + * and the error `ECANCELED` will be reported. + * + * Below there's a simple usage of this function. + * + * @code + * static Eina_Value + * _file_ready(const *data, const Eina_Value v, const Eina_Future *dead EINA_UNUSED) + * { + * Ctx *ctx = data; + * //It was not possible to fetch the file size. + * if (v.type == EINA_VALUE_TYPE_ERROR) + * { + * Eina_Error err; + * eina_value_get(&v, &err); + * fprintf(stderr, "Could not read the file size. Reason: %s\n", eina_error_msg_get(err)); + * ecore_main_loop_quit(); + * return v; + * } + * else if (v.type != EINA_VALUE_TYPE_INT) + * { + * fprintf(stderr, "Expecting type '%s' - received '%s'\n", EINA_VALUE_TYPE_INT->name, v.type->name); + * ecore_main_loop_quit(); + * return v; + * } + * eina_value_get(&v, &ctx->size); + * printf("File size is %d bytes\n", ctx->size); + * return v; + * } + * + * void + * file_operation(void) + * { + * Eina_Future *f = get_file_size_async("/MyFile.txt"); + * eina_future_then_from_desc(f, (const Eina_Future_Desc){.cb = _size_ready, .data = NULL}); + * //There's a helper macro called eina_future_then() which simplifies the usage. + * //The code below has the same effect. + * //eina_future_then(f, _size_ready, NULL); + * } + * @endcode + * + * Although the code presented at `_size_ready()` is very simple, most of it + * is just used to check the Eina_Value type. In order + * to avoid this type of code the function eina_future_cb_easy_from_desc() + * was created. Please, check its documentation for more information. + * + * This function can also be used to create a future chain, making + * it possible to execute the future result in a cascade order. When + * using a future chain the Eina_Value returned by a #Eina_Future_Desc::cb + * will be delivered to the next #Eina_Future_Desc::cb in the chain. + * + * Here's an example: + * + * static Eina_Value + * _future_cb1(const *data EINA_UNUSED, const Eina_Value v) + * { + * Eina_Value new_v; + * int i; + * + * //There's no need to check the Eina_Value type since we're using eina_future_cb_easy() + * eina_value_get(&v, &i); + * printf("File size as int: %d\n", i); + * eina_value_setup(&new_v, EINA_VALUE_TYPE_STRING); + * //Convert the file size to string + * eina_value_convert(&v, &new_v); + * return new_v; + * } + * + * static Eina_Value + * _future_cb2(const *data EINA_UNUSED, const Eina_Value v) + * { + * Eina_Value new_v; + * const char *file_size_str; + * + * //There's no need to check the Eina_Value type since we're using eina_future_cb_easy() + * eina_value_get(&v, &file_size_str); + * printf("File size as string: %s\n", file_size_str); + * eina_value_setup(&new_v, EINA_VALUE_TYPE_DOUBLE); + * eina_value_convert(&v, &new_v); + * return new_v; + * } + * + * static Eina_Value + * _future_cb3(const *data EINA_UNUSED, const Eina_Value v) + * { + * double d; + * + * //There's no need to check the Eina_Value type since we're using eina_future_cb_easy() + * eina_value_get(&v, &d); + * printf("File size as double: %g\n", d); + * return v; + * } + * + * static Eina_Value + * _future_err(void *data EINA_UNUSED, Eina_Error err) + * { + * //This function is called if future result type does not match or another error occurred + * Eina_Value new_v; + * eina_value_setup(&new_v, EINA_VALUE_TYPE_ERROR); + * eina_valuse_set(&new_v, err); + * fprintf(stderr, "Error during future process. Reason: %s\n", eina_error_msg_get(err)); + * //Pass the error to the next future in the chain.. + * return new_v; + * } + * + * @code + * void chain(void) + * { + * Eina_Future *f = get_file_size_async("/MyFile.txt"); + * f = eina_future_then_easy(f, .success = _future_cb1, .success_type = EINA_VALUE_TYPE_INT); + * //_future_cb2 will be executed after _future_cb1() + * f = eina_future_then_easy(f, .success = _future_cb2, .success_type = EINA_VALUE_TYPE_STRING); + * //_future_cb2 will be executed after _future_cb2() + * f = eina_future_then_easy(f, .success = _future_cb3, .success_type = EINA_VALUE_TYPE_DOUBLE); + * //If an error happens _future_err will be called + * eina_future_then_easy(f, .error = _future_err); + * } + * @endcode + * + * Although it's possible to create a future chain using eina_future_then()/eina_future_then_from_desc() + * there's a function that makes this task much easier, please check eina_future_chain_array() for more + * information. + * @note This example does manual conversion and print, however we offer + * eina_future_cb_convert_to() and eina_future_cb_console_from_desc() and to make those common case easier. + * + * @param prev A future to link against + * @param desc A description struct contaning the callback and data. + * @return A new future or @c NULL on error. + * @note If an error happens the whole future chain will CANCELED and + * desc.cb will be called in order to free desc.data. + * @see eina_future_new() + * @see eina_future_then() + * @see #Eina_Future_Desc + * @see eina_future_chain_array() + * @see eina_future_chain() + * @see eina_future_cb_console_from_desc() + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + * @see eina_future_cb_convert_to() + * @see eina_future_cancel() + * @see eina_future_then_easy() + * @see eina_future_cb_log_from_desc() + */ +EAPI Eina_Future *eina_future_then_from_desc(Eina_Future *prev, const Eina_Future_Desc desc) EINA_ARG_NONNULL(1); + + +/** + * Creates an Eina_Future_Desc that will log the previous future resolved value. + * + * This function can be used to quickly log the value that an #Eina_Future_Desc::cb + * is returning. The returned value will be passed to the next future in the chain without + * modifications. + * + * There're some helper macros like eina_future_cb_log_dbg() which will automatically + * fill the following fields: + * + * @li #Eina_Future_Cb_Log_Desc::file: The __FILE__ function will be used. + * @li #Eina_Future_Cb_Log_Desc::func: The __FUNCTION__ macro will be used. + * @li #Eina_Future_Cb_Log_Desc::level: EINA_LOG_LEVEL_DBG will be used. + * @li #Eina_Future_Cb_Log_Desc::domain: EINA_LOG_DOMAIN_DEFAULT will be used. + * @li #Eina_Future_Cb_Log_Desc::line: The __LINE__ macro will be used. + * + * + * @param desc The description data to be used. + * @return An #Eina_Future_Desc + * + * @see #Eina_Future_Cb_Log_Desc + * @see efl_future_then() + * @see efl_future_chain() + * @see eina_future_cb_log_dbg() + * @see eina_future_cb_log_crit() + * @see eina_future_cb_log_err() + * @see eina_future_cb_log_info() + * @see eina_future_cb_log_warn() + * @see eina_future_cb_console_from_desc() + */ +EAPI Eina_Future_Desc eina_future_cb_log_from_desc(const Eina_Future_Cb_Log_Desc desc) EINA_WARN_UNUSED_RESULT; + +/** + * Creates a future chain. + * + * + * This behaves exactly like eina_future_then_from_desc(), but makes it easier to create future chains. + * + * If during the future chain creation an error happens this function will return @c NULL, + * and the #Eina_Future_Desc::cb from the @p descs array will be called reporting an error (`EINVAL` or `ENOMEM`), + * so the user has a chance to free #Eina_Future_Desc::data. + * + * Just like eina_future_then_from_desc(), the value returned by a #Eina_Future_Desc::cb callback will + * be delivered to the next #Eina_Future_Desc::cb in the chain. + * + * In case a future in the chain is canceled, the whole chain will be canceled immediately + * and the error `ECANCELED` will be reported. + * + * Here's an example: + * + * @code + * + * //callbacks code.... + * + * Eina_Future* chain(void) + * { + * Eina_Future *f = get_file_size_async("/MyFile.txt"); + * return eina_future_chain(f, eina_future_cb_easy(_future_cb1, _future_err, NULL, EINA_VALUE_TYPE_INT, NULL), + * eina_future_cb_easy(_future_cb2, _future_err, NULL, EINA_VALUE_TYPE_STRING, NULL), + * eina_future_cb_easy(_future_cb3, _future_err, NULL, EINA_VALUE_TYPE_DOUBLE, NULL), + * {.cb = _future_cb4, .data = NULL }); + * } + * @endcode + * + * @param prev The previous future + * @param descs An array of descriptions. The last element of the array must have the #Eina_Future_Desc::cb set to @c NULL + * @return A future or @c NULL on error. + * @note If an error happens the whole future chain will CANCELED and + * desc.cb will be called in order to free desc.data. + * @see eina_future_new() + * @see eina_future_then() + * @see #Eina_Future_Desc + * @see eina_future_chain() + * @see eina_future_cb_ignore_error() + * @see eina_future_cb_console_from_desc() + * @see eina_future_cb_log_from_desc() + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + * @see eina_future_then_from_desc() + * @see eina_future_then_easy() + * @see eina_future_cb_convert_to() + */ +EAPI Eina_Future *eina_future_chain_array(Eina_Future *prev, const Eina_Future_Desc descs[]) EINA_ARG_NONNULL(1, 2); + + +/** + * + * Wrappper around eina_future_chain_array() and eina_future_cb_easy_from_desc() + * + * This functions makes it easier to use eina_future_chain_array() with eina_future_cb_easy_from_desc(), + * check the macro eina_future_chain_easy() for an syntax sugar. + * + * + * @param prev The previous future + * @param descs An array of descriptions. The last element of the array must have the #Eina_Future_Desc::cb set to @c NULL + * @return A future or @c NULL on error. + * @note If an error happens the whole future chain will CANCELED and + * desc.cb will be called in order to free desc.data. + * @see eina_future_chain_easy() + * @see eina_future_chain() + * @see eina_future_cb_easy() + * @see eina_future_chain_array() + * @see eina_future_cb_easy_from_desc() + */ +EAPI Eina_Future *eina_future_chain_easy_array(Eina_Future *prev, const Eina_Future_Cb_Easy_Desc descs[]) EINA_ARG_NONNULL(1, 2); + +/** + * Creates an Eina_Future_Desc that will print the previous future resolved value. + * + * This function can be used to quickly inspect the value that an #Eina_Future_Desc::cb + * is returning. The returned value will be passed to the next future in the chain without + * modifications. + * + * There's also an helper macro called eina_future_cb_console() which makes this + * fuction easier to use. + * + * Example: + * + * @code + * + * eina_future_chain(a_future, {.cb = cb1, .data = NULL}, + * //Inspect the cb1 value and pass to cb2 without modifications + * eina_future_cb_console("cb1 value:"), + * {.cb = cb2, .data = NULL}, + * //Inspect the cb2 value + * eina_future_cb_console("cb2 value:", " cb2 value suffix\n")) + * @endcode + * + * @param prefix A Prefix to print, if @c NULL an empty string ("") is used. + * @param suffix A suffix to print. If @c NULL '\n' will be used. If suffix is provided + * the '\n' must be provided by suffix otherwise the printed text will not contain + * a line feed. + * @return An #Eina_Future_Desc + * @see eina_future_then() + * @see #Eina_Future_Desc + * @see eina_future_chain() + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + * @see eina_future_then_from_desc() + * @see eina_future_then_easy() + * @see eina_future_cb_console() + * @see eina_future_cb_ignore_error() + * @see eina_future_cb_log_from_desc() + */ +EAPI Eina_Future_Desc eina_future_cb_console_from_desc(const Eina_Future_Cb_Console_Desc desc) EINA_WARN_UNUSED_RESULT; + +/** + * Returns a #Eina_Future_Desc that ignores an error. + * + * This function may be used if one wants to ignore an error. If the error + * specified error happens an EINA_VALUE_EMPTY will be delivered to the + * next future in the chain. + * + * @param err The error to be ignored. + * @param A future descriptor to be used with eina_future_then() or eina_future_chain() + */ +EAPI Eina_Future_Desc eina_future_cb_ignore_error(Eina_Error err); + +/** + * Creates an #Eina_Future_Desc which will convert the the received eina value to a given type. + * + * @param type The Eina_Value_Type to convert to. + * @return An #Eina_Future_Desc + * @see eina_future_then() + * @see #Eina_Future_Desc + * @see eina_future_chain() + * @see eina_future_cb_easy_from_desc() + * @see eina_future_cb_easy() + * @see eina_future_then_from_desc() + * @see eina_future_then_easy() + */ +EAPI Eina_Future_Desc eina_future_cb_convert_to(const Eina_Value_Type *type); + +/** + * Creates an #Eina_Future_Desc based on a #Eina_Future_Cb_Easy_Desc + * + * This function aims to be used in conjuction with eina_future_chain(), + * eina_future_then_from_desc() and friends and its main objective is to simplify + * error handling and Eina_Value type checks. + * It uses three callbacks to inform the user about the future's + * result and life cycle. They are: + * + * @li #Eina_Future_Cb_Easy_Desc::success: This callback is called when + * the future execution was successful, this is, no errors occurred and + * the result type matches #Eina_Future_Cb_Easy_Desc::success_type. In case + * #Eina_Future_Cb_Easy_Desc::success_type is @c NULL, this function will + * only be called if the future did not report an error. The value returned + * by this function will be propagated to the next future in the chain (if any). + * + * @li #Eina_Future_Cb_Easy_Desc::error: This callback is called when + * the future result is an error or #Eina_Future_Cb_Easy_Desc::success_type + * does not match the future result type. The value returned + * by this function will be propagated to the next future in the chain (if any). + * + * @li #Eina_Future_Cb_Easy_Desc::free: Called after the future was freed and any resources + * allocated must be freed at this point. This callback is always called. + * + * Check the example below for a sample usage: + * + * @code + * static Eina_Value + * _file_size_ok(void *data, Eina_Value v) + * { + * Ctx *ctx = data; + * //Since an Eina_Future_Cb_Easy_Desc::success_type was provided, there's no need to check the value type + * int s; + * eina_value_get(&v, &s); + * printf("File size is %d bytes\n", s); + * ctx->file_size = s; + * return v; + * } + * + * static Eina_Value + * _file_size_err(void *data, Eina_Error err) + * { + * fprintf(stderr, "Could not read the file size. Reason: %s\n", eina_error_msg_get(err)); + * //Stop propagating the error. + * return EINA_VALUE_EMPTY; + * } + * + * static void + * _future_freed(void *data, const Eina_Future dead) + * { + * Ctx *ctx = data; + * printf("Future %p deleted\n", dead); + * ctx->file_size_handler_cb(ctx->file_size); + * free(ctx); + * } + * + * @code + * void do_work(File_Size_Handler_Cb cb) + * { + * Ctx *ctx = malloc(sizeof(Ctx)); + * ctx->f = get_file_size("/tmp/todo.txt"); + * ctx->file_size = -1; + * ctx->file_size_handler_cb = cb; + * eina_future_then_easy(f, _file_size_ok, _file_size_err, _future_freed, EINA_VALUE_TYPE_INT, ctx); + * } + * @endcode + * + * @return An #Eina_Future_Desc + * @see eina_future_chain() + * @see eina_future_then() + * @see eina_future_cb_easy() + */ +EAPI Eina_Future_Desc eina_future_cb_easy_from_desc(const Eina_Future_Cb_Easy_Desc desc) EINA_WARN_UNUSED_RESULT; +/** + * Creates an all promise. + * + * Creates a promise that is resolved once all the futures + * from the @p array are resolved. + * The promise is resolved with an Eina_Value type array which + * contains EINA_VALUE_TYPE_VALUE elements. The result array is + * ordered according to the @p array argument. Example: + * + * @code + * + * static const char * + * _get_operation_name_by_index(int idx) + * { + * switch (idx) + * { + * case 0: return "Get file data"; + * case 1: return "Get file size"; + * default: return "sum"; + * } + * } + * + * static Eina_Value + * _all_cb(const void *data EINA_UNUSED, const Eina_Value array, const Eina_Future *dead EINA_UNUSED) + * { + * Eina_Error err; + * unsined int i, len; + * + * if (v.type == EINA_VALUE_TYPE_ERROR) + * { + * eina_value_get(&array, &err); + * fprintf(stderr, "Could not complete all operations. Reason: %s\n", eina_error_msg_get(err)); + * return array; + * } + * len = eina_value_array_count(&array); + * for (i = 0; i < len; i++) + * { + * Eina_Value v; + * eina_value_array_get(&array, i, &v); + * if (v.type == EINA_VALUE_TYPE_ERROR) + * { + * eina_value_get(&array, &err); + * fprintf(stderr, "Could not complete operation '%s'. Reason: %s\n", _get_operation_name_by_index(i), eina_error_msg_get(err)); + * continue; + * } + * if (!i) + * { + * const char *msg; + * if (v.type != EINA_VALUE_TYPE_STRING) + * { + * fprintf(stderr, "Operation %s expects '%s' - received '%s'\n", _get_operation_name_by_index(i), EINA_VALUE_TYPE_STRING->name, v.type->name); + * continue; + * } + * eina_value_get(&v, &msg); + * printf("File content:%s\n", msg); + * } + * else if (i == 1) + * { + * int i; + * if (v.type != EINA_VALUE_TYPE_INT) + * { + * fprintf(stderr, "Operation %s expects '%s' - received '%s'\n", _get_operation_name_by_index(i), EINA_VALUE_TYPE_INT->name, v.type->name); + * continue; + * } + * eina_value_get(&v, &i); + * printf("File size: %d\n", i); + * } + * else + * { + * double p; + * if (v.type != EINA_VALUE_TYPE_DOUBLE) + * { + * fprintf(stderr, "Operation %s expects '%s' - received '%s'\n", _get_operation_name_by_index(i), EINA_VALUE_TYPE_DOUBLE->name, v.type->name); + * continue; + * } + * eina_value_get(&v, &p); + * printf("50 places of PI: %f\n", p); + * } + * } + * return array; + * } + * + * void func(void) + * { + * Eina_Future *f1, *f2, *f3, f_all; + * + * f1 = read_file("/tmp/todo.txt"); + * f2 = get_file_size("/tmp/file.txt"); + * //calculates 50 places of PI + * f3 = calc_pi(50); + * f_all = eina_future_all(f1, f2, f3); + * eina_future_then(f_all, _all_cb, NULL); + * } + * @endcode + * + * @param array An array of futures, @c #EINA_FUTURE_SENTINEL terminated. + * @return A promise or @c NULL on error. + * @note On error all the futures will be CANCELED. + * @see eina_future_all_array() + */ +EAPI Eina_Promise *eina_promise_all_array(Eina_Future *array[]) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT; + +/** + * Creates a race promise. + * + * Creates a promise that resolves when a future from the @p array + * is completed. The remaining futures will be canceled with the + * error code `ECANCELED` + * + * The resulting value is an EINA_VALUE_TYPE_STRUCT with two fields: + * + * @li A field named "value" which contains an Eina_Value with the result itself. + * @li A field named "index" which is an int that contains the index of the completed + * function relative to the @p array; + * + * Example. + * + * @code + * + * static const char * + * _get_operation_name_by_index(int idx) + * { + * switch (idx) + * { + * case 0: return "Get file data"; + * case 1: return "Get file size"; + * default: return "sum"; + * } + * } + * + * static Eina_Value + * _race_cb(const void *data EINA_UNUSED, const Eina_Value v) + * { + * unsigned int i; + * Eina_Value result; + * Eina_Error err; + * Eina_Value_Struct *st; + * + * //No need to check for the 'v' type. eina_future_cb_easy() did that for us, + * //However we should check if the struct has the correct description + * st = eina_value_memory_get(&v); + * if (st->desc != EINA_PROMISE_RACE_STRUCT_DESC) + * { + * fprintf(stderr, "Eina_Value is not a race struct\n"); + * return v; + * } + * eina_value_struct_get(&v, "index", &i); + * //Get the operation result + * eina_value_struct_get(&v, "value", &result); + * if (!i) + * { + * const char *msg; + * if (result.type != EINA_VALUE_TYPE_STRING) + * fprintf(stderr, "Operation %s expects '%s' - received '%s'\n", _get_operation_name_by_index(i), EINA_VALUE_TYPE_STRING->name, result.type->name); + * else + * { + * eina_value_get(&result, &msg); + * printf("File content:%s\n", msg); + * } + * } + * else if (i == 1) + * { + * int i; + * if (result.type != EINA_VALUE_TYPE_INT) + * fprintf(stderr, "Operation %s expects '%s' - received '%s'\n", _get_operation_name_by_index(i), EINA_VALUE_TYPE_INT->name, v.type->name); + * else + * { + * eina_value_get(&result, &i); + * printf("File size: %d\n", i); + * } + * } + * else + * { + * double p; + * if (result.type != EINA_VALUE_TYPE_DOUBLE) + * fprintf(stderr, "Operation %s expects '%s' - received '%s'\n", _get_operation_name_by_index(i), EINA_VALUE_TYPE_DOUBLE->name, result.type->name); + * else + * { + * eina_value_get(&result, &p); + * printf("50 places of PI: %f\n", p); + * } + * } + * eina_value_flush(&result); + * return v; + * } + * + * static Eina_Value + * _race_err(void *data, Eina_Error err) + * { + * fprintf(stderr, "Could not complete the race future. Reason: %s\n", eina_error_msg_get(err)); + * return EINA_VALUE_EMPTY; + * } + * + * void func(void) + * { + * static const *Eina_Future[] = {NULL, NULL, NULL, NULL}; + * Eina_List *l = NULL; + * + * futures[0] = read_file("/tmp/todo.txt"); + * futures[1] = get_file_size("/tmp/file.txt"); + * //calculates 50 places of PI + * futures[2] = calc_pi(50); + * f_race = eina_future_race_array(futures); + * eina_future_then_easy(f_race, _race_cb, _race_err, NULL, EINA_VALUE_TYPE_STRUCT, NULL); + * } + * @endcode + * + * @param array An array of futures, @c #EINA_FUTURE_SENTINEL terminated. + * @return A promise or @c NULL on error. + * @note On error all the futures will be CANCELED. + * @see eina_future_race_array() + * @see #_Eina_Future_Race_Result + */ +EAPI Eina_Promise *eina_promise_race_array(Eina_Future *array[]) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT; + +/** + * @struct The struct that is used to store the race result. + * + * When using eina_promise_race_array() and friends, the future result + * will be reported as a struct. The values can be obtained using + * eina_value_struct_get() or one could access the struct directly + * such as this example: + * + * @code + * static Eina_Value + * _race_cb(const void *data EINA_UNUSED, const Eina_Value v) + * { + * //code... + * Eina_Value_Struct st; + * Eina_Future_Race_Result *rr; + * eina_value_get(v, &st); + * rr = st.memory; + * printf("Winning future index: %u\n", rr->index); + * //more code.. + * return v; + * } + * @endcode + * + * @see eina_promise_race_array() + * @see eina_future_race_array() + * @see eina_promise_race() + * @see eina_future_race() + * @see #EINA_PROMISE_RACE_STRUCT_DESC + */ +struct _Eina_Future_Race_Result { + /** + * The race result. + */ + Eina_Value value; + /** + * The future index that won the race. + */ + unsigned int index; +}; + +/** + * @var EINA_PROMISE_RACE_STRUCT_DESC + * + * A pointer to the race struct description, which + * is used by eina_promise_race_array(); + * + * This struct contains two members: + * @li value An EINA_VALUE_TYPE_VALUE that contains the future result that wont the race. + * @li index An EINA_VALUE_TYPE_UINT that contains the future index that won the race. + * + * @see eina_promise_race_array() + * @see #_Eina_Future_Race_Result + */ +EAPI extern const Eina_Value_Struct_Desc *EINA_PROMISE_RACE_STRUCT_DESC; + +/** + * Creates a future that will be resolved once all futures from @p array is resolved. + * This is a helper over eina_promise_all_array() + * + * @param array A future array, must be terminated with #EINA_FUTURE_SENTINEL + * @return A future. + * @see eina_promise_all_array() + * @see #EINA_FUTURE_SENTINEL + */ +static inline Eina_Future * +eina_future_all_array(Eina_Future *array[]) +{ + Eina_Promise *p = eina_promise_all_array(array); + if (!p) return NULL; + return eina_future_new(p); +} + +/** + * Creates a future that will be resolved once a future @p array is resolved. + * This is a helper over eina_promise_race_array() + * + * @param array A future array, must be terminated with #EINA_FUTURE_SENTINEL + * @return A future. + * @see eina_promise_race_array() + * @see #EINA_FUTURE_SENTINEL + */ +static inline Eina_Future * +eina_future_race_array(Eina_Future *array[]) +{ + Eina_Promise *p = eina_promise_race_array(array); + if (!p) return NULL; + return eina_future_new(p); +} + +/** + * Used by eina_promise_race_array() and eina_promise_all_array() and + * friends to flag the end of the array. + * + * @see eina_promise_race_array() + * @see eina_promise_all_array() + */ +#define EINA_FUTURE_SENTINEL ((void *)(unsigned long)-1) + +/** + * A syntatic sugar over eina_promise_race_array(). + * Usage: + * @code + * promise = eina_promise_race(future1, future2, future3, future4); + * @endcode + * @see eina_promise_race_array() + */ +#define eina_promise_race(...) eina_promise_race_array((Eina_Future *[]){__VA_ARGS__, EINA_FUTURE_SENTINEL}) +/** + * A syntatic sugar over eina_future_race_array(). + * Usage: + * @code + * future = eina_future_race(future1, future2, future3, future4); + * @endcode + * @see eina_future_racec_array() + */ +#define eina_future_race(...) eina_future_race_array((Eina_Future *[]){__VA_ARGS__, EINA_FUTURE_SENTINEL}) +/** + * A syntatic sugar over eina_future_all_array(). + * Usage: + * @code + * future = eina_future_all(future1, future2, future3, future4); + * @endcode + * @see eina_future_all_array() + */ +#define eina_future_all(...) eina_future_all_array((Eina_Future *[]){__VA_ARGS__, EINA_FUTURE_SENTINEL}) +/** + * A syntatic sugar over eina_promise_all_array(). + * Usage: + * @code + * promise = eina_promise_all(future1, future2, future3, future4); + * @endcode + * @see eina_promise_all_array() + */ +#define eina_promise_all(...) eina_promise_all_array((Eina_Future *[]){__VA_ARGS__, EINA_FUTURE_SENTINEL}) +/** + * A syntatic sugar over eina_future_cb_easy_from_desc(). + * Usage: + * @code + * future_desc = eina_future_cb_easy(_success_cb, _error_cb, _free_cb, EINA_VALUE_TYPE_INT, my_data); + * @endcode + * @see eina_future_cb_easy_from_desc() + */ +#define eina_future_cb_easy(...) eina_future_cb_easy_from_desc((Eina_Future_Cb_Easy_Desc){__VA_ARGS__}) +/** + * A syntatic sugar over eina_future_chain_array(). + * Usage: + * @code + * future = eina_future_chain(future, {.cb = _my_cb, .data = my_data}, {.cb = _my_another_cb, .data = NULL}); + * @endcode + * @see eina_future_chain_array() + */ +#define eina_future_chain(_prev, ...) eina_future_chain_array(_prev, (Eina_Future_Desc[]){__VA_ARGS__, {.cb = NULL, .data = NULL}}) +/** + * A syntatic sugar over eina_future_then_from_desc(). + * Usage: + * @code + * future = eina_future_then(future, _my_cb, my_data); + * @endcode + * @see eina_future_then_from_desc() + * @see eina_future_then_easy() + */ +#define eina_future_then(_prev, ...) eina_future_then_from_desc(_prev, (Eina_Future_Desc){__VA_ARGS__}) +/** + * A syntatic sugar over eina_future_cb_console_from_desc(). + * Usage: + * @code + * desc = eina_future_cb_console(.prefix = "prefix", .suffix = "suffix"); + * @endcode + * @see eina_future_cb_console_from_desc() + */ +#define eina_future_cb_console(...) eina_future_cb_console_from_desc((Eina_Future_Cb_Console_Desc){__VA_ARGS__}) + +/** + * A syntatic sugar over eina_future_cb_log_from_desc(). + * + * This macro will set the following fields of the #Eina_Future_Cb_Log_Desc: + * + * @li #Eina_Future_Cb_Log_Desc::file: The __FILE__ function will be used. + * @li #Eina_Future_Cb_Log_Desc::func: The __FUNCTION__ macro will be used. + * @li #Eina_Future_Cb_Log_Desc::level: EINA_LOG_LEVEL_DBG will be used. + * @li #Eina_Future_Cb_Log_Desc::domain: EINA_LOG_DOMAIN_DEFAULT will be used. + * @li #Eina_Future_Cb_Log_Desc::line: The __LINE__ macro will be used. + * + * Usage: + * @code + * desc = eina_future_cb_log_dbg(.prefix = "prefix", .suffix = "suffix"); + * @endcode + * @see eina_future_cb_log_from_desc() + */ +#define eina_future_cb_log_dbg(_prefix, _suffix) \ + eina_future_cb_log_from_desc((Eina_Future_Cb_Log_Desc){_prefix, _suffix, __FILE__, \ + __FUNCTION__, EINA_LOG_LEVEL_DBG, EINA_LOG_DOMAIN_DEFAULT, __LINE__}) + +/** + * A syntatic sugar over eina_future_cb_log_from_desc(). + * + * This macro will set the following fields of the #Eina_Future_Cb_Log_Desc: + * + * @li #Eina_Future_Cb_Log_Desc::file: The __FILE__ function will be used. + * @li #Eina_Future_Cb_Log_Desc::func: The __FUNCTION__ macro will be used. + * @li #Eina_Future_Cb_Log_Desc::level: EINA_LOG_LEVEL_CRITICAL will be used. + * @li #Eina_Future_Cb_Log_Desc::domain: EINA_LOG_DOMAIN_DEFAULT will be used. + * @li #Eina_Future_Cb_Log_Desc::line: The __LINE__ macro will be used. + * + * Usage: + * @code + * desc = eina_future_cb_log_crit(.prefix = "prefix", .suffix = "suffix"); + * @endcode + * @see eina_future_cb_log_from_desc() + */ +#define eina_future_cb_log_crit(_prefix, _suffix) \ + eina_future_cb_log_from_desc((Eina_Future_Cb_Log_Desc){_prefix, _suffix, __FILE__, \ + __FUNCTION__, EINA_LOG_LEVEL_CRITICAL, EINA_LOG_DOMAIN_DEFAULT, __LINE__}) + +/** + * A syntatic sugar over eina_future_cb_log_from_desc(). + * + * This macro will set the following fields of the #Eina_Future_Cb_Log_Desc: + * + * @li #Eina_Future_Cb_Log_Desc::file: The __FILE__ function will be used. + * @li #Eina_Future_Cb_Log_Desc::func: The __FUNCTION__ macro will be used. + * @li #Eina_Future_Cb_Log_Desc::level: EINA_LOG_LEVEL_ERR will be used. + * @li #Eina_Future_Cb_Log_Desc::domain: EINA_LOG_DOMAIN_DEFAULT will be used. + * @li #Eina_Future_Cb_Log_Desc::line: The __LINE__ macro will be used. + * + * Usage: + * @code + * desc = eina_future_cb_log_err(.prefix = "prefix", .suffix = "suffix"); + * @endcode + * @see eina_future_cb_log_from_desc() + */ +#define eina_future_cb_log_err(_prefix, _suffix) \ + eina_future_cb_log_from_desc((Eina_Future_Cb_Log_Desc){_prefix, _suffix, __FILE__, \ + __FUNCTION__, EINA_LOG_LEVEL_ERR, EINA_LOG_DOMAIN_DEFAULT, __LINE__}) + +/** + * A syntatic sugar over eina_future_cb_log_from_desc(). + * + * This macro will set the following fields of the #Eina_Future_Cb_Log_Desc: + * + * @li #Eina_Future_Cb_Log_Desc::file: The __FILE__ function will be used. + * @li #Eina_Future_Cb_Log_Desc::func: The __FUNCTION__ macro will be used. + * @li #Eina_Future_Cb_Log_Desc::level: EINA_LOG_LEVEL_INFO will be used. + * @li #Eina_Future_Cb_Log_Desc::domain: EINA_LOG_DOMAIN_DEFAULT will be used. + * @li #Eina_Future_Cb_Log_Desc::line: The __LINE__ macro will be used. + * + * + * Usage: + * @code + * desc = eina_future_cb_log_info(.prefix = "prefix", .suffix = "suffix"); + * @endcode + * @see eina_future_cb_log_from_desc() + */ +#define eina_future_cb_log_info(_prefix, _suffix) \ + eina_future_cb_log_from_desc((Eina_Future_Cb_Log_Desc){_prefix, _suffix, __FILE__, \ + __FUNCTION__, EINA_LOG_LEVEL_INFO, EINA_LOG_DOMAIN_DEFAULT, __LINE__}) + +/** + * A syntatic sugar over eina_future_cb_log_from_desc(). + * + * This macro will set the following fields of the #Eina_Future_Cb_Log_Desc: + * + * @li #Eina_Future_Cb_Log_Desc::file: The __FILE__ function will be used. + * @li #Eina_Future_Cb_Log_Desc::func: The __FUNCTION__ macro will be used. + * @li #Eina_Future_Cb_Log_Desc::level: EINA_LOG_LEVEL_WARN will be used. + * @li #Eina_Future_Cb_Log_Desc::domain: EINA_LOG_DOMAIN_DEFAULT will be used. + * @li #Eina_Future_Cb_Log_Desc::line: The __LINE__ macro will be used. + * + * Usage: + * @code + * desc = eina_future_cb_log_warn(.prefix = "prefix", .suffix = "suffix"); + * @endcode + * @see eina_future_cb_log_from_desc() + */ +#define eina_future_cb_log_warn(_prefix, _suffix) \ + eina_future_cb_log_from_desc((Eina_Future_Cb_Log_Desc){_prefix, _suffix, __FILE__, \ + __FUNCTION__, EINA_LOG_LEVEL_WARN, EINA_LOG_DOMAIN_DEFAULT, __LINE__}) + +/** + * A syntatic sugar over eina_future_then() and eina_future_cb_easy(). + * Usage: + * @code + * f = eina_future_then_easy(f, .success = _success_cb, .success_type = EINA_VALUE_TYPE_DOUBLE, .data = NULL, ); + * @endcode + * @see eina_future_then_from_desc() + * @see eina_future_easy() + * @see eina_future_then() + * @see eina_future_cb_easy_from_desc() + */ +#define eina_future_then_easy(_prev, ...) eina_future_then_from_desc(_prev, eina_future_cb_easy(__VA_ARGS__)) + +/** + * A syntatic sugar over eina_future_chain() and eina_future_cb_easy(). + * Usage: + * @code + * f = eina_future_chain_easy(f, {.success = _success_cb, .success_type = EINA_VALUE_TYPE_DOUBLE, .data = NULL}, + * { .success = _success2_cb }, {.error = error_cb}); + * @endcode + * @see eina_future_chain_array() + * @see eina_future_easy() + * @see eina_future_chain_easy_array() + * @see eina_future_cb_easy_from_desc() + */ +#define eina_future_chain_easy(_prev, ...) eina_future_chain_easy_array(_prev, (Eina_Future_Cb_Easy_Desc[]) {__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}}) + + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/lib/eina/eina_promise_private.h b/src/lib/eina/eina_promise_private.h new file mode 100644 index 0000000000..31375b5794 --- /dev/null +++ b/src/lib/eina/eina_promise_private.h @@ -0,0 +1,38 @@ +#ifndef __EINA_PROMISE_PRIVATE_H__ +#define __EINA_PROMISE_PRIVATE_H__ + +#define ERROR_DISPATCH(_cbs, _ret, _value, _data) \ + do { \ + Eina_Error ERROR_DISPATCH__err; \ + if (!(_cbs)->error) (_ret) = (_value); \ + else \ + { \ + eina_value_get(&(_value), &ERROR_DISPATCH__err); \ + (_ret) = (_cbs)->error((_data), ERROR_DISPATCH__err); \ + } \ + } while (0) + +#define EASY_FUTURE_DISPATCH(_ret, _value, _dead_future, _cbs, _data) \ + do { \ + if ((_value).type == EINA_VALUE_TYPE_ERROR) ERROR_DISPATCH((_cbs), (_ret), (_value), (_data)); \ + else \ + { \ + if ((!(_cbs)->success_type) || ((_cbs)->success_type && (_value).type == (_cbs)->success_type)) \ + { \ + if (!(_cbs)->success) (_ret) = (_value); /* pass thru */ \ + else (_ret) = (_cbs)->success((_data), (_value)); \ + } \ + else \ + { \ + Eina_Value EASY_FUTURE_DISPATCH__err = EINA_VALUE_EMPTY; \ + ERR("Future %p, success cb: %p data: %p, expected success_type %p (%s), got %p (%s)", \ + _dead_future, (_cbs)->success, (_data), \ + (_cbs)->success_type, eina_value_type_name_get((_cbs)->success_type), \ + (_value).type, (_value).type ? eina_value_type_name_get((_value).type) : NULL); \ + if (eina_value_setup(&EASY_FUTURE_DISPATCH__err, EINA_VALUE_TYPE_ERROR)) eina_value_set(&EASY_FUTURE_DISPATCH__err, EINVAL); \ + ERROR_DISPATCH((_cbs), (_ret), EASY_FUTURE_DISPATCH__err, (_data)); \ + } \ + } \ + if ((_cbs)->free) (_cbs)->free((_data), _dead_future); \ + } while(0) +#endif diff --git a/src/lib/eo/eina_types.eot b/src/lib/eo/eina_types.eot index 40fbbf2df8..83be600926 100644 --- a/src/lib/eo/eina_types.eot +++ b/src/lib/eo/eina_types.eot @@ -62,3 +62,84 @@ struct @extern Eina.Rw_Slice { } struct @extern Eina.Value.Type; [[Eina value type]] + +struct @extern Eina.Future; [[Eina_Future handle]] +struct @extern Eina.Promise; [[Eina_Promise handle]] +struct @extern @free(eina_future_desc_flush) Eina_Future_Desc; [[A struct used to define a callback and data for a future.]] +struct @extern @free(eina_future_cb_easy_desc_flush) Eina_Future_Cb_Easy_Desc; [[A struct with callbacks to be used by eina_future_cb_easy_from_desc() and eina_future_cb_easy()]] +struct @extern Eina_Future_Cb_Console_Desc; [[A struct used to define the prefix and suffix to be printed + along side the a future value. This struct is used by + eina_future_cb_console_from_desc()]] +struct @extern Eina_Future_Schedule_Entry; [[A struct that represents an scheduled event. + This struct may be used by Eina to cancel + a scheduled future.]] +struct @extern Eina.Future.Scheduler; [[This struct is used as a bridge between Eina and the future scheduler. + By using the provided functions Eina can schedule futures resolutions, + rejections and cancelations to a safe context.]] + +function @extern Eina.Future.Cb { + params { + value: const(generic_value); [[An Eina_Value which contains the operation result. Before using + the value, its type must be checked in order to avoid errors. This is needed, because + if an operation fails the Eina_Value type will be EINA_VALUE_TYPE_ERROR + which is a different type than the expected operation result.]] + dead_ptr: const(ptr(Eina.Future)); [[A pointer to the future that was completed]] + } + return: generic_value; [[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 recommended + to pass-thru value argument. If you need to convert to a different type + or generate a new value, use eina_value_setup() on 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 not use stack allocated blobs, arrays, structures or types that + keeps references to memory you give. Values will be automatically cleaned up + using eina_value_flush() once they are unused (no more future or futures + returned a new value).]] +}; + +function @extern Eina.Promise.Cancel.Cb { + params { + dead_promise: const(ptr(Eina.Promise)); [[The canceled promise.]] + } +}; + +function @extern Eina.Future.Success.Cb { + params { + value: const(generic_value); [[The operation result]] + } + return: generic_value; [[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 recommended + to pass-thru value argument. If you need to convert to a different type + or generate a new value, use eina_value_setup() on 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 not use stack allocated blobs, arrays, structures or types that + keeps references to memory you give. Values will be automatically cleaned up + using eina_value_flush() once they are unused (no more future or futures + returned a new value).]] +}; + +function @extern Eina.Future.Error.Cb { + params { + error: const(Eina.Error); [[The operation error]] + } + return: generic_value; [[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 recommended + to pass-thru value argument. If you need to convert to a different type + or generate a new value, use eina_value_setup() on 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 not use stack allocated blobs, arrays, structures or types that + keeps references to memory you give. Values will be automatically cleaned up + using eina_value_flush() once they are unused (no more future or futures + returned a new value).]] +}; + +function @extern Eina.Future.Free.Cb { + params { + dead_future: const(ptr(Eina.Future)); [[The future that was freed]] + } +}; |