forked from enlightenment/efl
Compare commits
13 Commits
master
...
devs/barbi
Author | SHA1 | Date |
---|---|---|
Gustavo Sverzut Barbieri | 306ec6937b | |
Gustavo Sverzut Barbieri | 109bf1b387 | |
Gustavo Sverzut Barbieri | d10a35628c | |
Gustavo Sverzut Barbieri | 4d7661b174 | |
Gustavo Sverzut Barbieri | f3d5642503 | |
Gustavo Sverzut Barbieri | 7dc41ab0e5 | |
Gustavo Sverzut Barbieri | 1a0b921789 | |
Gustavo Sverzut Barbieri | 701aab803d | |
Gustavo Sverzut Barbieri | b2cd2cbf16 | |
Gustavo Sverzut Barbieri | 07f272b52e | |
Guilherme Iscaro | c35e929713 | |
Guilherme Iscaro | 0dd2c1a530 | |
Guilherme Iscaro | d30c0d4f03 |
|
@ -226,6 +226,7 @@ tests/ecore/ecore_test_ecore_thread_eina_thread_queue.c \
|
|||
tests/ecore/ecore_test_ecore_input.c \
|
||||
tests/ecore/ecore_test_ecore_file.c \
|
||||
tests/ecore/ecore_test_promise.c \
|
||||
tests/ecore/ecore_test_promise2.c \
|
||||
tests/ecore/ecore_test_job.c \
|
||||
tests/ecore/ecore_test_args.c \
|
||||
tests/ecore/ecore_suite.h
|
||||
|
|
|
@ -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 \
|
||||
|
@ -106,7 +107,8 @@ lib/eina/eina_slice.h \
|
|||
lib/eina/eina_inline_slice.x \
|
||||
lib/eina/eina_inline_modinfo.x \
|
||||
lib/eina/eina_freeq.h \
|
||||
lib/eina/eina_slstr.h
|
||||
lib/eina/eina_slstr.h \
|
||||
lib/eina/eina_coro.h
|
||||
|
||||
|
||||
lib_eina_libeina_la_SOURCES = \
|
||||
|
@ -150,6 +152,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 \
|
||||
|
@ -180,7 +184,8 @@ lib/eina/eina_quaternion.c \
|
|||
lib/eina/eina_bezier.c \
|
||||
lib/eina/eina_safepointer.c \
|
||||
lib/eina/eina_freeq.c \
|
||||
lib/eina/eina_slstr.c
|
||||
lib/eina/eina_slstr.c \
|
||||
lib/eina/eina_coro.c
|
||||
|
||||
|
||||
if HAVE_WIN32
|
||||
|
@ -353,7 +358,8 @@ tests/eina/eina_test_bezier.c \
|
|||
tests/eina/eina_test_safepointer.c \
|
||||
tests/eina/eina_test_slice.c \
|
||||
tests/eina/eina_test_freeq.c \
|
||||
tests/eina/eina_test_slstr.c
|
||||
tests/eina/eina_test_slstr.c \
|
||||
tests/eina/eina_test_coro.c
|
||||
|
||||
tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \
|
||||
-DTESTS_WD=\"`pwd`\" \
|
||||
|
|
|
@ -146,6 +146,7 @@ tests/eo/suite/eo_test_class_behaviour_errors.c \
|
|||
tests/eo/suite/eo_test_call_errors.c \
|
||||
tests/eo/suite/eo_test_general.c \
|
||||
tests/eo/suite/eo_test_value.c \
|
||||
tests/eo/suite/eo_test_coro.c \
|
||||
tests/eo/suite/eo_test_event.c \
|
||||
tests/eo/suite/eo_test_threaded_calls.c \
|
||||
tests/eo/suite/eo_test_init.c
|
||||
|
|
|
@ -76,6 +76,7 @@ ecore_idler_example \
|
|||
ecore_imf_example \
|
||||
ecore_job_example \
|
||||
ecore_poller_example \
|
||||
ecore_promise2_example \
|
||||
ecore_server_bench \
|
||||
ecore_thread_example \
|
||||
ecore_time_functions_example \
|
||||
|
@ -292,6 +293,9 @@ endif
|
|||
ecore_poller_example_SOURCES = ecore_poller_example.c
|
||||
ecore_poller_example_LDADD = $(ECORE_COMMON_LDADD)
|
||||
|
||||
ecore_promise2_example_SOURCES = ecore_promise2_example.c
|
||||
ecore_promise2_example_LDADD = $(ECORE_COMMON_LDADD)
|
||||
|
||||
ecore_server_bench_SOURCES = ecore_server_bench.c
|
||||
ecore_server_bench_LDADD = $(ECORE_CON_COMMON_LDADD)
|
||||
|
||||
|
@ -425,6 +429,7 @@ ecore_job_example.c \
|
|||
ecore_pipe_gstreamer_example.c \
|
||||
ecore_pipe_simple_example.c \
|
||||
ecore_poller_example.c \
|
||||
ecore_promise2_example.c \
|
||||
ecore_server_bench.c \
|
||||
ecore_thread_example.c \
|
||||
ecore_time_functions_example.c \
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
#define EFL_EO_API_SUPPORT 1
|
||||
#define EFL_BETA_API_SUPPORT 1
|
||||
|
||||
#include <Ecore.h>
|
||||
#include <Eina.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
typedef struct _Ctx {
|
||||
Eina_Promise *p;
|
||||
Eina_Bool should_fail;
|
||||
Ecore_Timer *timer;
|
||||
Eina_Value *value;
|
||||
} Ctx;
|
||||
|
||||
typedef struct _Inner_Promise_Ctx {
|
||||
Eina_Future *future;
|
||||
Eina_Promise *promise;
|
||||
} Inner_Promise_Ctx;
|
||||
|
||||
#define DEFAULT_MSG "the simple example is working!"
|
||||
|
||||
#define VALUE_TYPE_CHECK(_v, _type) \
|
||||
if (_v.type != _type) \
|
||||
{ \
|
||||
fprintf(stderr, "Value type is not '%s' - received '%s'\n", \
|
||||
_type->name, _v.type->name); \
|
||||
return _v; \
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_timeout(void *data)
|
||||
{
|
||||
Ctx *ctx = data;
|
||||
if (ctx->should_fail) eina_promise_reject(ctx->p, ENETDOWN);
|
||||
else
|
||||
{
|
||||
Eina_Value v;
|
||||
eina_value_copy(ctx->value, &v);
|
||||
eina_promise_resolve(ctx->p, v);
|
||||
eina_value_free(ctx->value);
|
||||
}
|
||||
free(ctx);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED)
|
||||
{
|
||||
Ctx *ctx = data;
|
||||
if (ctx->timer) ecore_timer_del(ctx->timer);
|
||||
eina_value_free(ctx->value);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static Eina_Future_Scheduler *
|
||||
_future_scheduler_get(void)
|
||||
{
|
||||
return efl_loop_future_scheduler_get(ecore_main_loop_get());
|
||||
}
|
||||
|
||||
static Ctx *
|
||||
_promise_ctx_new(Eina_Value *v)
|
||||
{
|
||||
Ctx *ctx;
|
||||
ctx = calloc(1, sizeof(Ctx));
|
||||
EINA_SAFETY_ON_NULL_GOTO(ctx, err_ctx);
|
||||
ctx->p = eina_promise_new(_future_scheduler_get(), _promise_cancel, ctx);
|
||||
EINA_SAFETY_ON_NULL_GOTO(ctx->p, err_timer);
|
||||
ctx->value = v;
|
||||
return ctx;
|
||||
err_timer:
|
||||
free(ctx);
|
||||
err_ctx:
|
||||
eina_value_free(v);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_future_get(Ctx *ctx)
|
||||
{
|
||||
Eina_Future *f;
|
||||
|
||||
f = eina_future_new(ctx->p);
|
||||
EINA_SAFETY_ON_NULL_GOTO(f, err_future);
|
||||
ctx->timer = ecore_timer_add(0.1, _timeout, ctx);
|
||||
EINA_SAFETY_ON_NULL_GOTO(ctx->timer, err_timer);
|
||||
return f;
|
||||
|
||||
err_timer:
|
||||
eina_future_cancel(f);
|
||||
err_future:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_fail_future_get(void)
|
||||
{
|
||||
Ctx *ctx = _promise_ctx_new(NULL);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
|
||||
ctx->should_fail = EINA_TRUE;
|
||||
return _future_get(ctx);
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_str_future_get(void)
|
||||
{
|
||||
Eina_Value *v = eina_value_util_string_new(DEFAULT_MSG);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(v, NULL);
|
||||
Ctx *ctx = _promise_ctx_new(v);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
|
||||
return _future_get(ctx);
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_int_future_get(void)
|
||||
{
|
||||
Eina_Value *v= eina_value_util_int_new(0);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(v, NULL);
|
||||
Ctx *ctx = _promise_ctx_new(v);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
|
||||
return _future_get(ctx);
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_simple_ok(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_STRING);
|
||||
return v;
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_alternate_error_cb(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
Eina_Bool *should_fail = data;
|
||||
Eina_Value new_v = EINA_VALUE_EMPTY;
|
||||
|
||||
if (*should_fail)
|
||||
{
|
||||
*should_fail = EINA_FALSE;
|
||||
eina_value_setup(&new_v, EINA_VALUE_TYPE_ERROR);
|
||||
eina_value_set(&new_v, ENETDOWN);
|
||||
printf("Received succes from the previous future - Generating error for the next future...\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
*should_fail = EINA_TRUE;
|
||||
Eina_Error err;
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
|
||||
eina_value_get(&v, &err);
|
||||
printf("Received error from the previous future - value: %s. Send success\n",
|
||||
eina_error_msg_get(err));
|
||||
}
|
||||
return new_v;
|
||||
}
|
||||
|
||||
static void
|
||||
_alternate_error(void)
|
||||
{
|
||||
static Eina_Bool should_fail = EINA_TRUE;
|
||||
|
||||
eina_future_chain(_str_future_get(),
|
||||
{.cb = _alternate_error_cb, .data = &should_fail},
|
||||
{.cb = _alternate_error_cb, .data = &should_fail},
|
||||
{.cb = _alternate_error_cb, .data = &should_fail},
|
||||
{.cb = _alternate_error_cb, .data = &should_fail});
|
||||
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_simple_err(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void
|
||||
_simple(void)
|
||||
{
|
||||
eina_future_chain(_str_future_get(),
|
||||
eina_future_cb_console("Expecting the following message: "DEFAULT_MSG ". Got: ", NULL),
|
||||
{ .cb = _simple_ok, .data = NULL });
|
||||
eina_future_chain(_fail_future_get(),
|
||||
eina_future_cb_console("Expectig network down error. Got: ", NULL),
|
||||
{ .cb = _simple_err, .data = NULL });
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_chain_no_errors_cb(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
int count;
|
||||
Eina_Value new_v;
|
||||
|
||||
eina_value_setup(&new_v, EINA_VALUE_TYPE_INT);
|
||||
if (!v.type)
|
||||
count = 1;
|
||||
else
|
||||
{
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_INT);
|
||||
eina_value_get(&v, &count);
|
||||
}
|
||||
eina_value_set(&new_v, count * 2);
|
||||
return new_v;
|
||||
}
|
||||
|
||||
static void
|
||||
_chain_no_errors(void)
|
||||
{
|
||||
eina_future_chain(_int_future_get(),
|
||||
eina_future_cb_console("Expecting no value. Got: ", NULL),
|
||||
{.cb = _chain_no_errors_cb, .data = NULL},
|
||||
eina_future_cb_console("Expecting number 2. Got: ", NULL),
|
||||
{.cb = _chain_no_errors_cb, .data = NULL},
|
||||
eina_future_cb_console("Expecting number 4. Got: ", NULL),
|
||||
{.cb = _chain_no_errors_cb, .data = NULL},
|
||||
eina_future_cb_console("Expecting number 8. Got: ", NULL),
|
||||
{.cb = _chain_no_errors_cb, .data = NULL},
|
||||
eina_future_cb_console("Expecting number 16. Got: ", NULL));
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_chain_with_error_cb(void *data EINA_UNUSED, const Eina_Value v EINA_UNUSED, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
Eina_Value err;
|
||||
eina_value_setup(&err, EINA_VALUE_TYPE_ERROR);
|
||||
eina_value_set(&err, E2BIG);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void
|
||||
_chain_with_error(void)
|
||||
{
|
||||
eina_future_chain(_int_future_get(),
|
||||
{ _chain_with_error_cb, NULL },
|
||||
eina_future_cb_console("Expecting argument list too long. Got: ", NULL),
|
||||
{ .cb = _simple_err, .data = NULL });
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_delayed_resolve(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
Inner_Promise_Ctx *ctx = data;
|
||||
Eina_Value new_v;
|
||||
eina_value_setup(&new_v, EINA_VALUE_TYPE_STRING);
|
||||
eina_value_set(&new_v, "Hello from inner future");
|
||||
eina_promise_resolve(ctx->promise, new_v);
|
||||
free(ctx);
|
||||
return v;
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_delayed_reject(void *data, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
Inner_Promise_Ctx *ctx = data;
|
||||
eina_promise_reject(ctx->promise, ENETDOWN);
|
||||
free(ctx);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void
|
||||
_inner_promise_cancel(void *data, const Eina_Promise *dead EINA_UNUSED)
|
||||
{
|
||||
Inner_Promise_Ctx *ctx = data;
|
||||
eina_future_cancel(ctx->future);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_chain_inner_cb(void *data, const Eina_Value v EINA_UNUSED, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
Inner_Promise_Ctx *ctx;
|
||||
Eina_Value r;
|
||||
|
||||
ctx = calloc(1, sizeof(Inner_Promise_Ctx));
|
||||
EINA_SAFETY_ON_NULL_GOTO(ctx, err);
|
||||
ctx->promise = eina_promise_new(_future_scheduler_get(), _inner_promise_cancel, ctx);
|
||||
EINA_SAFETY_ON_NULL_GOTO(ctx->promise, err);
|
||||
|
||||
printf("Creating a new promise inside the future cb\n");
|
||||
ctx->future = eina_future_then(_int_future_get(),
|
||||
!data ? _delayed_resolve : _delayed_reject,
|
||||
ctx);
|
||||
return eina_promise_as_value(ctx->promise);
|
||||
|
||||
err:
|
||||
eina_value_setup(&r, EINA_VALUE_TYPE_ERROR);
|
||||
eina_value_set(&r, ENOMEM);
|
||||
free(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_chain_inner_last_cb(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_STRING);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void
|
||||
_chain_inner_no_errors(void)
|
||||
{
|
||||
eina_future_chain(_int_future_get(),
|
||||
{ .cb = _chain_inner_cb, .data = NULL },
|
||||
eina_future_cb_console("Expecting message: 'Hello from inner future'. Got: ", NULL),
|
||||
{ .cb = _chain_inner_last_cb, .data = NULL });
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_err_inner_chain(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
|
||||
ecore_main_loop_quit();
|
||||
return v;
|
||||
}
|
||||
|
||||
static void
|
||||
_chain_inner_errors(void)
|
||||
{
|
||||
|
||||
eina_future_chain(_int_future_get(),
|
||||
{ .cb = _chain_inner_cb, .data = (void *)1 },
|
||||
eina_future_cb_console("Expection network down error. Got: ", NULL),
|
||||
{ .cb = _err_inner_chain, .data = NULL });
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_canceled_cb(void *data EINA_UNUSED, const Eina_Value v, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
VALUE_TYPE_CHECK(v, EINA_VALUE_TYPE_ERROR);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void
|
||||
_future_cancel(void)
|
||||
{
|
||||
Eina_Future *f;
|
||||
|
||||
f = eina_future_chain(_int_future_get(),
|
||||
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
|
||||
{ .cb = _canceled_cb, .data = NULL },
|
||||
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
|
||||
{ .cb = _canceled_cb, .data = NULL },
|
||||
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
|
||||
{ .cb = _canceled_cb, .data = NULL },
|
||||
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
|
||||
{ .cb = _canceled_cb, .data = NULL },
|
||||
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL),
|
||||
{ .cb = _canceled_cb, .data = NULL },
|
||||
eina_future_cb_console("Expecting cancelled operation error. Got: ", NULL));
|
||||
eina_future_cancel(f);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc EINA_UNUSED, char *argv[] EINA_UNUSED)
|
||||
{
|
||||
if (!eina_init())
|
||||
{
|
||||
fprintf(stderr, "Could not init eina\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!ecore_init())
|
||||
{
|
||||
fprintf(stderr, "Could not init ecore\n");
|
||||
goto err_ecore;
|
||||
}
|
||||
|
||||
_simple();
|
||||
_alternate_error();
|
||||
_chain_no_errors();
|
||||
_chain_with_error();
|
||||
_chain_inner_no_errors();
|
||||
_chain_inner_errors();
|
||||
_future_cancel();
|
||||
|
||||
ecore_main_loop_begin();
|
||||
|
||||
eina_shutdown();
|
||||
ecore_shutdown();
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
err_ecore:
|
||||
eina_shutdown();
|
||||
return EXIT_FAILURE;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -44,6 +44,7 @@ GENERIC_ALLOC_FREE(Ecore_Event, ecore_event);
|
|||
//GENERIC_ALLOC_FREE(Ecore_Poller, ecore_poller);
|
||||
GENERIC_ALLOC_FREE(Ecore_Pipe, ecore_pipe);
|
||||
GENERIC_ALLOC_FREE(Ecore_Fd_Handler, ecore_fd_handler);
|
||||
GENERIC_ALLOC_FREE(Efl_Loop_Promise_Simple_Data, efl_loop_promise_simple_data);
|
||||
#ifdef _WIN32
|
||||
GENERIC_ALLOC_FREE(Ecore_Win32_Handler, ecore_win32_handler);
|
||||
#endif
|
||||
|
@ -61,6 +62,7 @@ static Ecore_Mempool *mempool_array[] = {
|
|||
// &ecore_poller_mp,
|
||||
&ecore_pipe_mp,
|
||||
&ecore_fd_handler_mp,
|
||||
&efl_loop_promise_simple_data_mp,
|
||||
#ifdef _WIN32
|
||||
&ecore_win32_handler_mp
|
||||
#endif
|
||||
|
@ -87,6 +89,7 @@ ecore_mempool_init(void)
|
|||
// MP_SIZE_INIT(Ecore_Poller, ecore_poller);
|
||||
MP_SIZE_INIT(Ecore_Pipe, ecore_pipe);
|
||||
MP_SIZE_INIT(Ecore_Fd_Handler, ecore_fd_handler);
|
||||
MP_SIZE_INIT(Efl_Loop_Promise_Simple_Data, efl_loop_promise_simple_data);
|
||||
#ifdef _WIN32
|
||||
MP_SIZE_INIT(Ecore_Win32_Handler, ecore_win32_handler);
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -233,6 +233,15 @@ struct _Ecore_Fd_Handler
|
|||
};
|
||||
GENERIC_ALLOC_SIZE_DECLARE(Ecore_Fd_Handler);
|
||||
|
||||
typedef struct _Efl_Loop_Promise_Simple_Data {
|
||||
union {
|
||||
Ecore_Timer *timer;
|
||||
Ecore_Idler *idler;
|
||||
};
|
||||
Eina_Promise *promise;
|
||||
} Efl_Loop_Promise_Simple_Data;
|
||||
GENERIC_ALLOC_SIZE_DECLARE(Efl_Loop_Promise_Simple_Data);
|
||||
|
||||
#ifdef _WIN32
|
||||
struct _Ecore_Win32_Handler
|
||||
{
|
||||
|
@ -3112,6 +3121,277 @@ EFL_CALLBACKS_ARRAY_DEFINE(timeout,
|
|||
{ EFL_LOOP_TIMER_EVENT_TICK, _efl_loop_timeout_cb },
|
||||
{ EFL_EVENT_DEL, _efl_loop_timeout_force_cancel_cb });
|
||||
|
||||
static Eina_Future *
|
||||
_efl_loop_Eina_FutureXXX_job(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED)
|
||||
{
|
||||
// NOTE: Eolian should do efl_future_then() to bind future to object.
|
||||
return efl_future_Eina_FutureXXX_then(obj,
|
||||
eina_future_resolved(efl_loop_future_scheduler_get(obj),
|
||||
EINA_VALUE_EMPTY));
|
||||
}
|
||||
|
||||
static void
|
||||
_efl_loop_Eina_FutureXXX_idle_cancel(void *data, const Eina_Promise *dead_ptr EINA_UNUSED)
|
||||
{
|
||||
Efl_Loop_Promise_Simple_Data *d = data;
|
||||
ecore_idler_del(d->idler);
|
||||
efl_loop_promise_simple_data_mp_free(d);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_efl_loop_Eina_FutureXXX_idle_done(void *data)
|
||||
{
|
||||
Efl_Loop_Promise_Simple_Data *d = data;
|
||||
eina_promise_resolve(d->promise, EINA_VALUE_EMPTY);
|
||||
efl_loop_promise_simple_data_mp_free(d);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_efl_loop_Eina_FutureXXX_idle(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED)
|
||||
{
|
||||
Efl_Loop_Promise_Simple_Data *d;
|
||||
Eina_Promise *p;
|
||||
|
||||
d = efl_loop_promise_simple_data_calloc(1);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
|
||||
|
||||
d->idler = ecore_idler_add(_efl_loop_Eina_FutureXXX_idle_done, d);
|
||||
EINA_SAFETY_ON_NULL_GOTO(d->idler, idler_error);
|
||||
|
||||
p = eina_promise_new(efl_loop_future_scheduler_get(obj),
|
||||
_efl_loop_Eina_FutureXXX_idle_cancel, d);
|
||||
// d is dead if p is NULL
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
|
||||
d->promise = p;
|
||||
|
||||
// NOTE: Eolian should do efl_future_then() to bind future to object.
|
||||
return efl_future_Eina_FutureXXX_then(obj, eina_future_new(p));
|
||||
|
||||
idler_error:
|
||||
efl_loop_promise_simple_data_mp_free(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
_efl_loop_Eina_FutureXXX_timeout_cancel(void *data, const Eina_Promise *dead_ptr EINA_UNUSED)
|
||||
{
|
||||
Efl_Loop_Promise_Simple_Data *d = data;
|
||||
ecore_timer_del(d->timer);
|
||||
efl_loop_promise_simple_data_mp_free(d);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_efl_loop_Eina_FutureXXX_timeout_done(void *data)
|
||||
{
|
||||
Efl_Loop_Promise_Simple_Data *d = data;
|
||||
eina_promise_resolve(d->promise, EINA_VALUE_EMPTY);
|
||||
efl_loop_promise_simple_data_mp_free(d);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_efl_loop_Eina_FutureXXX_timeout(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED, double time)
|
||||
{
|
||||
Efl_Loop_Promise_Simple_Data *d;
|
||||
Eina_Promise *p;
|
||||
|
||||
d = efl_loop_promise_simple_data_calloc(1);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
|
||||
|
||||
d->timer = ecore_timer_add(time, _efl_loop_Eina_FutureXXX_timeout_done, d);
|
||||
EINA_SAFETY_ON_NULL_GOTO(d->timer, timer_error);
|
||||
|
||||
p = eina_promise_new(efl_loop_future_scheduler_get(obj),
|
||||
_efl_loop_Eina_FutureXXX_timeout_cancel, d);
|
||||
// d is dead if p is NULL
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(p, NULL);
|
||||
d->promise = p;
|
||||
|
||||
// NOTE: Eolian should do efl_future_then() to bind future to object.
|
||||
return efl_future_Eina_FutureXXX_then(obj, eina_future_new(p));
|
||||
|
||||
timer_error:
|
||||
efl_loop_promise_simple_data_mp_free(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct _Efl_Loop_Coro {
|
||||
Eina_Promise *promise;
|
||||
Eina_Coro *coro;
|
||||
Efl_Loop *loop;
|
||||
Eina_Future *scheduled;
|
||||
Efl_Loop_Coro_Cb func;
|
||||
const void *func_data;
|
||||
Eina_Free_Cb func_free_cb;
|
||||
Efl_Loop_Coro_Prio prio;
|
||||
Eina_Value value;
|
||||
} Efl_Loop_Coro;
|
||||
|
||||
static void
|
||||
_efl_loop_coro_free(Efl_Loop_Coro *lc)
|
||||
{
|
||||
if (lc->func_free_cb) lc->func_free_cb((void *)lc->func_data);
|
||||
if (lc->scheduled) eina_future_cancel(lc->scheduled);
|
||||
eina_value_flush(&lc->value);
|
||||
efl_unref(lc->loop);
|
||||
free(lc);
|
||||
}
|
||||
|
||||
static void _efl_loop_coro_reschedule(Efl_Loop_Coro *lc);
|
||||
|
||||
static Eina_Value
|
||||
_efl_loop_coro_schedule_resolved(void *data, const Eina_Value value, const Eina_Future *dead_future EINA_UNUSED)
|
||||
{
|
||||
Efl_Loop_Coro *lc = data;
|
||||
Eina_Future *awaiting = NULL;
|
||||
|
||||
if (value.type == EINA_VALUE_TYPE_ERROR)
|
||||
{
|
||||
Eina_Error err;
|
||||
eina_value_get(&value, &err);
|
||||
ERR("coro %p scheduled got error %s, try again.",
|
||||
lc, eina_error_msg_get(err));
|
||||
}
|
||||
else if (!eina_coro_run(&lc->coro, NULL, &awaiting))
|
||||
{
|
||||
INF("coroutine %p finished with value type=%p (%s)",
|
||||
lc, lc->value.type,
|
||||
lc->value.type ? lc->value.type->name : "EMPTY");
|
||||
|
||||
eina_promise_resolve(lc->promise, lc->value);
|
||||
lc->value = EINA_VALUE_EMPTY; // owned by promise
|
||||
_efl_loop_coro_free(lc);
|
||||
return value;
|
||||
}
|
||||
else if (awaiting)
|
||||
{
|
||||
DBG("coroutine %p is awaiting for future %p, do not reschedule", lc, awaiting);
|
||||
eina_future_chain(awaiting,
|
||||
{
|
||||
.cb = _efl_loop_coro_schedule_resolved,
|
||||
.data = lc,
|
||||
.storage = &lc->scheduled,
|
||||
},
|
||||
efl_future_cb(lc->loop));
|
||||
}
|
||||
else _efl_loop_coro_reschedule(lc);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void
|
||||
_efl_loop_coro_reschedule(Efl_Loop_Coro *lc)
|
||||
{
|
||||
Eina_Future *f;
|
||||
|
||||
// high uses 0-timeout instead of job, since job
|
||||
// is implemented using events and the Ecore implementation
|
||||
// will never run timers or anything else, just the new jobs :-/
|
||||
//
|
||||
// TODO: bug report ecore_main loop bug.
|
||||
if (lc->prio == EFL_LOOP_CORO_PRIO_HIGH)
|
||||
f = efl_loop_Eina_FutureXXX_timeout(lc->loop, 0);
|
||||
else
|
||||
f = efl_loop_Eina_FutureXXX_idle(lc->loop);
|
||||
|
||||
DBG("coroutine %p rescheduled as future=%p", lc, f);
|
||||
|
||||
// NOTE: efl_future_cb() doesn't allow for extra 'data', so it matches
|
||||
// methods more easily. However we need 'lc' and we can't store in
|
||||
// loop since we'd not know the key for efl_key_data_get().
|
||||
// Easy solution: use 2 futures, one to bind and another to resolve.
|
||||
eina_future_chain(f,
|
||||
{
|
||||
.cb = _efl_loop_coro_schedule_resolved,
|
||||
.data = lc,
|
||||
.storage = &lc->scheduled,
|
||||
},
|
||||
efl_future_cb(lc->loop));
|
||||
}
|
||||
|
||||
static void
|
||||
_efl_loop_coro_cancel(void *data, const Eina_Promise *dead_promise EINA_UNUSED)
|
||||
{
|
||||
Efl_Loop_Coro *lc = data;
|
||||
|
||||
INF("canceled coroutine %p (coro=%p)", lc, lc->coro);
|
||||
|
||||
eina_coro_cancel(&lc->coro);
|
||||
|
||||
_efl_loop_coro_free(lc);
|
||||
}
|
||||
|
||||
static const void *
|
||||
_efl_loop_coro_cb(void *data, Eina_Bool canceled, Eina_Coro *coro)
|
||||
{
|
||||
Efl_Loop_Coro *lc = data;
|
||||
|
||||
if (canceled) lc->value = eina_value_error_init(ECANCELED);
|
||||
else lc->value = lc->func((void *)lc->func_data, coro, lc->loop);
|
||||
|
||||
return lc;
|
||||
}
|
||||
|
||||
static Eina_Future *
|
||||
_efl_loop_coro(Eo *obj, Efl_Loop_Data *pd EINA_UNUSED, Efl_Loop_Coro_Prio prio, void *func_data, Efl_Loop_Coro_Cb func, Eina_Free_Cb func_free_cb)
|
||||
{
|
||||
Efl_Loop_Coro *lc;
|
||||
Eina_Promise *p;
|
||||
Eina_Future *f;
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(func, NULL);
|
||||
|
||||
lc = calloc(1, sizeof(Efl_Loop_Coro));
|
||||
EINA_SAFETY_ON_NULL_GOTO(lc, calloc_failed);
|
||||
|
||||
lc->loop = efl_ref(obj);
|
||||
lc->func = func;
|
||||
lc->func_data = func_data;
|
||||
lc->func_free_cb = func_free_cb;
|
||||
lc->prio = prio;
|
||||
|
||||
lc->coro = eina_coro_new(_efl_loop_coro_cb, lc, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
EINA_SAFETY_ON_NULL_GOTO(lc, coro_failed);
|
||||
|
||||
p = eina_promise_new(efl_loop_future_scheduler_get(obj),
|
||||
_efl_loop_coro_cancel, lc);
|
||||
// lc is dead if p is NULL
|
||||
EINA_SAFETY_ON_NULL_GOTO(p, promise_failed);
|
||||
lc->promise = p;
|
||||
|
||||
// must be done prior to reschedule, as it may resolve on errors
|
||||
// and promises without futures are simply ignored, will remain
|
||||
// alive.
|
||||
f = eina_future_new(p);
|
||||
|
||||
_efl_loop_coro_reschedule(lc);
|
||||
|
||||
INF("new coroutine %p (coro=%p)", lc, lc->coro);
|
||||
|
||||
// NOTE: Eolian should do efl_future_then() to bind future to object.
|
||||
return efl_future_Eina_FutureXXX_then(obj, f);
|
||||
|
||||
promise_failed:
|
||||
// _efl_loop_coro_cancel() was called, func was run... just return.
|
||||
|
||||
// NOTE: Eolian should do efl_future_then() to bind future to object.
|
||||
return efl_future_Eina_FutureXXX_then(obj,
|
||||
eina_future_resolved(efl_loop_future_scheduler_get(obj),
|
||||
eina_value_error_init(ENOMEM)));
|
||||
|
||||
coro_failed:
|
||||
_efl_loop_coro_free(lc);
|
||||
|
||||
calloc_failed:
|
||||
if (func_free_cb) func_free_cb((void *)func_data);
|
||||
|
||||
// NOTE: Eolian should do efl_future_then() to bind future to object.
|
||||
return efl_future_Eina_FutureXXX_then(obj,
|
||||
eina_future_resolved(efl_loop_future_scheduler_get(obj),
|
||||
eina_value_error_init(ENOMEM)));
|
||||
}
|
||||
|
||||
/* This event will be triggered when the main loop is destroyed and destroy its timers along */
|
||||
static void _efl_loop_internal_cancel(Efl_Internal_Promise *p);
|
||||
|
||||
|
@ -3286,5 +3566,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"
|
||||
|
|
|
@ -81,6 +81,8 @@ extern int _ecore_log_dom;
|
|||
|
||||
typedef struct _Ecore_Factorized_Idle Ecore_Factorized_Idle;
|
||||
|
||||
typedef struct _Efl_Loop_Promise_Simple_Data Efl_Loop_Promise_Simple_Data;
|
||||
|
||||
typedef struct _Efl_Loop_Data Efl_Loop_Data;
|
||||
struct _Efl_Loop_Data
|
||||
{
|
||||
|
@ -187,6 +189,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,
|
||||
|
@ -367,6 +373,7 @@ GENERIC_ALLOC_FREE_HEADER(Ecore_Event, ecore_event);
|
|||
//GENERIC_ALLOC_FREE_HEADER(Ecore_Poller, ecore_poller);
|
||||
GENERIC_ALLOC_FREE_HEADER(Ecore_Pipe, ecore_pipe);
|
||||
GENERIC_ALLOC_FREE_HEADER(Ecore_Fd_Handler, ecore_fd_handler);
|
||||
GENERIC_ALLOC_FREE_HEADER(Efl_Loop_Promise_Simple_Data, efl_loop_promise_simple_data);
|
||||
#ifdef _WIN32
|
||||
GENERIC_ALLOC_FREE_HEADER(Ecore_Win32_Handler, ecore_win32_handler);
|
||||
#endif
|
||||
|
@ -382,6 +389,9 @@ void ecore_loop_promise_register(Efl_Loop *l, Efl_Promise *p);
|
|||
void ecore_loop_promise_unregister(Efl_Loop *l, Efl_Promise *p);
|
||||
void ecore_loop_promise_fulfill(Efl_Promise *p);
|
||||
|
||||
Eina_Bool efl_promise2_init(void);
|
||||
void efl_promise2_shutdown(void);
|
||||
|
||||
// access to direct input cb
|
||||
#define ECORE_EVAS_INTERNAL
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import efl_types;
|
||||
import eina_types;
|
||||
|
||||
struct Efl.Loop.Arguments {
|
||||
[[EFL loop arguments data structure]]
|
||||
|
@ -6,6 +7,41 @@ struct Efl.Loop.Arguments {
|
|||
initialization: bool; [[Set to $true when the program should initialize its internal state. This happen once per process instance.]]
|
||||
}
|
||||
|
||||
enum Efl.Loop.Coro.Prio {
|
||||
[[Priority class for the coroutine.]]
|
||||
|
||||
high = 0, [[high priority coroutine, scheduled using zero-timers (will expire as soon as possible).]]
|
||||
idle, [[low priority coroutine, scheduled when nothing else should run]]
|
||||
}
|
||||
|
||||
function Efl.Loop.Coro.Cb {
|
||||
params {
|
||||
coro: ptr(Eina.Coro); [[The coroutine handle, used to $eina_coro_yield() and voluntarily give back control to the main loop until it's rescheduled.]]
|
||||
loop: Efl.Loop; [[The loop that schedules this coroutine.]]
|
||||
}
|
||||
return: generic_value; [[Value that will resolve the promise,
|
||||
being delivered to the future chain
|
||||
attached to the coroutine. Note that the
|
||||
value will be owned by the Efl_Loop_Coro
|
||||
and Eina_Future subsystems and will be
|
||||
flushed (eina_value_flush()) once
|
||||
unused. Its contents must survive the
|
||||
function return, that is, it shouldn't
|
||||
keep pointers to the stack.
|
||||
]]
|
||||
}; [[Coroutine function, it will be called back from the
|
||||
coroutine environment and when executed it's guaranteed that the
|
||||
main loop will be paused, so shared resources are safe to access
|
||||
(no locks are required).
|
||||
|
||||
\@note Eina_Coro may use threads, then take care to handle
|
||||
thread-local-storage (TLS) details properly, eventually you
|
||||
may consider eina_coro_hook_add() to be informed when the
|
||||
main or coroutine will exit and enter. For instance this is
|
||||
used by Efl_Object (handled transparently for the user).
|
||||
]]
|
||||
|
||||
|
||||
class Efl.Loop (Efl.Object)
|
||||
{
|
||||
[[The Efl Main Loop
|
||||
|
@ -69,6 +105,72 @@ 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.]]
|
||||
}
|
||||
}
|
||||
Eina_FutureXXX_job {
|
||||
[[A future promise that will be resolved from a clean main
|
||||
loop context as soon as possible.
|
||||
|
||||
This has higher priority, for low priority use
|
||||
@.Eina_FutureXXX_idle
|
||||
]]
|
||||
return: own(ptr(Eina.Future)) /* TODO: future<void> */; [[The future handle.]]
|
||||
}
|
||||
Eina_FutureXXX_idle {
|
||||
[[A future promise that will be resolved from a clean main
|
||||
loop context as soon as the main loop is idle.
|
||||
|
||||
This is a low priority version of @.Eina_FutureXXX_job
|
||||
]]
|
||||
return: own(ptr(Eina.Future)) /* TODO: future<void> */; [[The future handle.]]
|
||||
}
|
||||
Eina_FutureXXX_timeout {
|
||||
[[A future promise that will be resolved from a clean main
|
||||
loop context after $time seconds.]]
|
||||
params {
|
||||
@in time: double; [[The time from now in second that the main loop will wait before triggering it.]]
|
||||
}
|
||||
return: own(ptr(Eina.Future)) /* future<void> */; [[The future handle.]]
|
||||
}
|
||||
coro {
|
||||
[[A future promise that will be resolved using a coroutine.
|
||||
|
||||
A coroutine is a function that will be executed
|
||||
cooperatively with the main loop. The main loop will
|
||||
schedule the coroutine, explicitly giving control to it --
|
||||
by then the main loop is paused. The coroutine must then
|
||||
finish and return, or yield control back to the main loop
|
||||
using $eina_coro_yield(). This allows for shared context
|
||||
to be safely interchanged with the main loop -- it is
|
||||
guaranteed that if the coroutine is running, the main loop
|
||||
is pause; if the main loop is running the coroutine is
|
||||
paused.
|
||||
|
||||
Coroutines are implemented with @Eina.Coro, see their API
|
||||
and how it's exposed in your language -- it may be the
|
||||
case that you don't need to worry and it will be managed
|
||||
transparently by your language/binding.
|
||||
|
||||
Once finished the coroutine returns a value, that will be
|
||||
used to resolve the promise, propagating thru the future
|
||||
chain.
|
||||
]]
|
||||
params {
|
||||
@in priority: Efl.Loop.Coro.Prio; [[The priority used to schedule the coroutine.]]
|
||||
@in func: Efl.Loop.Coro.Cb @nonull; [[The function to run as a coroutine.]]
|
||||
}
|
||||
return: own(ptr(Eina.Future)) /* future<> */; [[The future handle, it provides the value returned by $func once it exits.]]
|
||||
}
|
||||
job {
|
||||
[[Will execute that promise in the near future.]]
|
||||
params {
|
||||
|
|
|
@ -273,6 +273,8 @@ extern "C" {
|
|||
#include <eina_freeq.h>
|
||||
#include <eina_slstr.h>
|
||||
#include <eina_debug.h>
|
||||
#include <eina_promise.h>
|
||||
#include <eina_coro.h>
|
||||
|
||||
#undef EAPI
|
||||
#define EAPI
|
||||
|
|
|
@ -0,0 +1,642 @@
|
|||
/* EINA - EFL data type library
|
||||
* Copyright (C) 2017 ProFUSION embedded systems
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library;
|
||||
* if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "eina_config.h"
|
||||
#include "eina_private.h"
|
||||
#include "eina_log.h"
|
||||
#include "eina_mempool.h"
|
||||
#include "eina_lock.h"
|
||||
#include "eina_thread.h"
|
||||
#include "eina_inarray.h"
|
||||
#include "eina_promise.h"
|
||||
|
||||
/* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */
|
||||
#include "eina_safety_checks.h"
|
||||
|
||||
#include "eina_coro.h"
|
||||
#include "eina_value.h"
|
||||
#include "eina_value_util.h"
|
||||
|
||||
static Eina_Mempool *_eina_coro_mp = NULL;
|
||||
static Eina_Lock _eina_coro_lock;
|
||||
|
||||
static int _eina_coro_log_dom = -1;
|
||||
static int _eina_coro_usage = 0;
|
||||
|
||||
static int _eina_coro_hooks_walking = 0;
|
||||
static Eina_Inarray _eina_coro_hooks;
|
||||
|
||||
#ifdef CRIT
|
||||
#undef CRIT
|
||||
#endif
|
||||
#define CRIT(...) EINA_LOG_DOM_CRIT(_eina_coro_log_dom, __VA_ARGS__)
|
||||
|
||||
#ifdef ERR
|
||||
#undef ERR
|
||||
#endif
|
||||
#define ERR(...) EINA_LOG_DOM_ERR(_eina_coro_log_dom, __VA_ARGS__)
|
||||
|
||||
#ifdef INF
|
||||
#undef INF
|
||||
#endif
|
||||
#define INF(...) EINA_LOG_DOM_INFO(_eina_coro_log_dom, __VA_ARGS__)
|
||||
|
||||
#ifdef DBG
|
||||
#undef DBG
|
||||
#endif
|
||||
#define DBG(...) EINA_LOG_DOM_DBG(_eina_coro_log_dom, __VA_ARGS__)
|
||||
|
||||
typedef enum _Eina_Coro_Turn {
|
||||
EINA_CORO_TURN_MAIN = 0,
|
||||
EINA_CORO_TURN_COROUTINE
|
||||
} Eina_Coro_Turn;
|
||||
|
||||
struct _Eina_Coro {
|
||||
Eina_Coro_Cb func;
|
||||
const void *data;
|
||||
Eina_Future *awaiting;
|
||||
Eina_Lock lock;
|
||||
Eina_Condition condition;
|
||||
Eina_Thread main;
|
||||
Eina_Thread coroutine;
|
||||
Eina_Bool finished;
|
||||
Eina_Bool canceled;
|
||||
Eina_Coro_Turn turn;
|
||||
};
|
||||
|
||||
#define CORO_TURN_STR(turn) \
|
||||
(((turn) == EINA_CORO_TURN_MAIN) ? "MAIN" : "COROUTINE")
|
||||
|
||||
#define CORO_FMT "coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}"
|
||||
#define CORO_EXP(coro) \
|
||||
coro, coro->func, coro->data, \
|
||||
CORO_TURN_STR(coro->turn), \
|
||||
(void *)coro->coroutine, \
|
||||
eina_thread_self() == coro->coroutine ? '*' : 0, \
|
||||
(void *)coro->main, \
|
||||
eina_thread_self() == coro->main ? '*' : 0, \
|
||||
coro->awaiting
|
||||
|
||||
#define EINA_CORO_CHECK(coro, turn, ...) \
|
||||
do \
|
||||
{ \
|
||||
if ((!_eina_coro_mp) || (!eina_mempool_from(_eina_coro_mp, (coro)))) \
|
||||
{ \
|
||||
CRIT(#coro "=%p is invalid.", (coro)); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
else if ((turn == EINA_CORO_TURN_COROUTINE) && ((coro)->coroutine != eina_thread_self())) \
|
||||
{ \
|
||||
CRIT("must be called from coroutine! " CORO_FMT, CORO_EXP((coro))); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
else if ((turn == EINA_CORO_TURN_MAIN) && ((coro)->main != eina_thread_self())) \
|
||||
{ \
|
||||
CRIT("must be called from main thread! " CORO_FMT, CORO_EXP((coro))); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define EINA_CORO_CHECK_GOTO(coro, turn, label) \
|
||||
do \
|
||||
{ \
|
||||
if ((!_eina_coro_mp) || (!eina_mempool_from(_eina_coro_mp, (coro)))) \
|
||||
{ \
|
||||
CRIT(#coro "=%p is invalid.", (coro)); \
|
||||
goto label; \
|
||||
} \
|
||||
else if ((turn == EINA_CORO_TURN_COROUTINE) && ((coro)->coroutine != eina_thread_self())) \
|
||||
{ \
|
||||
CRIT("must be called from coroutine! " CORO_FMT, CORO_EXP((coro))); \
|
||||
goto label; \
|
||||
} \
|
||||
else if ((turn == EINA_CORO_TURN_MAIN) && ((coro)->main != eina_thread_self())) \
|
||||
{ \
|
||||
CRIT("must be called from main thread! " CORO_FMT, CORO_EXP((coro))); \
|
||||
goto label; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
|
||||
typedef struct _Eina_Coro_Hook {
|
||||
Eina_Coro_Hook_Coro_Enter_Cb coro_enter;
|
||||
Eina_Coro_Hook_Coro_Exit_Cb coro_exit;
|
||||
Eina_Coro_Hook_Main_Enter_Cb main_enter;
|
||||
Eina_Coro_Hook_Main_Exit_Cb main_exit;
|
||||
const void *data;
|
||||
} Eina_Coro_Hook;
|
||||
|
||||
EAPI Eina_Bool
|
||||
eina_coro_hook_add(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data)
|
||||
{
|
||||
Eina_Coro_Hook hook = { coro_enter, coro_exit, main_enter, main_exit, data };
|
||||
int idx;
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
|
||||
EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_hooks_walking > 0, error);
|
||||
|
||||
idx = eina_inarray_push(&_eina_coro_hooks, &hook);
|
||||
EINA_SAFETY_ON_TRUE_GOTO(idx < 0, error);
|
||||
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
return EINA_TRUE;
|
||||
|
||||
error:
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
EAPI Eina_Bool
|
||||
eina_coro_hook_del(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data)
|
||||
{
|
||||
Eina_Coro_Hook hook = { coro_enter, coro_exit, main_enter, main_exit, data };
|
||||
int idx;
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
|
||||
EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_hooks_walking > 0, error);
|
||||
|
||||
idx = eina_inarray_remove(&_eina_coro_hooks, &hook);
|
||||
EINA_SAFETY_ON_TRUE_GOTO(idx < 0, error);
|
||||
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
return EINA_TRUE;
|
||||
|
||||
error:
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
|
||||
// opposite of the coro_exit, similar to coro_enter
|
||||
static void
|
||||
_eina_coro_hooks_main_exit(Eina_Coro *coro)
|
||||
{
|
||||
const Eina_Coro_Hook *itr;
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
_eina_coro_hooks_walking++;
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
|
||||
EINA_INARRAY_FOREACH(&_eina_coro_hooks, itr)
|
||||
{
|
||||
if (!itr->main_exit) continue;
|
||||
if (itr->main_exit((void *)itr->data, coro)) continue;
|
||||
coro->canceled = EINA_TRUE;
|
||||
ERR("failed hook exit=%p data=%p for main routine " CORO_FMT,
|
||||
itr->main_exit, itr->data, CORO_EXP(coro));
|
||||
}
|
||||
}
|
||||
|
||||
// opposite of the coro_enter, similar to coro_exit
|
||||
static void
|
||||
_eina_coro_hooks_main_enter(Eina_Coro *coro)
|
||||
{
|
||||
const Eina_Coro_Hook *itr;
|
||||
|
||||
EINA_INARRAY_REVERSE_FOREACH(&_eina_coro_hooks, itr)
|
||||
{
|
||||
if (!itr->main_enter) continue;
|
||||
itr->main_enter((void *)itr->data, coro);
|
||||
}
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
_eina_coro_hooks_walking--;
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eina_coro_hooks_coro_enter(Eina_Coro *coro)
|
||||
{
|
||||
const Eina_Coro_Hook *itr;
|
||||
Eina_Bool r = EINA_TRUE;
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
_eina_coro_hooks_walking++;
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
|
||||
EINA_INARRAY_FOREACH(&_eina_coro_hooks, itr)
|
||||
{
|
||||
if (!itr->coro_enter) continue;
|
||||
if (itr->coro_enter((void *)itr->data, coro)) continue;
|
||||
r = EINA_FALSE;
|
||||
ERR("failed hook enter=%p data=%p for coroutine " CORO_FMT,
|
||||
itr->coro_enter, itr->data, CORO_EXP(coro));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
_eina_coro_hooks_coro_exit(Eina_Coro *coro)
|
||||
{
|
||||
const Eina_Coro_Hook *itr;
|
||||
|
||||
EINA_INARRAY_REVERSE_FOREACH(&_eina_coro_hooks, itr)
|
||||
{
|
||||
if (!itr->coro_exit) continue;
|
||||
itr->coro_exit((void *)itr->data, coro);
|
||||
}
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
_eina_coro_hooks_walking--;
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
}
|
||||
|
||||
static void
|
||||
_eina_coro_signal(Eina_Coro *coro, Eina_Coro_Turn turn)
|
||||
{
|
||||
DBG("signal turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
|
||||
|
||||
eina_lock_take(&coro->lock);
|
||||
coro->turn = turn;
|
||||
eina_condition_signal(&coro->condition);
|
||||
eina_lock_release(&coro->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
_eina_coro_wait(Eina_Coro *coro, Eina_Coro_Turn turn)
|
||||
{
|
||||
DBG("waiting turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
|
||||
|
||||
eina_lock_take(&coro->lock);
|
||||
while (coro->turn != turn)
|
||||
eina_condition_wait(&coro->condition);
|
||||
eina_lock_release(&coro->lock);
|
||||
|
||||
eina_main_loop_define();
|
||||
|
||||
DBG("wait is over: turn=%s " CORO_FMT, CORO_TURN_STR(turn), CORO_EXP(coro));
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eina_coro_hooks_coro_enter_and_get_canceled(Eina_Coro *coro)
|
||||
{
|
||||
if (!_eina_coro_hooks_coro_enter(coro)) return EINA_TRUE;
|
||||
return coro->canceled;
|
||||
}
|
||||
|
||||
static void *
|
||||
_eina_coro_thread(void *data, Eina_Thread t EINA_UNUSED)
|
||||
{
|
||||
Eina_Coro *coro = data;
|
||||
void *result = NULL;
|
||||
Eina_Bool canceled = EINA_FALSE;
|
||||
|
||||
_eina_coro_wait(coro, EINA_CORO_TURN_COROUTINE);
|
||||
|
||||
canceled = _eina_coro_hooks_coro_enter_and_get_canceled(coro);
|
||||
|
||||
DBG("call (canceled=%hhu) " CORO_FMT, canceled, CORO_EXP(coro));
|
||||
result = (void *)coro->func((void *)coro->data, canceled, coro);
|
||||
DBG("finished with result=%p " CORO_FMT, result, CORO_EXP(coro));
|
||||
|
||||
_eina_coro_hooks_coro_exit(coro);
|
||||
|
||||
coro->finished = EINA_TRUE;
|
||||
_eina_coro_signal(coro, EINA_CORO_TURN_MAIN);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Eina_Coro *
|
||||
_eina_coro_alloc(void)
|
||||
{
|
||||
Eina_Coro *coro = NULL;
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
if (EINA_UNLIKELY(!_eina_coro_mp))
|
||||
{
|
||||
const char *choice, *tmp;
|
||||
|
||||
#ifdef EINA_DEFAULT_MEMPOOL
|
||||
choice = "pass_through";
|
||||
#else
|
||||
choice = "chained_mempool";
|
||||
#endif
|
||||
tmp = getenv("EINA_MEMPOOL");
|
||||
if (tmp && tmp[0])
|
||||
choice = tmp;
|
||||
|
||||
_eina_coro_mp = eina_mempool_add
|
||||
(choice, "coro", NULL, sizeof(Eina_Coro), 16);
|
||||
|
||||
if (!_eina_coro_mp)
|
||||
{
|
||||
ERR("Mempool for coro cannot be allocated in coro init.");
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
coro = eina_mempool_calloc(_eina_coro_mp, sizeof(Eina_Coro));
|
||||
if (coro) _eina_coro_usage++;
|
||||
|
||||
end:
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
|
||||
return coro;
|
||||
}
|
||||
|
||||
static void
|
||||
_eina_coro_free(Eina_Coro *coro)
|
||||
{
|
||||
EINA_SAFETY_ON_NULL_RETURN(coro);
|
||||
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
|
||||
eina_mempool_free(_eina_coro_mp, coro);
|
||||
_eina_coro_usage--;
|
||||
if (_eina_coro_usage == 0)
|
||||
{
|
||||
eina_mempool_del(_eina_coro_mp);
|
||||
_eina_coro_mp = NULL;
|
||||
}
|
||||
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
}
|
||||
|
||||
EAPI Eina_Coro *
|
||||
eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
Eina_Bool r;
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(func, NULL);
|
||||
|
||||
coro = _eina_coro_alloc();
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(coro, NULL);
|
||||
|
||||
coro->func = func;
|
||||
coro->data = data;
|
||||
r = eina_lock_new(&coro->lock);
|
||||
EINA_SAFETY_ON_FALSE_GOTO(r, failed_lock);
|
||||
r = eina_condition_new(&coro->condition, &coro->lock);
|
||||
EINA_SAFETY_ON_FALSE_GOTO(r, failed_condition);
|
||||
coro->main = eina_thread_self();
|
||||
coro->coroutine = 0;
|
||||
coro->finished = EINA_FALSE;
|
||||
coro->canceled = EINA_FALSE;
|
||||
coro->turn = EINA_CORO_TURN_MAIN;
|
||||
|
||||
/* eina_thread_create() doesn't take attributes so we can set stack size */
|
||||
if (stack_size)
|
||||
DBG("currently stack size is ignored! Using thread default.");
|
||||
|
||||
if (!eina_thread_create(&coro->coroutine,
|
||||
EINA_THREAD_NORMAL, -1,
|
||||
_eina_coro_thread, coro))
|
||||
{
|
||||
ERR("could not create thread for " CORO_FMT, CORO_EXP(coro));
|
||||
goto failed_thread;
|
||||
}
|
||||
|
||||
INF(CORO_FMT, CORO_EXP(coro));
|
||||
return coro;
|
||||
|
||||
failed_thread:
|
||||
eina_condition_free(&coro->condition);
|
||||
failed_condition:
|
||||
eina_lock_free(&coro->lock);
|
||||
failed_lock:
|
||||
_eina_coro_free(coro);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EAPI Eina_Bool
|
||||
eina_coro_yield(Eina_Coro *coro)
|
||||
{
|
||||
EINA_CORO_CHECK(coro, EINA_CORO_TURN_COROUTINE, EINA_FALSE);
|
||||
|
||||
_eina_coro_hooks_coro_exit(coro);
|
||||
|
||||
_eina_coro_signal(coro, EINA_CORO_TURN_MAIN);
|
||||
_eina_coro_wait(coro, EINA_CORO_TURN_COROUTINE);
|
||||
|
||||
return !_eina_coro_hooks_coro_enter_and_get_canceled(coro);
|
||||
}
|
||||
|
||||
EAPI Eina_Bool
|
||||
eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
|
||||
if (p_result) *p_result = NULL;
|
||||
if (p_awaiting) *p_awaiting = NULL;
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(p_coro, EINA_FALSE);
|
||||
EINA_CORO_CHECK(*p_coro, EINA_CORO_TURN_MAIN, EINA_FALSE);
|
||||
|
||||
coro = *p_coro;
|
||||
|
||||
_eina_coro_hooks_main_exit(coro);
|
||||
|
||||
_eina_coro_signal(coro, EINA_CORO_TURN_COROUTINE);
|
||||
_eina_coro_wait(coro, EINA_CORO_TURN_MAIN);
|
||||
|
||||
_eina_coro_hooks_main_enter(coro);
|
||||
|
||||
if (EINA_UNLIKELY(coro->finished)) {
|
||||
void *result;
|
||||
DBG("coroutine finished, join thread " CORO_FMT, CORO_EXP(coro));
|
||||
|
||||
result = eina_thread_join(coro->coroutine);
|
||||
INF("coroutine finished with result=%p " CORO_FMT,
|
||||
result, CORO_EXP(coro));
|
||||
if (p_result) *p_result = result;
|
||||
if (coro->awaiting) eina_future_cancel(coro->awaiting);
|
||||
eina_condition_free(&coro->condition);
|
||||
eina_lock_free(&coro->lock);
|
||||
_eina_coro_free(coro);
|
||||
*p_coro = NULL;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
if (p_awaiting) *p_awaiting = coro->awaiting;
|
||||
|
||||
DBG("coroutine yielded, must run again " CORO_FMT, CORO_EXP(coro));
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
typedef struct _Eina_Coro_Await_Data {
|
||||
Eina_Coro *coro;
|
||||
Eina_Value *p_value;
|
||||
Eina_Bool resolved;
|
||||
} Eina_Coro_Await_Data;
|
||||
|
||||
static Eina_Value
|
||||
_eina_coro_await_cb(void *data, const Eina_Value value, const Eina_Future *dead_future)
|
||||
{
|
||||
Eina_Coro_Await_Data *d = data;
|
||||
|
||||
DBG("future %p resolved with value type %p (%s) " CORO_FMT,
|
||||
dead_future, value.type, value.type ? value.type->name : "EMPTY",
|
||||
CORO_EXP(d->coro));
|
||||
|
||||
if (d->p_value)
|
||||
{
|
||||
// copy is needed as value contents is flushed when this function returns.
|
||||
if (!value.type) *d->p_value = value;
|
||||
else if (!eina_value_copy(&value, d->p_value))
|
||||
{
|
||||
ERR("Value cannot be copied - unusable with Eina_Future: %p (%s)", value.type, value.type->name);
|
||||
eina_value_setup(d->p_value, EINA_VALUE_TYPE_ERROR);
|
||||
eina_value_set(d->p_value, ENOTSUP);
|
||||
}
|
||||
}
|
||||
|
||||
d->resolved = EINA_TRUE;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
EAPI Eina_Bool
|
||||
eina_coro_await(Eina_Coro *coro, Eina_Future *f, Eina_Value *p_value)
|
||||
{
|
||||
Eina_Coro_Await_Data data = { coro, p_value, EINA_FALSE };
|
||||
|
||||
if (p_value) *p_value = EINA_VALUE_EMPTY;
|
||||
|
||||
EINA_CORO_CHECK_GOTO(coro, EINA_CORO_TURN_COROUTINE, no_coro);
|
||||
EINA_SAFETY_ON_TRUE_GOTO(coro->awaiting != NULL, no_coro);
|
||||
|
||||
// storage will be NULL once future dies...
|
||||
f = eina_future_then(f, _eina_coro_await_cb, &data, &coro->awaiting);
|
||||
if (!f) return EINA_FALSE;
|
||||
|
||||
INF("await future %p " CORO_FMT, f, CORO_EXP(coro));
|
||||
while (eina_coro_yield(coro) && !data.resolved)
|
||||
DBG("future %p still pending " CORO_FMT, f, CORO_EXP(coro));
|
||||
|
||||
if (!data.resolved)
|
||||
{
|
||||
INF("future %p still pending and coroutine was canceled " CORO_FMT,
|
||||
f, CORO_EXP(coro));
|
||||
if (p_value)
|
||||
{
|
||||
eina_value_flush(p_value);
|
||||
*p_value = eina_value_error_init(ECANCELED);
|
||||
}
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
INF("future %p resolved! continue coroutine " CORO_FMT, f, CORO_EXP(coro));
|
||||
return EINA_TRUE;
|
||||
|
||||
no_coro:
|
||||
if (p_value) *p_value = eina_value_error_init(EINVAL);
|
||||
eina_future_cancel(f);
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
EAPI void *
|
||||
eina_coro_cancel(Eina_Coro **p_coro)
|
||||
{
|
||||
void *result = NULL;
|
||||
Eina_Coro *coro;
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(p_coro, NULL);
|
||||
EINA_CORO_CHECK(*p_coro, EINA_CORO_TURN_MAIN, NULL);
|
||||
|
||||
coro = *p_coro;
|
||||
|
||||
coro->canceled = EINA_TRUE;
|
||||
|
||||
if (coro->awaiting) eina_future_cancel(coro->awaiting);
|
||||
|
||||
DBG("marked as canceled, run so it can exit... " CORO_FMT, CORO_EXP(coro));
|
||||
while (eina_coro_run(p_coro, &result, NULL))
|
||||
DBG("did not exited, try running again..." CORO_FMT, CORO_EXP(coro));
|
||||
|
||||
// be careful, coro is dead!
|
||||
INF("coroutine %p canceled and returned %p", coro, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Initialize the coroutine module.
|
||||
*
|
||||
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
|
||||
*
|
||||
* This function sets up the coroutine module of Eina. It is called by
|
||||
* eina_init().
|
||||
*
|
||||
* This function creates mempool to speed up and keep safety of coro
|
||||
* handles, using EINA_MEMPOOL environment variable if it is set to
|
||||
* choose the memory pool type to use.
|
||||
*
|
||||
* @see eina_init()
|
||||
*/
|
||||
Eina_Bool
|
||||
eina_coro_init(void)
|
||||
{
|
||||
_eina_coro_log_dom = eina_log_domain_register("eina_coro",
|
||||
EINA_LOG_COLOR_DEFAULT);
|
||||
if (_eina_coro_log_dom < 0)
|
||||
{
|
||||
EINA_LOG_ERR("Could not register log domain: eina_coro");
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
eina_lock_new(&_eina_coro_lock);
|
||||
_eina_coro_usage = 0;
|
||||
_eina_coro_hooks_walking = 0;
|
||||
eina_inarray_step_set(&_eina_coro_hooks, sizeof(_eina_coro_hooks),
|
||||
sizeof(Eina_Coro_Hook), 1);
|
||||
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Shut down the coroutine module.
|
||||
*
|
||||
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
|
||||
*
|
||||
* This function shuts down the coroutine module set up by
|
||||
* eina_coro_init(). It is called by eina_shutdown().
|
||||
*
|
||||
* @see eina_shutdown()
|
||||
*/
|
||||
Eina_Bool
|
||||
eina_coro_shutdown(void)
|
||||
{
|
||||
eina_lock_take(&_eina_coro_lock);
|
||||
EINA_SAFETY_ON_TRUE_GOTO(_eina_coro_usage > 0, in_use);
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
eina_lock_free(&_eina_coro_lock);
|
||||
eina_inarray_flush(&_eina_coro_hooks);
|
||||
|
||||
eina_log_domain_unregister(_eina_coro_log_dom);
|
||||
_eina_coro_log_dom = -1;
|
||||
return EINA_TRUE;
|
||||
|
||||
in_use:
|
||||
eina_lock_release(&_eina_coro_lock);
|
||||
return EINA_FALSE;
|
||||
}
|
|
@ -0,0 +1,554 @@
|
|||
/* EINA - EFL data type library
|
||||
* Copyright (C) 2017 ProFUSION embedded systems
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library;
|
||||
* if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EINA_CORO_H_
|
||||
#define EINA_CORO_H_
|
||||
|
||||
#include "eina_config.h"
|
||||
#include "eina_types.h"
|
||||
#include "eina_error.h"
|
||||
|
||||
typedef struct _Eina_Future Eina_Future;
|
||||
typedef struct _Eina_Value Eina_Value;
|
||||
|
||||
/**
|
||||
* @addtogroup Eina_Tools_Group Tools
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup Eina_Coro_Group Co-routines
|
||||
*
|
||||
* Co-routines are cooperative threads, that is, their execution will
|
||||
* stop the caller's thread, execute the coroutine until it finishes
|
||||
* or yield, then give back control to the caller thread.
|
||||
*
|
||||
* The purpose of this primitive is to allow two functions to run with
|
||||
* their own stack and registers and be sure that the caller thread
|
||||
* won't run meanwhile, then shared context (variables, pointers) do
|
||||
* not need locks as this is done implicitly by the design: if one
|
||||
* thread is running you can be sure the other is not. The coroutine
|
||||
* must explicitly give back control to the caller thread, either by
|
||||
* eina_coro_yield() or by return.
|
||||
*
|
||||
* Multiple coroutines may exist at a given time, however if they are
|
||||
* managed by the same caller then it's guaranteed that they will
|
||||
* cooperate among themselves.
|
||||
*
|
||||
* @note The current implementation @b may use real threads with a
|
||||
* lock and condition variable to ensure the behavior, this is
|
||||
* an implementation detail that must not be relied upon.
|
||||
* Depending on the platform it may use ucontext.h (SysV-like)
|
||||
* or custom task save/restore. Libraries can use
|
||||
* eina_coro_hook_add() to be called when the coroutine code
|
||||
* will enter and exit, being able to retrieve context and set
|
||||
* some other locks such or Efl_Object's efl_domain_data_adopt()
|
||||
* (done automatically from efl_object_init()).
|
||||
*
|
||||
* @see @ref Eina_Thread_Group for regular concurrent threads.
|
||||
*
|
||||
* @since 1.21
|
||||
* @{
|
||||
*/
|
||||
|
||||
typedef struct _Eina_Coro Eina_Coro;
|
||||
|
||||
/**
|
||||
* @typedef Eina_Coro_Hook_Coro_Enter_Cb
|
||||
*
|
||||
* @brief Type for the definition of a coroutine hook.
|
||||
*
|
||||
* The pointer will be called back with the given @c data and the
|
||||
* coroutine that will be entered or exited.
|
||||
*
|
||||
* The coroutine "enters" when eina_coro_run() is called and "exits"
|
||||
* when the provided function returns or calls eina_coro_yield().
|
||||
*
|
||||
* If the callback returns #EINA_FALSE, then eina_coro_yield() will
|
||||
* return that value, meaning the coroutine should voluntarily exit.
|
||||
*
|
||||
* All hooks are called, and if any of them returns #EINA_FALSE,
|
||||
* eina_coro_yield() will return the same.
|
||||
*
|
||||
* If #EINA_FALSE is returned prior the first execution of the
|
||||
* coroutine, then the coroutine will get a canceled #EINA_TRUE as
|
||||
* parameter. This allows coroutine to cleanup whatever is needed
|
||||
* and return.
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
typedef Eina_Bool (*Eina_Coro_Hook_Coro_Enter_Cb)(void *data, const Eina_Coro *coro);
|
||||
|
||||
/**
|
||||
* @typedef Eina_Coro_Hook_Coro_Exit_Cb
|
||||
*
|
||||
* @brief Type for the definition of a coroutine exit hook.
|
||||
*
|
||||
* The pointer will be called back with the given @c data and the
|
||||
* coroutine that exited.
|
||||
*
|
||||
* The coroutine "enters" when eina_coro_run() is called and "exits"
|
||||
* when the provided function returns or calls eina_coro_yield().
|
||||
*
|
||||
* Exit hooks are always called in the reverse order they were added,
|
||||
* that is, the last added hook will run first (stack).
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
typedef void (*Eina_Coro_Hook_Coro_Exit_Cb)(void *data, const Eina_Coro *coro);
|
||||
|
||||
/**
|
||||
* @typedef Eina_Coro_Hook_Main_Enter_Cb
|
||||
*
|
||||
* @brief Type for the definition of a main routine hook.
|
||||
*
|
||||
* The pointer will be called back with the given @c data and the
|
||||
* coroutine that will be entered or exited.
|
||||
*
|
||||
* The coroutine "enters" when eina_coro_run() is called and "exits"
|
||||
* when the provided function returns or calls eina_coro_yield().
|
||||
*
|
||||
* Unlike the coroutine exit hooks, the main routine Exit hooks are
|
||||
* always called in the reverse order they were added, that is, the
|
||||
* last added hook will run first (stack). This is because they match
|
||||
* Eina_Coro_Hook_Coro_Exit_Cb, a coroutine exits so the main routine
|
||||
* can enter.
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
typedef void (*Eina_Coro_Hook_Main_Enter_Cb)(void *data, const Eina_Coro *coro);
|
||||
|
||||
/**
|
||||
* @typedef Eina_Coro_Hook_Main_Exit_Cb
|
||||
*
|
||||
* @brief Type for the definition of a main routine exit hook.
|
||||
*
|
||||
* The pointer will be called back with the given @c data and the
|
||||
* coroutine that exited.
|
||||
*
|
||||
* The coroutine "enters" when eina_coro_run() is called and "exits"
|
||||
* when the provided function returns or calls eina_coro_yield().
|
||||
*
|
||||
* Unlike the coroutine enter hooks, the main routine Enter hooks are
|
||||
* called in forward order, that is, the first added hook will run
|
||||
* first. This is because they match Eina_Coro_Hook_Coro_Enter_Cb, a
|
||||
* main routine exits so the coroutine can enter.
|
||||
*
|
||||
* If the callback returns #EINA_FALSE, then eina_coro_yield() will
|
||||
* return that value, meaning the coroutine should voluntarily exit.
|
||||
*
|
||||
* All hooks are called, and if any of them returns #EINA_FALSE,
|
||||
* eina_coro_yield() will return the same.
|
||||
*
|
||||
* If #EINA_FALSE is returned prior the first execution of the
|
||||
* coroutine, then the coroutine will get a canceled #EINA_TRUE as
|
||||
* parameter. This allows coroutine to cleanup whatever is needed
|
||||
* and return.
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
typedef Eina_Bool (*Eina_Coro_Hook_Main_Exit_Cb)(void *data, const Eina_Coro *coro);
|
||||
|
||||
|
||||
/**
|
||||
* Adds a hook to the coroutine subsystem.
|
||||
*
|
||||
* The coroutine "enters" when eina_coro_run() is called and "exits"
|
||||
* when the provided function returns or calls eina_coro_yield().
|
||||
*
|
||||
* The main routine (the caller) is the opposite: when eina_coro_run()
|
||||
* it will "exit" and it will "enter" before eina_coro_run() returns.
|
||||
*
|
||||
* Enter hooks are executed in order, while exit hooks are always
|
||||
* called in the reverse order they were added, that is, the last
|
||||
* added hook will run first (stack).
|
||||
*
|
||||
* If any enter hooks fail, then eina_coro_yield() will return
|
||||
* #EINA_FALSE or the parameter @c canceled as #EINA_TRUE will be
|
||||
* given to the coroutine function. This allows coroutines to cleanup
|
||||
* and exit.
|
||||
*
|
||||
* The flow is the following:
|
||||
*
|
||||
* @li main_exit is called to notify main routine will be stopped.
|
||||
* @li coro_enter is called to notify the coroutine will be started.
|
||||
* @li coro_exit is called to notify the coroutine stopped.
|
||||
* @li main_enter is called to notify main routine will be resumed.
|
||||
*
|
||||
* They may be useful to properly setup environment prior to callback
|
||||
* user code, like Efl_Object must check if we're running in an actual
|
||||
* thread and adopt Efl_Domain_Data so objects are accessible from the
|
||||
* coroutine -- this is done automatically by efl_object_init().
|
||||
*
|
||||
* @note this must @b NOT be called from within the coroutine itself,
|
||||
* usually do this from your "init".
|
||||
*
|
||||
* @param coro_enter the hook to be called when the coroutine
|
||||
* enters. May be #NULL if no enter hook is needed.
|
||||
* @param coro_exit the hook to be called when the coroutine
|
||||
* exits. May be #NULL if no exit hook is needed.
|
||||
* @param main_enter the hook to be called when the caller
|
||||
* enters. May be #NULL if no enter hook is needed.
|
||||
* @param main_exit the hook to be called when the caller
|
||||
* exits. May be #NULL if no exit hook is needed.
|
||||
* @param data the context to pass to hooks. May be #NULL if no
|
||||
* context is needed.
|
||||
* @return #EINA_TRUE on success, #EINA_FALSE on failure.
|
||||
*
|
||||
* @since 1.12
|
||||
*/
|
||||
EAPI Eina_Bool eina_coro_hook_add(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data);
|
||||
|
||||
/**
|
||||
* Removes a hook from the coroutine subsystem.
|
||||
*
|
||||
* @note this must @b NOT be called from within the coroutine itself,
|
||||
* usually do this from your "init".
|
||||
*
|
||||
* @param coro_enter the pointer that was given to eina_coro_hook_add().
|
||||
* @param coro_exit the pointer that was given to eina_coro_hook_add().
|
||||
* @param main_enter the pointer that was given to eina_coro_hook_add().
|
||||
* @param main_exit the pointer that was given to eina_coro_hook_add().
|
||||
* @param data the pointer that was given to eina_coro_hook_add().
|
||||
* @return #EINA_TRUE on success, #EINA_FALSE on failure (ie: not found).
|
||||
*
|
||||
* @since 1.12
|
||||
*/
|
||||
EAPI Eina_Bool eina_coro_hook_del(Eina_Coro_Hook_Coro_Enter_Cb coro_enter, Eina_Coro_Hook_Coro_Exit_Cb coro_exit, Eina_Coro_Hook_Main_Enter_Cb main_enter, Eina_Coro_Hook_Main_Exit_Cb main_exit, const void *data);
|
||||
|
||||
/**
|
||||
* @typedef Eina_Coro_Cb
|
||||
* Type for the definition of a coroutine callback function.
|
||||
*
|
||||
* Once eina_coro_new() returns non-NULL, this function will be always
|
||||
* called. However it may receive @c canceled as #EINA_TRUE, in this
|
||||
* case it should cleanup and exit as soon as possible.
|
||||
*
|
||||
* @note The current implementation @b may use real threads with a
|
||||
* lock and condition variable to ensure the behavior, this is
|
||||
* an implementation detail that must not be relied upon.
|
||||
* Depending on the platform it may use ucontext.h (SysV-like)
|
||||
* or custom task save/restore. Libraries can use
|
||||
* eina_coro_hook_add() to be called when the coroutine code
|
||||
* will enter and exit, being able to retrieve context and set
|
||||
* some other locks such as the Python's GIL or Efl_Object's
|
||||
* efl_domain_data_adopt() (done automatically from
|
||||
* efl_object_init()).
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
typedef const void *(*Eina_Coro_Cb)(void *data, Eina_Bool canceled, Eina_Coro *coro);
|
||||
|
||||
/**
|
||||
* @def EINA_CORO_STACK_SIZE_DEFAULT
|
||||
*
|
||||
* Use the system's default stack size, usually @c PTHREAD_STACK_MIN
|
||||
* (16Kb - 16384 bytes).
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
#define EINA_CORO_STACK_SIZE_DEFAULT 0
|
||||
|
||||
/**
|
||||
* @brief Creates a new coroutine.
|
||||
*
|
||||
* Allocates a coroutine environment using the given @a stack_size to
|
||||
* execute @a func with the given context @a data. The coroutine must
|
||||
* be explicitly executed with eina_coro_run(), that will stop the
|
||||
* caller and let the coroutine work.
|
||||
*
|
||||
* @note The coroutine is @b not executed when it's created, that is
|
||||
* managed by eina_coro_run().
|
||||
*
|
||||
* @note Currently @a stack_size is ignored, the default thread stack
|
||||
* size will be used.
|
||||
*
|
||||
* @param func function to run in the coroutine. Must @b not be @c NULL.
|
||||
* @param data context data to provide to @a func as first argument.
|
||||
* @param stack_size defines the stack size to use to run the function
|
||||
* @a func. Usually must be multiple of @c PAGE_SIZE and most
|
||||
* systems will define a minimum stack limit such as 16Kb -
|
||||
* those nuances are handled automatically for you. Most users
|
||||
* want #EINA_CORO_STACK_SIZE_DEFAULT.
|
||||
* @return newly allocated coroutine handle on success, #NULL on failure.
|
||||
*
|
||||
* @see eina_coro_run()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
EAPI Eina_Coro *eina_coro_new(Eina_Coro_Cb func, const void *data, size_t stack_size) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* @brief Yields control from coroutine to caller.
|
||||
*
|
||||
* This @b must be called from within the @b coroutine and will pause it,
|
||||
* giving back control to the caller of eina_coro_run(). The coroutine
|
||||
* will remain stalled until eina_coro_run() is executed again.
|
||||
*
|
||||
* Yield can return #EINA_FALSE if any hooks fail, see
|
||||
* eina_coro_hook_add(), or if the coroutine is canceled with
|
||||
* eina_coro_cancel().
|
||||
*
|
||||
* @note The current implementation @b may use real threads with a
|
||||
* lock and condition variable to ensure the behavior, this is
|
||||
* an implementation detail that must not be relied upon.
|
||||
* Depending on the platform it may use ucontext.h (SysV-like)
|
||||
* or custom task save/restore. Libraries can use
|
||||
* eina_coro_hook_add() to be called when the coroutine code
|
||||
* will enter and exit, being able to retrieve context and set
|
||||
* some other locks such as the Python's GIL or Efl_Object's
|
||||
* efl_domain_data_adopt() (done automatically from
|
||||
* efl_object_init()).
|
||||
*
|
||||
* @param coro the coroutine that will yield control. Must not be #NULL.
|
||||
*
|
||||
* @return #EINA_TRUE if coroutine is allowed to continue,
|
||||
* #EINA_FALSE if the coroutine should cleanup and exit.
|
||||
* Refer to macros eina_coro_yield_or_return() or
|
||||
* eina_coro_yield_or_goto() to aid such tasks.
|
||||
*
|
||||
* @see eina_coro_run()
|
||||
* @see eina_coro_new()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
EAPI Eina_Bool eina_coro_yield(Eina_Coro *coro) EINA_ARG_NONNULL(1) EINA_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* #def eina_coro_yield_or_return(coro, val)
|
||||
*
|
||||
* This will yield control back to the main thread and wait. If the
|
||||
* yield returns #EINA_FALSE then it will return the given value.
|
||||
*
|
||||
* @param coro the coroutine handle. Must not be #NULL.
|
||||
* @param val the value to return (void *) if yield returns #EINA_FALSE.
|
||||
*
|
||||
* @see eina_coro_yield()
|
||||
* @see eina_coro_yield_or_goto()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
#define eina_coro_yield_or_return(coro, val) \
|
||||
do { if (!eina_coro_yield(coro)) return (val); } while (0)
|
||||
|
||||
/**
|
||||
* #def eina_coro_yield_or_goto(coro, label)
|
||||
*
|
||||
* This will yield control back to the main thread and wait. If the
|
||||
* yield returns #EINA_FALSE then it will goto the given label.
|
||||
*
|
||||
* @param coro the coroutine handle. Must not be #NULL.
|
||||
* @param label the label to jump if yield returns #EINA_FALSE.
|
||||
*
|
||||
* @see eina_coro_yield()
|
||||
* @see eina_coro_yield_or_return()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
#define eina_coro_yield_or_goto(coro, label) \
|
||||
do { if (!eina_coro_yield(coro)) goto label; } while (0)
|
||||
|
||||
/**
|
||||
* @brief Yield control from coroutine to the caller and report it's
|
||||
* awaiting a future to be resolved.
|
||||
*
|
||||
* This @b must be called from within the @b coroutine and will pause
|
||||
* it, giving back control to the caller of eina_coro_run(). The
|
||||
* coroutine will remain stalled until eina_coro_run() is executed
|
||||
* again. The caller of eina_coro_run() will get @c p_awaiting set so
|
||||
* it can wait for the future to be resolved and improve its
|
||||
* scheduling. Note that this will busy wait using eina_coro_yield()
|
||||
* until the future resolves, then it's safe to naively call
|
||||
* eina_coro_run() if the future is still pending -- but it will keep
|
||||
* CPU consumption to its maximum.
|
||||
*
|
||||
* Await can return #EINA_FALSE if any hooks fail, see
|
||||
* eina_coro_hook_add(), or if the coroutine is canceled with
|
||||
* eina_coro_cancel().
|
||||
*
|
||||
* @note The current implementation @b may use real threads with a
|
||||
* lock and condition variable to ensure the behavior, this is
|
||||
* an implementation detail that must not be relied upon.
|
||||
* Depending on the platform it may use ucontext.h (SysV-like)
|
||||
* or custom task save/restore. Libraries can use
|
||||
* eina_coro_hook_add() to be called when the coroutine code
|
||||
* will enter and exit, being able to retrieve context and set
|
||||
* some other locks such as the Python's GIL or Efl_Object's
|
||||
* efl_domain_data_adopt() (done automatically from
|
||||
* efl_object_init()).
|
||||
*
|
||||
* @param coro the coroutine that will await control. Must not be #NULL.
|
||||
* @param future the future that this will wait. Must not be #NULL.
|
||||
* @param[out] p_value the pointer to value containing the future
|
||||
* resolution. If given the value will be owned by caller and
|
||||
* must be released with eina_value_flush(). May be #NULL.
|
||||
*
|
||||
* @return #EINA_TRUE if coroutine is allowed to continue,
|
||||
* #EINA_FALSE if the coroutine should cleanup and exit.
|
||||
* Refer to macros eina_coro_await_or_return() or
|
||||
* eina_coro_await_or_goto() to aid such tasks.
|
||||
*
|
||||
* @see eina_coro_run()
|
||||
* @see eina_coro_new()
|
||||
* @see eina_coro_yield()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
EAPI Eina_Bool eina_coro_await(Eina_Coro *coro, Eina_Future *future, Eina_Value *p_value) EINA_ARG_NONNULL(1, 2) EINA_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* #def eina_coro_await_or_return(coro, future, p_value, val)
|
||||
*
|
||||
* This will yield control back to the main thread and wait for future
|
||||
* to be resolved. If the await returns #EINA_FALSE then it will
|
||||
* return the given value.
|
||||
*
|
||||
* @param coro the coroutine handle. Must not be #NULL.
|
||||
* @param future the future that this will wait. Must not be #NULL.
|
||||
* @param[out] p_value the pointer to value containing the future
|
||||
* resolution. If given the value will be owned by caller and
|
||||
* must be released with eina_value_flush(). May be #NULL.
|
||||
* @param val the value to return (void *) if await returns #EINA_FALSE.
|
||||
*
|
||||
* @see eina_coro_await()
|
||||
* @see eina_coro_await_or_goto()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
#define eina_coro_await_or_return(coro, future, p_value, val) \
|
||||
do { if (!eina_coro_await(coro, future, p_value)) return (val); } while (0)
|
||||
|
||||
/**
|
||||
* #def eina_coro_await_or_goto(coro, label)
|
||||
*
|
||||
* This will yield control back to the main thread and wait for future
|
||||
* to be resolved. If the await returns #EINA_FALSE then it will goto
|
||||
* the given label.
|
||||
*
|
||||
* @param coro the coroutine handle. Must not be #NULL.
|
||||
* @param future the future that this will wait. Must not be #NULL.
|
||||
* @param[out] p_value the pointer to value containing the future
|
||||
* resolution. If given the value will be owned by caller and
|
||||
* must be released with eina_value_flush(). May be #NULL.
|
||||
* @param label the label to jump if await returns #EINA_FALSE.
|
||||
*
|
||||
* @see eina_coro_await()
|
||||
* @see eina_coro_await_or_return()
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
#define eina_coro_await_or_goto(coro, future, p_value, label) \
|
||||
do { if (!eina_coro_await(coro, future, p_value)) goto label; } while (0)
|
||||
|
||||
|
||||
/**
|
||||
* @brief Run the coroutine and report if it's still alive.
|
||||
*
|
||||
* This @b must be called from @b outside the coroutine, what we call
|
||||
* "the caller side", also known as "main thread". The caller will be
|
||||
* paused and control will be handled to the coroutine until it
|
||||
* eina_coro_yield() or exits (returns).
|
||||
*
|
||||
* If the coroutine exits (returns), then this function will free the
|
||||
* coroutine created, make @a p_coro #NULL, set @a p_result to
|
||||
* whatever the Eina_Coro_Cb returned and return #EINA_FALSE.
|
||||
*
|
||||
* If the coroutine yields, then this function will return #EINA_TRUE,
|
||||
* meaning it must be called again.
|
||||
*
|
||||
* @note The current implementation @b may use real threads with a
|
||||
* lock and condition variable to ensure the behavior, this is
|
||||
* an implementation detail that must not be relied upon.
|
||||
* Depending on the platform it may use ucontext.h (SysV-like)
|
||||
* or custom task save/restore. Libraries can use
|
||||
* eina_coro_hook_add() to be called when the coroutine code
|
||||
* will enter and exit, being able to retrieve context and set
|
||||
* some other locks such as the Python's GIL or Efl_Object's
|
||||
* efl_domain_data_adopt() (done automatically from
|
||||
* efl_object_init()).
|
||||
*
|
||||
* @param[inout] p_coro pointer to the coroutine that will be
|
||||
* executed. Must @b not be #NULL.
|
||||
* @param[out] p_result if the coroutine exited, then will be set to the
|
||||
* returned value. May be #NULL.
|
||||
* @param[out] p_awaiting if the coroutine is awaiting a future to be
|
||||
* resolved, then the handle. The scheduler should consider
|
||||
* eina_future_then() and only call the coroutine once it's
|
||||
* resolved. This happens when coroutine used eina_coro_await()
|
||||
* instead of eina_coro_yield().
|
||||
*
|
||||
* @return #EINA_TRUE if the coroutine yielded and the user must call
|
||||
* eina_coro_run() again. #EINA_FALSE if the coroutine exited,
|
||||
* then @a p_coro will point to #NULL and if @a p_result is
|
||||
* given it will be set to whatever the coroutine @c func returned.
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
EAPI Eina_Bool eina_coro_run(Eina_Coro **p_coro, void **p_result, Eina_Future **p_awaiting) EINA_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @brief Cancel the coroutine and wait for it to finish.
|
||||
*
|
||||
* This @b must be called from @b outside the coroutine, what we call
|
||||
* "the caller side", also known as "main thread". The caller will be
|
||||
* paused and control will be handled to the coroutine until it exits
|
||||
* (returns).
|
||||
*
|
||||
* This function will free the coroutine created, make @a p_coro
|
||||
* #NULL, set @a p_result to whatever the Eina_Coro_Cb returned and
|
||||
* return #EINA_FALSE.
|
||||
*
|
||||
* @note This will busy wait on eina_coro_run() until the coroutine @b
|
||||
* voluntarily finishes, it's not aborted in any way. The
|
||||
* primitive eina_coro_yield() will return #EINA_FALSE when the
|
||||
* coroutine is canceled, this gives the opportunity to cleanup
|
||||
* and exit. Likewise, if canceled before it's ever ran, then
|
||||
* it will pass @c canceled as #EINA_TRUE to the Eina_Coro_Cb
|
||||
* given to eina_coro_new()
|
||||
*
|
||||
* @note The current implementation @b may use real threads with a
|
||||
* lock and condition variable to ensure the behavior, this is
|
||||
* an implementation detail that must not be relied upon.
|
||||
* Depending on the platform it may use ucontext.h (SysV-like)
|
||||
* or custom task save/restore. Libraries can use
|
||||
* eina_coro_hook_add() to be called when the coroutine code
|
||||
* will enter and exit, being able to retrieve context and set
|
||||
* some other locks such as the Python's GIL or Efl_Object's
|
||||
* efl_domain_data_adopt() (done automatically from
|
||||
* efl_object_init()).
|
||||
*
|
||||
* @param[inout] p_coro pointer to the coroutine that will be
|
||||
* executed. Must @b not be #NULL.
|
||||
*
|
||||
* @return The value returned by the coroutine function, same as would
|
||||
* be returned in eina_coro_run()'s @c p_result parameter.
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
EAPI void *eina_coro_cancel(Eina_Coro **p_coro) EINA_ARG_NONNULL(1);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif
|
|
@ -74,6 +74,7 @@
|
|||
#include "eina_evlog.h"
|
||||
#include "eina_freeq.h"
|
||||
#include "eina_slstr.h"
|
||||
#include "eina_coro.h"
|
||||
|
||||
/*============================================================================*
|
||||
* Local *
|
||||
|
@ -159,6 +160,8 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
|
|||
S(file);
|
||||
S(safepointer);
|
||||
S(slstr);
|
||||
S(promise);
|
||||
S(coro);
|
||||
#undef S
|
||||
|
||||
struct eina_desc_setup
|
||||
|
@ -205,6 +208,8 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
|
|||
S(file),
|
||||
S(safepointer),
|
||||
S(slstr),
|
||||
S(promise),
|
||||
S(coro),
|
||||
#undef S
|
||||
};
|
||||
static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) /
|
||||
|
@ -305,7 +310,6 @@ eina_init(void)
|
|||
eina_cpu_count_internal();
|
||||
|
||||
eina_log_timing(_eina_log_dom, EINA_LOG_STATE_STOP, EINA_LOG_STATE_INIT);
|
||||
|
||||
_eina_main_count = 1;
|
||||
eina_evlog("-eina_init", NULL, 0.0, NULL);
|
||||
return 1;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
247
src/lib/eo/Eo.h
247
src/lib/eo/Eo.h
|
@ -340,6 +340,251 @@ EOAPI Eina_Bool efl_event_callback_legacy_call(Eo *obj, const Efl_Event_Descript
|
|||
*/
|
||||
EOAPI Eina_Bool efl_future_link(Eo *obj, Efl_Future *link);
|
||||
|
||||
|
||||
/**
|
||||
* @struct _Efl_Future_Cb_Desc
|
||||
*
|
||||
* A struct with callbacks to be used by efl_future_cb_from_desc() and efl_future_chain_array()
|
||||
*
|
||||
* @see efl_future_cb_from_desc()
|
||||
* @see efl_future_chain_array()
|
||||
*/
|
||||
typedef struct _Efl_Future_Cb_Desc {
|
||||
/**
|
||||
* Called on success (value.type is not @c EINA_VALUE_TYPE_ERROR).
|
||||
*
|
||||
* if @c success_type is not NULL, then the value is guaranteed to be of that type,
|
||||
* if it's not, then it will trigger @c error with @c EINVAL.
|
||||
*
|
||||
* After this function returns, @c free callback is called if provided.
|
||||
*
|
||||
* @note This function is always called from a safe context (main loop or some platform defined safe context).
|
||||
*
|
||||
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array().
|
||||
* @param value The operation result
|
||||
* @return An Eina_Value to pass to the next Eina_Future in the chain (if any).
|
||||
* If there is no need to convert the received value, it's @b recommended
|
||||
* to pass-thru @p value argument. If you need to convert to a different type
|
||||
* or generate a new value, use @c eina_value_setup() on @b another Eina_Value
|
||||
* and return it. By returning an promise Eina_Value (eina_promise_as_value()) the
|
||||
* whole chain will wait until the promise is resolved in
|
||||
* order to continue its execution.
|
||||
* Note that the value contents must survive this function scope,
|
||||
* that is, do @b not use stack allocated blobs, arrays, structures or types that
|
||||
* keeps references to memory you give. Values will be automatically cleaned up
|
||||
* using @c eina_value_flush() once they are unused (no more future or futures
|
||||
* returned a new value).
|
||||
*/
|
||||
Eina_Value (*success)(Eo *o, const Eina_Value value);
|
||||
/**
|
||||
* Called on error (value.type is @c EINA_VALUE_TYPE_ERROR).
|
||||
*
|
||||
* This function can return another error, propagating or converting it. However it
|
||||
* may also return a non-error, in this case the next future in chain will receive a regular
|
||||
* value, which may call its @c success.
|
||||
*
|
||||
* If this function is not provided, then it will pass thru the error to the next error handler.
|
||||
*
|
||||
* It may be called with @c EINVAL if @c success_type is provided and doesn't
|
||||
* match the received type.
|
||||
*
|
||||
* It may be called with @c ECANCELED if future was canceled.
|
||||
*
|
||||
* It may be called with @c ENOMEM if memory allocation failed during callback creation.
|
||||
*
|
||||
* After this function returns, @c free callback is called if provided.
|
||||
*
|
||||
* @note On future creation errors and future cancellation this function will be called
|
||||
* from the current context with the following errors respectitally: `EINVAL`, `ENOMEM` and `ECANCELED`.
|
||||
* Otherwise this function is called from a safe context.
|
||||
*
|
||||
*
|
||||
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array().
|
||||
* @param error The operation error
|
||||
* @return An Eina_Value to pass to the next Eina_Future in the chain (if any).
|
||||
* If you need to convert to a different type or generate a new value,
|
||||
* use @c eina_value_setup() on @b another Eina_Value
|
||||
* and return it. By returning an promise Eina_Value (eina_promise_as_value()) the
|
||||
* whole chain will wait until the promise is resolved in
|
||||
* order to continue its execution.
|
||||
* Note that the value contents must survive this function scope,
|
||||
* that is, do @b not use stack allocated blobs, arrays, structures or types that
|
||||
* keeps references to memory you give. Values will be automatically cleaned up
|
||||
* using @c eina_value_flush() once they are unused (no more future or futures
|
||||
* returned a new value).
|
||||
*/
|
||||
Eina_Value (*error)(Eo *o, Eina_Error error);
|
||||
/**
|
||||
* Called on @b all situations to notify future destruction.
|
||||
*
|
||||
* This is called after @c success or @c error, as well as it's called if none of them are
|
||||
* provided. Thus can be used as a "weak ref" mechanism.
|
||||
*
|
||||
* @note On future creation errors and future cancellation this function will be called
|
||||
* from the current context with the following errors respectitally: `EINVAL`, `ENOMEM` and `ECANCELED`.
|
||||
* Otherwise this function is called from a safe context.
|
||||
*
|
||||
* @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array().
|
||||
* @param dead_future The future that was freed.
|
||||
*/
|
||||
void (*free)(Eo *o, const Eina_Future *dead_future);
|
||||
/**
|
||||
* If provided, then @c success will only be called if the value type matches the given pointer.
|
||||
*
|
||||
* If provided and doesn't match, then @c error will be called with @c EINVAL. If no @c error,
|
||||
* then it will be propagated to the next future in the chain.
|
||||
*/
|
||||
const Eina_Value_Type *success_type;
|
||||
/**
|
||||
* This is used by Eo to cancel a pending futures in case
|
||||
* an Eo object is deleted. It can be @c NULL.
|
||||
*/
|
||||
Eina_Future **storage;
|
||||
} Efl_Future_Cb_Desc;
|
||||
|
||||
/**
|
||||
* Creates an Eina_Future_Desc for an EO object.
|
||||
*
|
||||
* This function creates an Eina_Future_Desc based on an Efl_Future_Cb_Desc.
|
||||
* The main purpose of this function is create a "link" between the future
|
||||
* and the object. In case the object is deleted before the future is resolved/rejected,
|
||||
* the object destructor will cancel the future.
|
||||
*
|
||||
* @note In case context info are needed for the #Efl_Future_Desc callbacks efl_key_data_set()
|
||||
* can be used.
|
||||
*
|
||||
* The example below shows a file download using an Eo object, if the download
|
||||
* lasts more than 30 seconds the Eo object will be deleted, causing the
|
||||
* future to also be deleted.
|
||||
* Usually this would be done with an eina_future_race() of the download promise and a timeout promise,
|
||||
* however we provide the following example to illustrate efl_key_data_set() usage.
|
||||
*
|
||||
* @code
|
||||
*
|
||||
* static Eina_Bool
|
||||
* _timeout(void *data)
|
||||
* {
|
||||
* Eo *downloader = data;
|
||||
* //In case the download is not completed yet.
|
||||
* //Delete the downloader (which in cancel the file download and the future)
|
||||
* efl_key_data_set(downloader, "timer", NULL);
|
||||
* efl_unref(downloader);
|
||||
* return EINA_FALSE;
|
||||
* }
|
||||
*
|
||||
* static Eina_Value
|
||||
* _file_ok(Eo *o EINA_UNUSED, const Eina_Value value)
|
||||
* {
|
||||
* const char *data;
|
||||
* //There's no need to check the value type since EO infra already did that for us
|
||||
* eina_value_get(&value, &data);
|
||||
* //Deliver the data to the user
|
||||
* data_deliver(data);
|
||||
* return v;
|
||||
* }
|
||||
*
|
||||
* static Eina_Value
|
||||
* _file_err(Eo *o EINA_UNUSED, Eina_Error error)
|
||||
* {
|
||||
* //In case the downloader is deleted before the future is resolved, the future will be canceled thus this callback will be called.
|
||||
* fprintf(stderr, "Could not download the file. Reason: %s\n", eina_error_msg_get(error));
|
||||
* return EINA_VALUE_EMPTY;
|
||||
* }
|
||||
*
|
||||
* static void
|
||||
* _downlader_free(Eo *o, const Eina_Future *dead_future EINA_UNUSED)
|
||||
* {
|
||||
* Ecore_Timer *t = efl_key_data_get(o, "timer");
|
||||
* //The download was finished before the timer expired. Cancel it...
|
||||
* if (t)
|
||||
* {
|
||||
* ecore_timer_del(t);
|
||||
* efl_unref(o); //Delete the object
|
||||
* } //else - In this case the future was canceled due efl_unref() in _timeout - No need to call efl_unref()
|
||||
* }
|
||||
*
|
||||
* void download_file(const char *file)
|
||||
* {
|
||||
* //This could be rewritten using eina_future_race()
|
||||
* Eo *downloader = efl_add(MY_DOWNLOADER_CLASS, NULL);
|
||||
* Eina_Future *f = downloader_download_file(downloader, file);
|
||||
* timer = ecore_timer_add(30, _timeout, downloader);
|
||||
* //Usually this would be done with an eina_future_race() of the download promise and a timeout promise,
|
||||
* //however we provide the following example to illustrate efl_key_data_set() usage.
|
||||
* efl_key_data_set(downloader, "timer", timer);
|
||||
* eina_future_then_from_desc(f, efl_future_cb(.success = _file_ok, .error = _file_err, .success_type = EINA_VALUE_TYPE_STRING, .free = downloader_free));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param obj The object to create the link.
|
||||
* @param desc An Efl_Future_Cb_Desc
|
||||
* @return An Eina_Future_Desc to be used by eina_future_then(), eina_future_chain() and friends.
|
||||
* @see efl_future_chain_array()
|
||||
* @see efl_future_cb()
|
||||
* @see #Efl_Future_Cb_Desc
|
||||
* @see efl_key_data_set()
|
||||
*/
|
||||
EOAPI Eina_Future_Desc efl_future_cb_from_desc(Eo *obj, const Efl_Future_Cb_Desc desc);
|
||||
|
||||
/**
|
||||
* Syntax suger over efl_future_cb_from_desc()
|
||||
*
|
||||
* Usage:
|
||||
* @code
|
||||
* eina_future_then_from_desc(future, efl_future_cb(my_object, .succes = success, .success_type = EINA_VALUE_TYPE_INT));
|
||||
* @endcode
|
||||
*
|
||||
* @see efl_future_cb_from_desc()
|
||||
* @see efl_future_Eina_FutureXXX_then()
|
||||
*/
|
||||
#define efl_future_cb(_eo, ...) efl_future_cb_from_desc(_eo, (Efl_Future_Cb_Desc){__VA_ARGS__})
|
||||
|
||||
/**
|
||||
* Syntax sugar over eina_future_then_from_desc() and efl_future_cb().
|
||||
*
|
||||
* Usage:
|
||||
* @code
|
||||
* efl_future_Eina_FutureXXX_then(o, future, .success = success, .success_type = EINA_VALUE_TYPE_INT);
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
#define efl_future_Eina_FutureXXX_then(_eo, _future, ...) eina_future_then_from_desc(_future, efl_future_cb(_eo, ## __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* Creates an Future chain based on #Efl_Future_Cb_Desc
|
||||
*
|
||||
* This function is an wrapper around efl_future_cb_from_desc() and eina_future_then_from_desc()
|
||||
*
|
||||
* For more information about them, check their documentations.
|
||||
*
|
||||
*
|
||||
* @param obj An EO object to link against the future
|
||||
* @param prev The previous future
|
||||
* @param descs An array of Efl_Future_Cb_Desc
|
||||
* @return An Eina_Future or @c NULL on error.
|
||||
* @note If an error happens the whole future chain will be CANCELED, causing
|
||||
* desc.error to be called passing `ENOMEM` or `EINVAL` and desc.free
|
||||
* to free the @p obj if necessary.
|
||||
*
|
||||
* @see efl_future_chain()
|
||||
* @see efl_future_cb()
|
||||
* @see eina_future_then_from_desc()
|
||||
* @see #Efl_Future_Cb_Desc
|
||||
*/
|
||||
EOAPI Eina_Future *efl_future_chain_array(Eo *obj, Eina_Future *prev, const Efl_Future_Cb_Desc descs[]);
|
||||
|
||||
/**
|
||||
* Syntax suger over efl_future_chain_array()
|
||||
*
|
||||
* Usage:
|
||||
* @code
|
||||
* Eina_Future *f = efl_future_chain(my_object, prev_future, {}, {});
|
||||
* @endcode
|
||||
*
|
||||
* @see efl_future_chain_array()
|
||||
*/
|
||||
#define efl_future_chain(_eo, _prev, ...) efl_future_chain_array(_eo, _prev, (Efl_Future_Cb_Desc []){__VA_ARGS__, {NULL, NULL, NULL, NULL, NULL}})
|
||||
|
||||
/**
|
||||
* @addtogroup Eo_Debug_Information Eo's Debug information helper.
|
||||
* @{
|
||||
|
@ -1806,6 +2051,8 @@ efl_replace(Eo **storage, Eo *new_obj)
|
|||
*storage = new_obj;
|
||||
}
|
||||
|
||||
EOAPI extern const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT;
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
|
|
@ -62,3 +62,11 @@ struct @extern Eina.Rw_Slice {
|
|||
}
|
||||
|
||||
struct @extern Eina.Value.Type; [[Eina value type]]
|
||||
|
||||
struct @extern Eina.Future; [[Eina Future handles]]
|
||||
|
||||
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.]]
|
||||
|
||||
struct @extern Eina.Coro; [[Eina Coro (coroutine) handle.]]
|
||||
|
|
221
src/lib/eo/eo.c
221
src/lib/eo/eo.c
|
@ -2137,6 +2137,118 @@ _eo_table_del_cb(void *in)
|
|||
* This is used by the gdb debug helper script */
|
||||
Eo_Id_Data *_eo_gdb_main_domain = NULL;
|
||||
|
||||
typedef struct _Eo_Coro_Hook_Data
|
||||
{
|
||||
EINA_INLIST;
|
||||
const Eina_Coro *coro;
|
||||
Efl_Domain_Data *domain_data;
|
||||
Efl_Id_Domain return_domain;
|
||||
} Eo_Coro_Hook_Data;
|
||||
|
||||
static Eina_Inlist *_eo_coro_hook_data = NULL;
|
||||
static Eina_Lock _eo_coro_hook_data_lock;
|
||||
|
||||
/* Flow:
|
||||
*
|
||||
* main_exit -> coro_enter -> coro_exit -> main_enter
|
||||
*
|
||||
* main_exit: efl_domain_data_get()
|
||||
* coro_enter: efl_domain_data_adopt()
|
||||
* coro_exit: efl_domain_data_return()
|
||||
* main_enter: remove from list
|
||||
*/
|
||||
|
||||
static Eo_Coro_Hook_Data *
|
||||
_eo_coro_hook_data_find_unlocked(const Eina_Coro *coro)
|
||||
{
|
||||
Eo_Coro_Hook_Data *d;
|
||||
|
||||
EINA_INLIST_FOREACH(_eo_coro_hook_data, d)
|
||||
{
|
||||
if (d->coro == coro)
|
||||
{
|
||||
if (_eo_coro_hook_data != EINA_INLIST_GET(d))
|
||||
_eo_coro_hook_data = eina_inlist_promote(_eo_coro_hook_data,
|
||||
EINA_INLIST_GET(d));
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Eo_Coro_Hook_Data *
|
||||
_eo_coro_hook_data_find(const Eina_Coro *coro)
|
||||
{
|
||||
Eo_Coro_Hook_Data *d;
|
||||
|
||||
eina_lock_take(&_eo_coro_hook_data_lock);
|
||||
d = _eo_coro_hook_data_find_unlocked(coro);
|
||||
eina_lock_release(&_eo_coro_hook_data_lock);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_coro_hook_main_exit(void *data EINA_UNUSED, const Eina_Coro *coro)
|
||||
{
|
||||
Eo_Coro_Hook_Data *d = malloc(sizeof(Eo_Coro_Hook_Data));
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(d, EINA_FALSE);
|
||||
|
||||
d->coro = coro;
|
||||
d->domain_data = efl_domain_data_get();
|
||||
d->return_domain = EFL_ID_DOMAIN_INVALID;
|
||||
|
||||
eina_lock_take(&_eo_coro_hook_data_lock);
|
||||
_eo_coro_hook_data = eina_inlist_prepend(_eo_coro_hook_data, EINA_INLIST_GET(d));
|
||||
eina_lock_release(&_eo_coro_hook_data_lock);
|
||||
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_eo_coro_hook_main_enter(void *data EINA_UNUSED, const Eina_Coro *coro)
|
||||
{
|
||||
Eo_Coro_Hook_Data *d;
|
||||
|
||||
eina_lock_take(&_eo_coro_hook_data_lock);
|
||||
d = _eo_coro_hook_data_find_unlocked(coro);
|
||||
if (d)
|
||||
_eo_coro_hook_data = eina_inlist_remove(_eo_coro_hook_data, EINA_INLIST_GET(d));
|
||||
eina_lock_release(&_eo_coro_hook_data_lock);
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN(d); // just to print-out unexpected error.
|
||||
free(d);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_coro_hook_coro_enter(void *data EINA_UNUSED, const Eina_Coro *coro)
|
||||
{
|
||||
Eo_Coro_Hook_Data *d = _eo_coro_hook_data_find(coro);
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(d, EINA_FALSE);
|
||||
EINA_SAFETY_ON_NULL_RETURN_VAL(d->domain_data, EINA_FALSE);
|
||||
EINA_SAFETY_ON_TRUE_RETURN_VAL(d->return_domain != EFL_ID_DOMAIN_INVALID, EINA_FALSE);
|
||||
|
||||
d->return_domain = efl_domain_data_adopt(d->domain_data);
|
||||
EINA_SAFETY_ON_TRUE_RETURN_VAL(d->return_domain == EFL_ID_DOMAIN_INVALID, EINA_FALSE);
|
||||
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_eo_coro_hook_coro_exit(void *data EINA_UNUSED, const Eina_Coro *coro)
|
||||
{
|
||||
Eo_Coro_Hook_Data *d = _eo_coro_hook_data_find(coro);
|
||||
|
||||
EINA_SAFETY_ON_NULL_RETURN(d);
|
||||
EINA_SAFETY_ON_NULL_RETURN(d->domain_data);
|
||||
EINA_SAFETY_ON_TRUE_RETURN(d->return_domain == EFL_ID_DOMAIN_INVALID);
|
||||
|
||||
efl_domain_data_return(d->return_domain);
|
||||
d->domain_data = NULL;
|
||||
}
|
||||
|
||||
|
||||
EAPI Eina_Bool
|
||||
efl_object_init(void)
|
||||
{
|
||||
|
@ -2229,6 +2341,13 @@ efl_object_init(void)
|
|||
|
||||
_efl_add_fallback_init();
|
||||
|
||||
eina_lock_new(&_eo_coro_hook_data_lock);
|
||||
eina_coro_hook_add(_eo_coro_hook_coro_enter,
|
||||
_eo_coro_hook_coro_exit,
|
||||
_eo_coro_hook_main_enter,
|
||||
_eo_coro_hook_main_exit,
|
||||
NULL);
|
||||
|
||||
eina_log_timing(_eo_log_dom,
|
||||
EINA_LOG_STATE_STOP,
|
||||
EINA_LOG_STATE_INIT);
|
||||
|
@ -2252,6 +2371,13 @@ efl_object_shutdown(void)
|
|||
EINA_LOG_STATE_START,
|
||||
EINA_LOG_STATE_SHUTDOWN);
|
||||
|
||||
eina_coro_hook_del(_eo_coro_hook_coro_enter,
|
||||
_eo_coro_hook_coro_exit,
|
||||
_eo_coro_hook_main_enter,
|
||||
_eo_coro_hook_main_exit,
|
||||
NULL);
|
||||
eina_lock_free(&_eo_coro_hook_data_lock);
|
||||
|
||||
_efl_add_fallback_shutdown();
|
||||
|
||||
efl_future_shutdown();
|
||||
|
@ -3182,3 +3308,98 @@ eo_objects_iterator_new(void)
|
|||
|
||||
return (Eina_Iterator *)it;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_value_setup(const Eina_Value_Type *type EINA_UNUSED, void *mem)
|
||||
{
|
||||
Eo **tmem = mem;
|
||||
*tmem = NULL;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_value_flush(const Eina_Value_Type *type EINA_UNUSED, void *mem)
|
||||
{
|
||||
Eo **tmem = mem;
|
||||
if (*tmem)
|
||||
{
|
||||
efl_unref(*tmem);
|
||||
*tmem = NULL;
|
||||
}
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_eo_value_replace(Eo **dst, Eo * const *src)
|
||||
{
|
||||
if (*src == *dst) return;
|
||||
//ref *src first, since efl_unref(*dst) may trigger *src unref()
|
||||
efl_ref(*src);
|
||||
efl_unref(*dst);
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_value_vset(const Eina_Value_Type *type EINA_UNUSED, void *mem, va_list args)
|
||||
{
|
||||
Eo **dst = mem;
|
||||
Eo **src = va_arg(args, Eo **);
|
||||
_eo_value_replace(dst, src);
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_value_pset(const Eina_Value_Type *type EINA_UNUSED,
|
||||
void *mem, const void *ptr)
|
||||
{
|
||||
Eo **dst = mem;
|
||||
Eo * const *src = ptr;
|
||||
_eo_value_replace(dst, src);
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_value_pget(const Eina_Value_Type *type EINA_UNUSED,
|
||||
const void *mem, void *ptr)
|
||||
{
|
||||
Eo * const *src = mem;
|
||||
Eo **dst = ptr;
|
||||
*dst = *src;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_eo_value_convert_to(const Eina_Value_Type *type EINA_UNUSED, const Eina_Value_Type *convert, const void *type_mem, void *convert_mem)
|
||||
{
|
||||
Eo * const *eo = type_mem;
|
||||
|
||||
if (convert == EINA_VALUE_TYPE_STRINGSHARE ||
|
||||
convert == EINA_VALUE_TYPE_STRING)
|
||||
{
|
||||
const char *other_mem;
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "Object id: %p, class: %s, name: %s",
|
||||
*eo, efl_class_name_get(efl_class_get(*eo)),
|
||||
efl_debug_name_get(*eo));
|
||||
other_mem = buf;
|
||||
return eina_value_type_pset(convert, convert_mem, &other_mem);
|
||||
}
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
static const Eina_Value_Type _EINA_VALUE_TYPE_OBJECT = {
|
||||
.version = EINA_VALUE_TYPE_VERSION,
|
||||
.value_size = sizeof(Eo *),
|
||||
.name = "Efl_Object",
|
||||
.setup = _eo_value_setup,
|
||||
.flush = _eo_value_flush,
|
||||
.copy = NULL,
|
||||
.compare = NULL,
|
||||
.convert_to = _eo_value_convert_to,
|
||||
.convert_from = NULL,
|
||||
.vset = _eo_value_vset,
|
||||
.pset = _eo_value_pset,
|
||||
.pget = _eo_value_pget
|
||||
};
|
||||
|
||||
EOAPI const Eina_Value_Type *EINA_VALUE_TYPE_OBJECT = &_EINA_VALUE_TYPE_OBJECT;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Eo.h"
|
||||
#include "eo_ptr_indirection.h"
|
||||
#include "eo_private.h"
|
||||
#include "eina_promise_private.h"
|
||||
|
||||
#define EFL_EVENT_SPECIAL_SKIP 1
|
||||
|
||||
|
@ -45,6 +46,7 @@ typedef struct
|
|||
|
||||
Efl_Event_Callback_Frame *event_frame;
|
||||
Eo_Callback_Description **callbacks;
|
||||
Eina_Inlist *pending_futures;
|
||||
unsigned int callbacks_count;
|
||||
|
||||
unsigned short event_freeze_count;
|
||||
|
@ -79,6 +81,15 @@ typedef struct
|
|||
Eo_Generic_Data_Node_Type d_type;
|
||||
} Eo_Generic_Data_Node;
|
||||
|
||||
typedef struct _Efl_Future_Pending
|
||||
{
|
||||
EINA_INLIST;
|
||||
Eo *o;
|
||||
Eina_Future *future;
|
||||
Efl_Future_Cb_Desc desc;
|
||||
} Efl_Future_Pending;
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
EINA_INLIST;
|
||||
|
@ -972,48 +983,77 @@ struct _Eo_Callback_Description
|
|||
|
||||
static int _eo_callbacks = 0;
|
||||
static Eina_Mempool *_eo_callback_mempool = NULL;
|
||||
static int _efl_pending_futures = 0;
|
||||
static Eina_Mempool *_efl_pending_future_mempool = NULL;
|
||||
|
||||
static void
|
||||
_eo_callback_free(Eo_Callback_Description *cb)
|
||||
_mempool_data_free(Eina_Mempool **mp, int *usage, void *data)
|
||||
{
|
||||
if (!cb) return;
|
||||
eina_mempool_free(_eo_callback_mempool, cb);
|
||||
_eo_callbacks--;
|
||||
if (_eo_callbacks == 0)
|
||||
if (!data) return;
|
||||
eina_mempool_free(*mp, data);
|
||||
(*usage)--;
|
||||
if (*usage == 0)
|
||||
{
|
||||
eina_mempool_del(_eo_callback_mempool);
|
||||
_eo_callback_mempool = NULL;
|
||||
eina_mempool_del(*mp);
|
||||
*mp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Eo_Callback_Description *
|
||||
_eo_callback_new(void)
|
||||
static void *
|
||||
_mempool_data_alloc(Eina_Mempool **mp, int *usage, size_t size)
|
||||
{
|
||||
Eo_Callback_Description *cb;
|
||||
// very unlikely that the mempool isnt initted, so take all the init code
|
||||
// and move it out of l1 instruction cache space so we dont pollute the
|
||||
// l1 cache with unused code 99% of the time
|
||||
if (!_eo_callback_mempool) goto init_mempool;
|
||||
if (!*mp) goto init_mempool;
|
||||
init_mempool_back:
|
||||
|
||||
cb = eina_mempool_calloc(_eo_callback_mempool,
|
||||
sizeof(Eo_Callback_Description));
|
||||
cb = eina_mempool_calloc(*mp, size);
|
||||
if (cb)
|
||||
{
|
||||
_eo_callbacks++;
|
||||
(*usage)++;
|
||||
return cb;
|
||||
}
|
||||
if (_eo_callbacks != 0) return NULL;
|
||||
eina_mempool_del(_eo_callback_mempool);
|
||||
_eo_callback_mempool = NULL;
|
||||
if (*usage != 0) return NULL;
|
||||
eina_mempool_del(*mp);
|
||||
*mp = NULL;
|
||||
return NULL;
|
||||
init_mempool:
|
||||
_eo_callback_mempool = eina_mempool_add
|
||||
("chained_mempool", NULL, NULL, sizeof(Eo_Callback_Description), 256);
|
||||
if (!_eo_callback_mempool) return NULL;
|
||||
*mp = eina_mempool_add
|
||||
("chained_mempool", NULL, NULL, size, 256);
|
||||
if (!*mp) return NULL;
|
||||
goto init_mempool_back;
|
||||
}
|
||||
|
||||
static void
|
||||
_eo_callback_free(Eo_Callback_Description *cb)
|
||||
{
|
||||
_mempool_data_free(&_eo_callback_mempool, &_eo_callbacks, cb);
|
||||
}
|
||||
|
||||
static Eo_Callback_Description *
|
||||
_eo_callback_new(void)
|
||||
{
|
||||
return _mempool_data_alloc(&_eo_callback_mempool, &_eo_callbacks,
|
||||
sizeof(Eo_Callback_Description));
|
||||
}
|
||||
|
||||
static void
|
||||
_efl_pending_future_free(Efl_Future_Pending *pending)
|
||||
{
|
||||
_mempool_data_free(&_efl_pending_future_mempool,
|
||||
&_efl_pending_futures, pending);
|
||||
}
|
||||
|
||||
static Efl_Future_Pending *
|
||||
_efl_pending_future_new(void)
|
||||
{
|
||||
return _mempool_data_alloc(&_efl_pending_future_mempool,
|
||||
&_efl_pending_futures,
|
||||
sizeof(Efl_Future_Pending));
|
||||
}
|
||||
|
||||
#ifdef EFL_EVENT_SPECIAL_SKIP
|
||||
|
||||
#define CB_COUNT_INC(cnt) do { if ((cnt) != 0xffff) (cnt)++; } while(0)
|
||||
|
@ -1858,6 +1898,104 @@ EAPI const Eina_Value_Type *EFL_DBG_INFO_TYPE = &_EFL_DBG_INFO_TYPE;
|
|||
/* EFL_OBJECT_CLASS stuff */
|
||||
#define MY_CLASS EFL_OBJECT_CLASS
|
||||
|
||||
static void
|
||||
_efl_pending_futures_clear(Efl_Object_Data *pd)
|
||||
{
|
||||
while (pd->pending_futures)
|
||||
{
|
||||
Efl_Future_Pending *pending = EINA_INLIST_CONTAINER_GET(pd->pending_futures, Efl_Future_Pending);
|
||||
Eina_Future *future = *pending->desc.storage;
|
||||
assert(future);
|
||||
eina_future_cancel(future);
|
||||
}
|
||||
}
|
||||
|
||||
static Eina_Value
|
||||
_efl_future_cb(void *data, const Eina_Value value, const Eina_Future *dead_future)
|
||||
{
|
||||
Efl_Future_Pending *pending = data;
|
||||
Eina_Value ret = value;
|
||||
Eo *o;
|
||||
Efl_Object_Data *pd;
|
||||
|
||||
EINA_SAFETY_ON_NULL_GOTO(pending, err);
|
||||
o = pending->o;
|
||||
pd = efl_data_scope_get(o, EFL_OBJECT_CLASS);
|
||||
EINA_SAFETY_ON_NULL_GOTO(pd, err);
|
||||
|
||||
pd->pending_futures = eina_inlist_remove(pd->pending_futures,
|
||||
EINA_INLIST_GET(pending));
|
||||
efl_ref(o);
|
||||
EASY_FUTURE_DISPATCH(ret, value, dead_future, &pending->desc, o);
|
||||
efl_unref(o);
|
||||
_efl_pending_future_free(pending);
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
eina_value_setup(&ret, EINA_VALUE_TYPE_ERROR);
|
||||
eina_value_set(&ret, ENOMEM);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EOAPI Eina_Future_Desc
|
||||
efl_future_cb_from_desc(Eo *o, const Efl_Future_Cb_Desc desc)
|
||||
{
|
||||
Efl_Future_Pending *pending = NULL;
|
||||
Eina_Future **storage = NULL;
|
||||
Efl_Object_Data *pd;
|
||||
|
||||
EINA_SAFETY_ON_NULL_GOTO(o, end);
|
||||
pd = efl_data_scope_get(o, EFL_OBJECT_CLASS);
|
||||
EINA_SAFETY_ON_NULL_GOTO(pd, end);
|
||||
pending = _efl_pending_future_new();
|
||||
EINA_SAFETY_ON_NULL_GOTO(pending, end);
|
||||
memcpy(&pending->desc, &desc, sizeof(Efl_Future_Cb_Desc));
|
||||
pending->o = o;
|
||||
pending->future = NULL;
|
||||
if (!pending->desc.storage) pending->desc.storage = &pending->future;
|
||||
pd->pending_futures = eina_inlist_append(pd->pending_futures,
|
||||
EINA_INLIST_GET(pending));
|
||||
storage = pending->desc.storage;
|
||||
end:
|
||||
return (Eina_Future_Desc){ .cb = _efl_future_cb, .data = pending, .storage = storage };
|
||||
}
|
||||
|
||||
EOAPI Eina_Future *
|
||||
efl_future_chain_array(Eo *obj,
|
||||
Eina_Future *prev,
|
||||
const Efl_Future_Cb_Desc descs[])
|
||||
{
|
||||
ssize_t i = -1;
|
||||
Eina_Future *f = prev;
|
||||
|
||||
for (i = 0; descs[i].success || descs[i].error || descs[i].free || descs[i].success_type; i++)
|
||||
{
|
||||
Eina_Future_Desc eina_desc = efl_future_cb_from_desc(obj, descs[i]);
|
||||
f = eina_future_then_from_desc(f, eina_desc);
|
||||
EINA_SAFETY_ON_NULL_GOTO(f, err);
|
||||
}
|
||||
|
||||
return f;
|
||||
|
||||
err:
|
||||
/*
|
||||
There's no need to cancel the futures, since eina_future_then_from_desc()
|
||||
will cancel the whole chain in case of failure.
|
||||
All we need to do is to free the remaining descs
|
||||
*/
|
||||
for (i = i + 1; descs[i].error || descs[i].free; i++)
|
||||
{
|
||||
if (descs[i].error)
|
||||
{
|
||||
Eina_Value r = descs[i].error(obj, ENOMEM);
|
||||
if (r.type) eina_value_flush(&r);
|
||||
}
|
||||
if (descs[i].free) descs[i].free(obj, NULL);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EOLIAN static Eo *
|
||||
_efl_object_constructor(Eo *obj, Efl_Object_Data *pd EINA_UNUSED)
|
||||
{
|
||||
|
@ -1905,6 +2043,7 @@ composite_obj_back:
|
|||
if (pd->parent) goto err_parent;
|
||||
err_parent_back:
|
||||
|
||||
_efl_pending_futures_clear(pd);
|
||||
_eo_generic_data_del_all(obj, pd);
|
||||
_wref_destruct(pd);
|
||||
_eo_callback_remove_all(pd);
|
||||
|
|
|
@ -29,6 +29,7 @@ static const Efl_Test_Case etc[] = {
|
|||
{ "Ecore_Promise", ecore_test_ecore_promise },
|
||||
{ "Ecore_Job", ecore_test_ecore_job },
|
||||
{ "Ecore_Args", ecore_test_ecore_args },
|
||||
{ "Ecore_Promise2", ecore_test_ecore_promise2 },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
|
@ -18,5 +18,6 @@ void ecore_test_ecore_file(TCase *tc);
|
|||
void ecore_test_ecore_promise(TCase *tc);
|
||||
void ecore_test_ecore_job(TCase *tc);
|
||||
void ecore_test_ecore_args(TCase *tc);
|
||||
void ecore_test_ecore_promise2(TCase *tc);
|
||||
|
||||
#endif /* _ECORE_SUITE_H */
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -87,6 +87,7 @@ static const Efl_Test_Case etc[] = {
|
|||
{ "Free Queue", eina_test_freeq },
|
||||
{ "Util", eina_test_util },
|
||||
{ "Short Lived Strings", eina_test_slstr },
|
||||
{ "Coroutines", eina_test_coro },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
|
@ -74,5 +74,6 @@ void eina_test_safepointer(TCase *tc);
|
|||
void eina_test_slice(TCase *tc);
|
||||
void eina_test_freeq(TCase *tc);
|
||||
void eina_test_slstr(TCase *tc);
|
||||
void eina_test_coro(TCase *tc);
|
||||
|
||||
#endif /* EINA_SUITE_H_ */
|
||||
|
|
|
@ -0,0 +1,562 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Eina.h>
|
||||
|
||||
#include "eina_suite.h"
|
||||
|
||||
struct ctx {
|
||||
int a, b;
|
||||
};
|
||||
|
||||
#define VAL_A 1234
|
||||
#define VAL_B 4567
|
||||
#define RETVAL (void*)8901
|
||||
#define CANCELVAL (void*)0xCA
|
||||
#define COUNT 100
|
||||
|
||||
#ifdef EINA_SAFETY_CHECKS
|
||||
struct log_ctx {
|
||||
const char *msg;
|
||||
const char *fnc;
|
||||
int level;
|
||||
Eina_Bool did;
|
||||
Eina_Bool just_fmt;
|
||||
};
|
||||
|
||||
/* tests should not output on success, just uncomment this for debugging */
|
||||
//#define SHOW_LOG 1
|
||||
|
||||
static void
|
||||
_eina_test_safety_print_cb(const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line, const char *fmt, void *data, va_list args EINA_UNUSED)
|
||||
{
|
||||
struct log_ctx *ctx = data;
|
||||
va_list cp_args;
|
||||
const char *str;
|
||||
|
||||
va_copy(cp_args, args);
|
||||
str = va_arg(cp_args, const char *);
|
||||
va_end(cp_args);
|
||||
|
||||
ck_assert_int_eq(level, ctx->level);
|
||||
if (ctx->just_fmt)
|
||||
ck_assert_str_eq(fmt, ctx->msg);
|
||||
else
|
||||
{
|
||||
ck_assert_str_eq(fmt, "%s");
|
||||
ck_assert_str_eq(ctx->msg, str);
|
||||
}
|
||||
ck_assert_str_eq(ctx->fnc, fnc);
|
||||
ctx->did = EINA_TRUE;
|
||||
|
||||
#ifdef SHOW_LOG
|
||||
eina_log_print_cb_stderr(d, level, file, fnc, line, fmt, NULL, args);
|
||||
#else
|
||||
(void)d;
|
||||
(void)file;
|
||||
(void)line;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static const void *
|
||||
coro_func_noyield(void *data, Eina_Bool canceled, Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
struct ctx *ctx = data;
|
||||
|
||||
ck_assert_ptr_nonnull(ctx);
|
||||
ck_assert_int_eq(ctx->a, VAL_A);
|
||||
|
||||
if (canceled) return CANCELVAL;
|
||||
|
||||
ctx->b = VAL_B;
|
||||
|
||||
return RETVAL;
|
||||
}
|
||||
|
||||
START_TEST(coro_noyield)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
void *result = NULL;
|
||||
int i = 0;
|
||||
|
||||
eina_init();
|
||||
|
||||
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
i++;
|
||||
ck_assert_int_le(i, 1);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
ck_assert_int_eq(ctx.b, VAL_B);
|
||||
ck_assert_ptr_eq(result, RETVAL);
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static const void *
|
||||
coro_func_yield(void *data, Eina_Bool canceled, Eina_Coro *coro)
|
||||
{
|
||||
struct ctx *ctx = data;
|
||||
char buf[256] = "hi there";
|
||||
int i;
|
||||
|
||||
ck_assert_ptr_nonnull(ctx);
|
||||
ck_assert_int_eq(ctx->a, VAL_A);
|
||||
|
||||
if (canceled) return CANCELVAL;
|
||||
|
||||
ctx->b = 1;
|
||||
eina_coro_yield_or_return(coro, CANCELVAL);
|
||||
|
||||
ctx->b = 2;
|
||||
eina_coro_yield_or_return(coro, CANCELVAL);
|
||||
|
||||
for (i = 0; i < COUNT; i++) {
|
||||
ctx->b = i * 10;
|
||||
/* have some stuff on stack and write to it, so we validate
|
||||
* non-thread based solutions are really saving their stack.
|
||||
*/
|
||||
snprintf(buf, sizeof(buf), "b=%d -----------------", ctx->b);
|
||||
eina_coro_yield_or_return(coro, CANCELVAL);
|
||||
}
|
||||
|
||||
ctx->b = VAL_B;
|
||||
|
||||
return RETVAL;
|
||||
}
|
||||
|
||||
START_TEST(coro_yield)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
Eina_Bool r;
|
||||
void *result = NULL;
|
||||
int i = 0;
|
||||
|
||||
eina_init();
|
||||
|
||||
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
r = eina_coro_run(&coro, NULL, NULL);
|
||||
ck_assert_int_eq(r, EINA_TRUE);
|
||||
ck_assert_int_eq(ctx.b, 1);
|
||||
|
||||
r = eina_coro_run(&coro, NULL, NULL);
|
||||
ck_assert_int_eq(r, EINA_TRUE);
|
||||
ck_assert_int_eq(ctx.b, 2);
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
ck_assert_int_eq(ctx.b, i * 10);
|
||||
i++;
|
||||
ck_assert_int_le(i, COUNT);
|
||||
/* change caller's stack to guarantee coroutine stack is
|
||||
* being properly persisted.
|
||||
*/
|
||||
memset(alloca(10), 0xff, 10);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
ck_assert_int_eq(i, COUNT);
|
||||
|
||||
ck_assert_int_eq(ctx.b, VAL_B);
|
||||
ck_assert_ptr_eq(result, RETVAL);
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(coro_cancel)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
Eina_Bool r;
|
||||
|
||||
eina_init();
|
||||
|
||||
// cancel before it runs
|
||||
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
// cancel after single run
|
||||
coro = eina_coro_new(coro_func_yield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
r = eina_coro_run(&coro, NULL, NULL);
|
||||
ck_assert_int_eq(r, EINA_TRUE);
|
||||
ck_assert_int_eq(ctx.b, 1);
|
||||
|
||||
ck_assert_ptr_eq(eina_coro_cancel(&coro), CANCELVAL);
|
||||
ck_assert_ptr_null(coro);
|
||||
ck_assert_int_eq(ctx.b, 1); // it's yielding after setting b=1
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Eina_Bool
|
||||
_coro_hook_enter_success(void *data, const Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
int *i = data;
|
||||
(*i) += 1;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_coro_hook_exit(void *data, const Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
int *i = data;
|
||||
(*i) += 100;
|
||||
}
|
||||
|
||||
static void
|
||||
_main_hook_enter(void *data, const Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
int *i = data;
|
||||
(*i) += 10;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_main_hook_exit_success(void *data, const Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
int *i = data;
|
||||
(*i) += 1000;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
START_TEST(coro_hook)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
void *result = NULL;
|
||||
int i = 0, hooks_result = 0;
|
||||
|
||||
eina_init();
|
||||
|
||||
fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
|
||||
_coro_hook_exit,
|
||||
_main_hook_enter,
|
||||
_main_hook_exit_success,
|
||||
&hooks_result));
|
||||
|
||||
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
i++;
|
||||
ck_assert_int_le(i, 1);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
ck_assert_int_eq(ctx.b, VAL_B);
|
||||
ck_assert_ptr_eq(result, RETVAL);
|
||||
|
||||
fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
|
||||
_coro_hook_exit,
|
||||
_main_hook_enter,
|
||||
_main_hook_exit_success,
|
||||
&hooks_result));
|
||||
|
||||
ck_assert_int_eq(hooks_result, 1111);
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
#ifdef EINA_SAFETY_CHECKS
|
||||
START_TEST(coro_new_null)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
|
||||
eina_init();
|
||||
|
||||
#ifdef SHOW_LOG
|
||||
fprintf(stderr, "you should have a safety check failure below:\n");
|
||||
#endif
|
||||
struct log_ctx lctx;
|
||||
|
||||
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
||||
lctx.msg = _msg; \
|
||||
lctx.fnc = fn; \
|
||||
lctx.just_fmt = EINA_FALSE; \
|
||||
lctx.level = EINA_LOG_LEVEL_ERR; \
|
||||
lctx.did = EINA_FALSE
|
||||
|
||||
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
||||
|
||||
TEST_MAGIC_SAFETY("eina_coro_new", "safety check failed: func == NULL");
|
||||
|
||||
coro = eina_coro_new(NULL, NULL, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
fail_unless(lctx.did);
|
||||
|
||||
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
||||
#undef TEST_MAGIC_SAFETY
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(coro_yield_incorrect)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
void *result = NULL;
|
||||
int i = 0;
|
||||
|
||||
eina_init();
|
||||
|
||||
#ifdef SHOW_LOG
|
||||
fprintf(stderr, "you should have a safety check failure below:\n");
|
||||
#endif
|
||||
struct log_ctx lctx;
|
||||
|
||||
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
||||
lctx.msg = _msg; \
|
||||
lctx.fnc = fn; \
|
||||
lctx.just_fmt = EINA_TRUE; \
|
||||
lctx.level = EINA_LOG_LEVEL_CRITICAL; \
|
||||
lctx.did = EINA_FALSE
|
||||
|
||||
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
||||
|
||||
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
TEST_MAGIC_SAFETY("eina_coro_yield", "must be called from coroutine! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
||||
fail_if(eina_coro_yield(coro));
|
||||
fail_unless(lctx.did);
|
||||
|
||||
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
||||
#undef TEST_MAGIC_SAFETY
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
i++;
|
||||
ck_assert_int_le(i, 1);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
ck_assert_int_eq(ctx.b, VAL_B);
|
||||
ck_assert_ptr_eq(result, RETVAL);
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static const void *
|
||||
coro_func_run_incorrect(void *data, Eina_Bool canceled, Eina_Coro *coro)
|
||||
{
|
||||
#ifdef SHOW_LOG
|
||||
fprintf(stderr, "you should have a safety check failure below:\n");
|
||||
#endif
|
||||
struct log_ctx lctx;
|
||||
|
||||
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
||||
lctx.msg = _msg; \
|
||||
lctx.fnc = fn; \
|
||||
lctx.just_fmt = EINA_TRUE; \
|
||||
lctx.level = EINA_LOG_LEVEL_CRITICAL; \
|
||||
lctx.did = EINA_FALSE
|
||||
|
||||
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
||||
|
||||
TEST_MAGIC_SAFETY("eina_coro_run", "must be called from main thread! coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
||||
fail_if(eina_coro_run(&coro, NULL, NULL));
|
||||
fail_unless(lctx.did);
|
||||
|
||||
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
||||
#undef TEST_MAGIC_SAFETY
|
||||
|
||||
return coro_func_noyield(data, canceled, coro);
|
||||
}
|
||||
|
||||
START_TEST(coro_run_incorrect)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
void *result = NULL;
|
||||
int i = 0;
|
||||
|
||||
eina_init();
|
||||
|
||||
coro = eina_coro_new(coro_func_run_incorrect, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
i++;
|
||||
ck_assert_int_le(i, 1);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
ck_assert_int_eq(ctx.b, VAL_B);
|
||||
ck_assert_ptr_eq(result, RETVAL);
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Eina_Bool
|
||||
_coro_hook_enter_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
int *i = data;
|
||||
(*i) += 2;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_main_hook_exit_failed(void *data, const Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
int *i = data;
|
||||
(*i) += 2000;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
START_TEST(coro_hook_failed)
|
||||
{
|
||||
Eina_Coro *coro;
|
||||
struct ctx ctx = {
|
||||
.a = VAL_A,
|
||||
.b = 0,
|
||||
};
|
||||
void *result = NULL;
|
||||
int i = 0, hooks_result = 0;
|
||||
|
||||
eina_init();
|
||||
|
||||
#ifdef SHOW_LOG
|
||||
fprintf(stderr, "you should have a safety check failure below:\n");
|
||||
#endif
|
||||
struct log_ctx lctx;
|
||||
|
||||
#define TEST_MAGIC_SAFETY(fn, _msg) \
|
||||
lctx.msg = _msg; \
|
||||
lctx.fnc = fn; \
|
||||
lctx.just_fmt = EINA_TRUE; \
|
||||
lctx.level = EINA_LOG_LEVEL_ERR; \
|
||||
lctx.did = EINA_FALSE
|
||||
|
||||
eina_log_print_cb_set(_eina_test_safety_print_cb, &lctx);
|
||||
|
||||
fail_unless(eina_coro_hook_add(_coro_hook_enter_failed,
|
||||
_coro_hook_exit,
|
||||
_main_hook_enter,
|
||||
_main_hook_exit_success,
|
||||
&hooks_result));
|
||||
|
||||
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
TEST_MAGIC_SAFETY("_eina_coro_hooks_coro_enter", "failed hook enter=%p data=%p for coroutine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
i++;
|
||||
ck_assert_int_le(i, 1);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
ck_assert_int_eq(ctx.b, 0);
|
||||
ck_assert_ptr_eq(result, CANCELVAL);
|
||||
|
||||
fail_unless(lctx.did);
|
||||
|
||||
fail_unless(eina_coro_hook_del(_coro_hook_enter_failed,
|
||||
_coro_hook_exit,
|
||||
_main_hook_enter,
|
||||
_main_hook_exit_success,
|
||||
&hooks_result));
|
||||
|
||||
ck_assert_int_eq(hooks_result, 1112);
|
||||
|
||||
// now fail main exit
|
||||
hooks_result = 0;
|
||||
|
||||
fail_unless(eina_coro_hook_add(_coro_hook_enter_success,
|
||||
_coro_hook_exit,
|
||||
_main_hook_enter,
|
||||
_main_hook_exit_failed,
|
||||
&hooks_result));
|
||||
|
||||
coro = eina_coro_new(coro_func_noyield, &ctx, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
ck_assert_ptr_nonnull(coro);
|
||||
|
||||
TEST_MAGIC_SAFETY("_eina_coro_hooks_main_exit", "failed hook exit=%p data=%p for main routine coro=%p {func=%p data=%p turn=%s threads={%p%c %p%c} awaiting=%p}");
|
||||
|
||||
while (eina_coro_run(&coro, &result, NULL))
|
||||
{
|
||||
i++;
|
||||
ck_assert_int_le(i, 1);
|
||||
}
|
||||
ck_assert_ptr_null(coro);
|
||||
|
||||
ck_assert_int_eq(ctx.b, 0);
|
||||
ck_assert_ptr_eq(result, CANCELVAL);
|
||||
|
||||
fail_unless(lctx.did);
|
||||
|
||||
fail_unless(eina_coro_hook_del(_coro_hook_enter_success,
|
||||
_coro_hook_exit,
|
||||
_main_hook_enter,
|
||||
_main_hook_exit_failed,
|
||||
&hooks_result));
|
||||
|
||||
ck_assert_int_eq(hooks_result, 2111);
|
||||
|
||||
eina_log_print_cb_set(eina_log_print_cb_stderr, NULL);
|
||||
#undef TEST_MAGIC_SAFETY
|
||||
|
||||
eina_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
#endif
|
||||
|
||||
void
|
||||
eina_test_coro(TCase *tc)
|
||||
{
|
||||
tcase_add_test(tc, coro_noyield);
|
||||
tcase_add_test(tc, coro_yield);
|
||||
// coro_await is tested in ecore_suite, so it hooks into the main loop
|
||||
tcase_add_test(tc, coro_cancel);
|
||||
tcase_add_test(tc, coro_hook);
|
||||
|
||||
#ifdef EINA_SAFETY_CHECKS
|
||||
tcase_add_test(tc, coro_new_null);
|
||||
tcase_add_test(tc, coro_yield_incorrect);
|
||||
tcase_add_test(tc, coro_run_incorrect);
|
||||
tcase_add_test(tc, coro_hook_failed);
|
||||
#endif
|
||||
}
|
|
@ -12,6 +12,7 @@ static const Efl_Test_Case etc[] = {
|
|||
{ "Eo class behaviour errors", eo_test_class_behaviour_errors },
|
||||
{ "Eo call errors", eo_test_call_errors },
|
||||
{ "Eo eina value", eo_test_value },
|
||||
{ "Eo eina coroutines", eo_test_coro },
|
||||
{ "Eo threaded eo calls", eo_test_threaded_calls },
|
||||
{ "Eo event calls", eo_test_event},
|
||||
{ NULL, NULL }
|
||||
|
|
|
@ -9,6 +9,7 @@ void eo_test_class_errors(TCase *tc);
|
|||
void eo_test_class_behaviour_errors(TCase *tc);
|
||||
void eo_test_call_errors(TCase *tc);
|
||||
void eo_test_value(TCase *tc);
|
||||
void eo_test_coro(TCase *tc);
|
||||
void eo_test_threaded_calls(TCase *tc);
|
||||
void eo_test_event(TCase *tc);
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <Eo.h>
|
||||
|
||||
#include "eo_suite.h"
|
||||
#include "eo_test_class_simple.h"
|
||||
|
||||
#define RETVAL ((void *)0x1234)
|
||||
|
||||
static const void *
|
||||
func(void *data, Eina_Bool canceled EINA_UNUSED, Eina_Coro *coro EINA_UNUSED)
|
||||
{
|
||||
Eo *obj = data;
|
||||
|
||||
fail_unless(efl_ref(obj)); // if efl_domain_data is wrong, this fails...
|
||||
|
||||
return RETVAL;
|
||||
}
|
||||
|
||||
START_TEST(eo_coro)
|
||||
{
|
||||
Eo *obj;
|
||||
Eina_Coro *coro;
|
||||
void *result;
|
||||
|
||||
efl_object_init();
|
||||
|
||||
obj = efl_add(SIMPLE_CLASS, NULL);
|
||||
fail_unless(obj);
|
||||
|
||||
coro = eina_coro_new(func, obj, EINA_CORO_STACK_SIZE_DEFAULT);
|
||||
fail_unless(coro);
|
||||
|
||||
fail_if(eina_coro_run(&coro, &result, NULL)); // doesn't yield, so should finish
|
||||
ck_assert_ptr_eq(result, RETVAL);
|
||||
|
||||
ck_assert_int_eq(efl_ref_get(obj), 2);
|
||||
efl_unref(obj);
|
||||
efl_unref(obj);
|
||||
|
||||
efl_object_shutdown();
|
||||
}
|
||||
END_TEST
|
||||
|
||||
void eo_test_coro(TCase *tc)
|
||||
{
|
||||
tcase_add_test(tc, eo_coro);
|
||||
}
|
Loading…
Reference in New Issue