Eina: Add Eina_Promise/Eina_Future.

This commit adds a new promise/future API which aims to replace
efl_future.
This commit is contained in:
Guilherme Iscaro 2017-08-08 18:10:36 -03:00
parent c9a0237770
commit 5bd8c9a78d
12 changed files with 3290 additions and 0 deletions

View File

@ -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 \

View File

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

View File

@ -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

View File

@ -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"

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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) /

1355
src/lib/eina/eina_promise.c Normal file

File diff suppressed because it is too large Load Diff

1674
src/lib/eina/eina_promise.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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]]
}
};