From 236c13df34576c51473463d9f0ef5247810e89e3 Mon Sep 17 00:00:00 2001 From: Felipe Magno de Almeida Date: Sun, 17 Apr 2016 15:47:33 -0300 Subject: [PATCH] eina: Add progress notify callback feature for Promise Owners Add a way for users of the promise owner to get notified when a promise progress is registered. Also added a convenience composition function that creates a promise which is fulfilled when another promise has a progress notification. --- src/lib/eina/eina_main.c | 3 ++ src/lib/eina/eina_promise.c | 101 +++++++++++++++++++++++++++++++++++++ src/lib/eina/eina_promise.h | 44 ++++++++++++++++ src/tests/eina/eina_test_promise.c | 87 ++++++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+) diff --git a/src/lib/eina/eina_main.c b/src/lib/eina/eina_main.c index 8c084db..ffa9c98 100644 --- a/src/lib/eina/eina_main.c +++ b/src/lib/eina/eina_main.c @@ -87,6 +87,7 @@ static int _eina_main_count = 0; static int _eina_main_thread_count = 0; #endif static int _eina_log_dom = -1; +void _eina_promise_init(void); #ifdef ERR #undef ERR @@ -299,6 +300,8 @@ eina_init(void) } } + _eina_promise_init(); + eina_cpu_count_internal(); eina_log_timing(_eina_log_dom, EINA_LOG_STATE_STOP, EINA_LOG_STATE_INIT); diff --git a/src/lib/eina/eina_promise.c b/src/lib/eina/eina_promise.c index 459f686..2f83dc4 100644 --- a/src/lib/eina/eina_promise.c +++ b/src/lib/eina/eina_promise.c @@ -9,6 +9,7 @@ typedef struct _Eina_Promise_Then_Cb _Eina_Promise_Then_Cb; typedef struct _Eina_Promise_Progress_Cb _Eina_Promise_Progress_Cb; typedef struct _Eina_Promise_Cancel_Cb _Eina_Promise_Cancel_Cb; +typedef struct _Eina_Promise_Owner_Progress_Notify_Data _Eina_Promise_Owner_Progress_Notify_Data; typedef struct _Eina_Promise_Default _Eina_Promise_Default; typedef struct _Eina_Promise_Default_Owner _Eina_Promise_Default_Owner; typedef struct _Eina_Promise_Iterator _Eina_Promise_Iterator; @@ -40,6 +41,15 @@ struct _Eina_Promise_Cancel_Cb void* data; }; +struct _Eina_Promise_Owner_Progress_Notify_Data +{ + EINA_INLIST; + + Eina_Promise_Progress_Notify_Cb callback; + Eina_Promise_Free_Cb free_cb; + void* data; +}; + struct _Eina_Promise_Default { Eina_Promise vtable; @@ -49,6 +59,7 @@ struct _Eina_Promise_Default Eina_Inlist *then_callbacks; Eina_Inlist *progress_callbacks; Eina_Inlist *cancel_callbacks; + Eina_Inlist *progress_notify_callbacks; Eina_Promise_Free_Cb value_free_cb; int ref; @@ -89,6 +100,21 @@ static void _eina_promise_unref(_Eina_Promise_Default* promise); static void _eina_promise_iterator_setup(_Eina_Promise_Iterator* iterator, Eina_Array* promises); +static void _eina_promise_free_callback_list(Eina_Inlist** list, void(*free_cb)(void* node)) +{ + struct node + { + EINA_INLIST; + } *node; + Eina_Inlist *list2; + + EINA_INLIST_FOREACH_SAFE(*list, list2, node) + { + free_cb(node); + } + *list = NULL; +} + static void _eina_promise_then_calls(_Eina_Promise_Default_Owner* promise) { @@ -188,6 +214,7 @@ _eina_promise_then(_Eina_Promise_Default* p, Eina_Promise_Cb callback, { _Eina_Promise_Default_Owner* promise; _Eina_Promise_Then_Cb* cb; + _Eina_Promise_Owner_Progress_Notify_Data* notify_data; promise = EINA_PROMISE_GET_OWNER(p); @@ -198,6 +225,12 @@ _eina_promise_then(_Eina_Promise_Default* p, Eina_Promise_Cb callback, cb->data = data; promise->promise.then_callbacks = eina_inlist_append(promise->promise.then_callbacks, EINA_INLIST_GET(cb)); + EINA_INLIST_FOREACH(promise->promise.progress_notify_callbacks, notify_data) + { + (*notify_data->callback)(notify_data->data, &promise->owner_vtable); + } + _eina_promise_free_callback_list(&promise->promise.progress_notify_callbacks, &free); + if (!promise->promise.is_first_then) { _eina_promise_ref(p); @@ -263,11 +296,19 @@ static void _eina_promise_progress_cb_add(_Eina_Promise_Default* promise, Eina_Promise_Progress_Cb callback, void* data) { _Eina_Promise_Progress_Cb* cb; + _Eina_Promise_Owner_Progress_Notify_Data* notify_data; + _Eina_Promise_Default_Owner* owner = EINA_PROMISE_GET_OWNER(promise); cb = malloc(sizeof(struct _Eina_Promise_Progress_Cb)); cb->callback = callback; cb->data = data; promise->progress_callbacks = eina_inlist_append(promise->progress_callbacks, EINA_INLIST_GET(cb)); + + EINA_INLIST_FOREACH(owner->promise.progress_notify_callbacks, notify_data) + { + (*notify_data->callback)(notify_data->data, &owner->owner_vtable); + } + _eina_promise_free_callback_list(&owner->promise.progress_notify_callbacks, &free); } static void @@ -356,6 +397,20 @@ _eina_promise_owner_progress(_Eina_Promise_Default_Owner* promise, void* data) } } +static void +_eina_promise_owner_progress_notify(_Eina_Promise_Default_Owner* promise, Eina_Promise_Progress_Notify_Cb notify, + void* data, Eina_Promise_Free_Cb free_cb) +{ + _Eina_Promise_Owner_Progress_Notify_Data* cb + = malloc(sizeof(struct _Eina_Promise_Owner_Progress_Notify_Data)); + + cb->callback = notify; + cb->free_cb = free_cb; + cb->data = data; + promise->promise.progress_notify_callbacks = + eina_inlist_append(promise->promise.progress_notify_callbacks, EINA_INLIST_GET(cb)); +} + Eina_Promise_Owner * eina_promise_default_add(int value_size) { @@ -378,6 +433,7 @@ eina_promise_default_add(int value_size) p->promise.ref = 1; memset(&p->promise.then_callbacks, 0, sizeof(p->promise.then_callbacks)); memset(&p->promise.progress_callbacks, 0, sizeof(p->promise.progress_callbacks)); + memset(&p->promise.progress_notify_callbacks, 0, sizeof(p->promise.progress_notify_callbacks)); memset(&p->promise.cancel_callbacks, 0, sizeof(p->promise.cancel_callbacks)); p->promise.value_size = value_size; p->promise.value_free_cb = NULL; @@ -392,6 +448,7 @@ eina_promise_default_add(int value_size) p->owner_vtable.pending_is = EINA_FUNC_PROMISE_OWNER_PENDING_IS(_eina_promise_owner_pending_is); p->owner_vtable.cancelled_is = EINA_FUNC_PROMISE_OWNER_CANCELLED_IS(_eina_promise_owner_cancelled_is); p->owner_vtable.progress = EINA_FUNC_PROMISE_OWNER_PROGRESS(_eina_promise_owner_progress); + p->owner_vtable.progress_notify = EINA_FUNC_PROMISE_OWNER_PROGRESS_NOTIFY(_eina_promise_owner_progress_notify); return &p->owner_vtable; } @@ -540,6 +597,36 @@ _eina_promise_iterator_setup(_Eina_Promise_Iterator* it, Eina_Array* promises_ar it->data.success_iterator_impl.free = FUNC_ITERATOR_FREE(_eina_promise_iterator_free); } +static void +_eina_promise_progress_notify_fulfilled(void* data, Eina_Promise_Owner* p EINA_UNUSED) +{ + Eina_Promise_Owner* owner = data; + eina_promise_owner_value_set(owner, NULL, NULL); +} + +EAPI Eina_Error EINA_ERROR_PROMISE_NO_NOTIFY; + +static void +_eina_promise_progress_notify_failed(void* data) +{ + Eina_Promise_Owner* owner = data; + if(eina_promise_owner_pending_is(owner)) + eina_promise_owner_error_set(owner, EINA_ERROR_PROMISE_NO_NOTIFY); +} + +EAPI Eina_Promise* +eina_promise_progress_notification(Eina_Promise_Owner* promise) +{ + Eina_Promise_Owner* owner; + + owner = eina_promise_default_add(0); + + eina_promise_owner_progress_notify(promise, &_eina_promise_progress_notify_fulfilled, owner, + &_eina_promise_progress_notify_failed); + + return eina_promise_owner_promise_get(owner); +} + // API functions EAPI void eina_promise_then(Eina_Promise* promise, Eina_Promise_Cb callback, @@ -643,3 +730,17 @@ eina_promise_owner_progress(Eina_Promise_Owner const* promise, void* progress) { promise->progress(promise, progress); } + +EAPI void +eina_promise_owner_progress_notify(Eina_Promise_Owner* promise, Eina_Promise_Progress_Notify_Cb progress_cb, + void* data, Eina_Promise_Free_Cb free_cb) +{ + promise->progress_notify(promise, progress_cb, data, free_cb); +} + +static const char EINA_ERROR_PROMISE_NO_NOTIFY_STR[] = "Out of memory"; + +void _eina_promise_init() +{ + EINA_ERROR_PROMISE_NO_NOTIFY = eina_error_msg_static_register(EINA_ERROR_PROMISE_NO_NOTIFY_STR); +} diff --git a/src/lib/eina/eina_promise.h b/src/lib/eina/eina_promise.h index 9093b92..75508be 100644 --- a/src/lib/eina/eina_promise.h +++ b/src/lib/eina/eina_promise.h @@ -20,6 +20,11 @@ typedef struct _Eina_Promise_Owner Eina_Promise_Owner; typedef void(*Eina_Promise_Free_Cb)(void* value); /* + * @brief Callback type for Promise_Owner to get notified of when someone registered a progress and/or then callback + */ +typedef void(*Eina_Promise_Progress_Notify_Cb)(void* data, Eina_Promise_Owner* promise); + +/* * @brief Function callback type for when using eina_promise_then */ typedef void(*Eina_Promise_Cb)(void* data, void* value); @@ -162,6 +167,14 @@ typedef Eina_Bool(*Eina_Promise_Owner_Progress_Cb)(Eina_Promise_Owner const* pro #define EINA_FUNC_PROMISE_OWNER_PROGRESS(Function) ((Eina_Promise_Owner_Progress_Cb)Function) +/* + * @brief Function callback type for promise owner's progress notify registration function override + */ +typedef Eina_Bool(*Eina_Promise_Owner_Progress_Notify_Cb)(Eina_Promise_Owner* promise, + Eina_Promise_Progress_Notify_Cb progress_cb, void* data, Eina_Promise_Free_Cb free_cb); + +#define EINA_FUNC_PROMISE_OWNER_PROGRESS_NOTIFY(Function) ((Eina_Promise_Owner_Progress_Notify_Cb)Function) + #define EINA_PROMISE_VERSION 1 @@ -193,6 +206,7 @@ struct _Eina_Promise_Owner Eina_Promise_Owner_Pending_Is_Cb pending_is; Eina_Promise_Owner_Cancelled_Is_Cb cancelled_is; Eina_Promise_Owner_Progress_Cb progress; + Eina_Promise_Owner_Progress_Notify_Cb progress_notify; #define EINA_MAGIC_PROMISE_OWNER 0x07932A5C EINA_MAGIC; }; @@ -217,6 +231,15 @@ EAPI void eina_promise_then(Eina_Promise* promise, Eina_Promise_Cb callback, EAPI Eina_Promise* eina_promise_all(Eina_Iterator* promises); /* + * @brief Creates a new @Eina_Promise from another @Eina_Promise_Owner which + * is fulfilled when @promise has a progress callback registered + * + * @param promise Promise Owner which to be waited for a progress callback register + * @return Returns a new Eina_Promise + */ +EAPI Eina_Promise* eina_promise_progress_notification(Eina_Promise_Owner* promise); + +/* * @brief Sets value for Eina_Promise_Owner * * This finishes the Promise and calls all eina_promise_then callbacks @@ -379,6 +402,20 @@ EAPI Eina_Bool eina_promise_owner_cancelled_is(Eina_Promise_Owner const* promise EAPI void eina_promise_owner_progress(Eina_Promise_Owner const* promise, void* progress); /* + * @brief Registers a progress notify callbacks in promise owner. + * + * Registers a callback to be called for when a progress callback is + * registered by the linked @Eina_Promise. + * + * @param promise The promise for which to get the cancelled status + * @param notify_cb The callback to be called + * @param data The data to be passed to progress notify callback + * @param free_cb The free function that is called for the data param + */ +EAPI void eina_promise_owner_progress_notify(Eina_Promise_Owner* promise, + Eina_Promise_Progress_Notify_Cb notify_cb, void* data, Eina_Promise_Free_Cb free_cb); + +/* * @brief Decrement the reference count for the Eina_Promise. * The Eina_Promise, if its reference count drops to zero and is not @@ -438,6 +475,13 @@ EAPI void eina_promise_owner_default_manual_then_set(Eina_Promise_Owner* promise */ EAPI void eina_promise_owner_default_call_then(Eina_Promise_Owner* promise); +/** + * @var EINA_ERROR_PROMISE_NO_NOTIFY + * + * @brief The error identifier corresponding to when a promise was + * free'd before any progress callback was registered + */ +EAPI extern Eina_Error EINA_ERROR_PROMISE_NO_NOTIFY; /* * @internal diff --git a/src/tests/eina/eina_test_promise.c b/src/tests/eina/eina_test_promise.c index 59ed7d7..f21f5ff 100644 --- a/src/tests/eina/eina_test_promise.c +++ b/src/tests/eina/eina_test_promise.c @@ -236,6 +236,90 @@ START_TEST(eina_test_promise_progress) } END_TEST +static void progress_notify(void* data, Eina_Promise_Owner* promise EINA_UNUSED) +{ + ck_assert(!*(Eina_Bool*)data); + *(Eina_Bool*)data = EINA_TRUE; +} + +START_TEST(eina_test_promise_progress_notify1) +{ + Eina_Bool progress_notify_ran = EINA_FALSE; + Eina_Promise_Owner* owner; + Eina_Promise* promise; + + eina_init(); + + owner = eina_promise_default_add(0); + eina_promise_owner_progress_notify(owner, &progress_notify, &progress_notify_ran, NULL); + + promise = eina_promise_owner_promise_get(owner); + eina_promise_progress_cb_add(promise, &progress_callback, NULL); // never run + eina_promise_progress_cb_add(promise, &progress_callback, NULL); // never run + + ck_assert(progress_notify_ran); + + eina_shutdown(); +} +END_TEST + +START_TEST(eina_test_promise_progress_notify2) +{ + Eina_Bool progress_notify_ran = EINA_FALSE; + Eina_Promise_Owner* owner; + Eina_Promise* promise; + + eina_init(); + + owner = eina_promise_default_add(0); + eina_promise_owner_progress_notify(owner, &progress_notify, &progress_notify_ran, NULL); + + promise = eina_promise_owner_promise_get(owner); + eina_promise_then(promise, NULL, &_cancel_promise_callback, NULL); // never run + eina_promise_then(promise, NULL, &_cancel_promise_callback, NULL); // never run + + ck_assert(progress_notify_ran); + + eina_shutdown(); +} +END_TEST + +static void +_eina_promise_progress_notify_fulfilled(void* data, void* value EINA_UNUSED) +{ + *(Eina_Bool*)data = EINA_TRUE; +} + +static void +_eina_promise_progress_notify_error(void* data EINA_UNUSED, Eina_Error const* error EINA_UNUSED) +{ + ck_assert(EINA_FALSE); +} + +START_TEST(eina_test_promise_progress_notify3) +{ + Eina_Bool progress_notify_ran = EINA_FALSE; + Eina_Promise_Owner* owner; + Eina_Promise* promise; + Eina_Promise* promise_progress; + + eina_init(); + + owner = eina_promise_default_add(0); + promise_progress = eina_promise_progress_notification(owner); + eina_promise_then(promise_progress, &_eina_promise_progress_notify_fulfilled, + &_eina_promise_progress_notify_error, &progress_notify_ran); + + promise = eina_promise_owner_promise_get(owner); + eina_promise_progress_cb_add(promise, &progress_callback, NULL); // never run + eina_promise_progress_cb_add(promise, &progress_callback, NULL); // never run + + ck_assert(progress_notify_ran); + + eina_shutdown(); +} +END_TEST + void eina_test_promise(TCase *tc) { @@ -246,4 +330,7 @@ eina_test_promise(TCase *tc) tcase_add_test(tc, eina_test_promise_values_all); tcase_add_test(tc, eina_test_promise_cancel_promise); tcase_add_test(tc, eina_test_promise_progress); + tcase_add_test(tc, eina_test_promise_progress_notify1); + tcase_add_test(tc, eina_test_promise_progress_notify2); + tcase_add_test(tc, eina_test_promise_progress_notify3); } -- 2.7.4