summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCedric BAIL <cedric@osg.samsung.com>2016-06-30 16:51:06 -0700
committerCedric BAIL <cedric@osg.samsung.com>2016-09-08 14:51:56 -0700
commitb9f679f23c24f5a6ace2491c685f82e1317c15c4 (patch)
treea3ea2e7a97202b5a6a8e06e2daf80d74e04a2251
parent99f5f710796db507053e6b1903b26a9149585ce6 (diff)
downloadefl-b9f679f23c24f5a6ace2491c685f82e1317c15c4.tar.gz
ecore: add Efl.Promise.
-rw-r--r--src/Makefile_Ecore.am2
-rw-r--r--src/lib/ecore/Ecore_Eo.h4
-rw-r--r--src/lib/ecore/efl_promise.c542
-rw-r--r--src/lib/ecore/efl_promise.eo44
4 files changed, 591 insertions, 1 deletions
diff --git a/src/Makefile_Ecore.am b/src/Makefile_Ecore.am
index 303a648b38..4a1d6a6795 100644
--- a/src/Makefile_Ecore.am
+++ b/src/Makefile_Ecore.am
@@ -20,6 +20,7 @@ ecore_eolian_files = \
lib/ecore/efl_io_stderr.eo \
lib/ecore/efl_io_file.eo \
lib/ecore/efl_io_copier.eo \
+ lib/ecore/efl_promise.eo \
lib/ecore/ecore_parent.eo \
$(ecore_eolian_files_legacy)
@@ -79,6 +80,7 @@ lib/ecore/efl_io_stdout.c \
lib/ecore/efl_io_stderr.c \
lib/ecore/efl_io_file.c \
lib/ecore/efl_io_copier.c \
+lib/ecore/efl_promise.c \
lib/ecore/ecore_pipe.c \
lib/ecore/ecore_poller.c \
lib/ecore/ecore_time.c \
diff --git a/src/lib/ecore/Ecore_Eo.h b/src/lib/ecore/Ecore_Eo.h
index feb66dc722..7e10c51949 100644
--- a/src/lib/ecore/Ecore_Eo.h
+++ b/src/lib/ecore/Ecore_Eo.h
@@ -54,7 +54,9 @@ extern "C" {
#include "efl_loop_fd.eo.h"
-/* We ue the factory pattern here, so you shouldn't call efl_add directly. */
+#include "efl_promise.eo.h"
+
+/* We ue the factory pattern here, so you shouldn't call eo_add directly. */
EAPI Eo *ecore_main_loop_get(void);
/**
diff --git a/src/lib/ecore/efl_promise.c b/src/lib/ecore/efl_promise.c
new file mode 100644
index 0000000000..5a03cee90d
--- /dev/null
+++ b/src/lib/ecore/efl_promise.c
@@ -0,0 +1,542 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <Ecore.h>
+
+#include "ecore_private.h"
+
+// FIXME: handle self destruction when back in the main loop
+
+typedef struct _Efl_Promise_Data Efl_Promise_Data;
+typedef struct _Efl_Promise_Msg Efl_Promise_Msg;
+
+struct _Efl_Promise_Msg
+{
+ EINA_REFCOUNT;
+
+ void *value;
+ Eina_Free_Cb free_cb;
+
+ Eina_Error error;
+};
+
+struct _Efl_Promise_Data
+{
+ Efl_Promise *promise;
+ Efl_Promise_Msg *message;
+ Eina_List *futures;
+
+ struct {
+ Eina_Bool future : 1;
+ Eina_Bool future_triggered : 1;
+ Eina_Bool progress : 1;
+ Eina_Bool progress_triggered : 1;
+ } set;
+};
+
+static void
+_efl_promise_msg_free(Efl_Promise_Msg *msg)
+{
+ if (msg->free_cb)
+ msg->free_cb(msg->value);
+ free(msg);
+}
+
+#define EFL_LOOP_FUTURE_CLASS efl_loop_future_class_get()
+static const Efl_Class *efl_loop_future_class_get(void);
+
+typedef struct _Efl_Loop_Future_Data Efl_Loop_Future_Data;
+typedef struct _Efl_Loop_Future_Callback Efl_Loop_Future_Callback;
+
+struct _Efl_Loop_Future_Callback
+{
+ EINA_INLIST;
+
+ Efl_Event_Cb success;
+ Efl_Event_Cb failure;
+ Efl_Event_Cb progress;
+
+ Efl_Promise *next;
+
+ const void *data;
+};
+
+struct _Efl_Loop_Future_Data
+{
+ Eina_Inlist *callbacks;
+
+ Efl_Future *self;
+ Efl_Promise_Msg *message;
+ Efl_Promise_Data *promise;
+
+#ifndef NDEBUG
+ int wref;
+#endif
+
+ Eina_Bool fulfilled : 1;
+ Eina_Bool death : 1;
+ Eina_Bool delayed : 1;
+};
+
+static void
+_efl_loop_future_success(Efl_Event *ev, Efl_Loop_Future_Data *pd, void *value)
+{
+ Efl_Loop_Future_Callback *cb;
+ Efl_Future_Event_Success chain_success;
+
+ ev->info = &chain_success;
+ ev->desc = EFL_FUTURE_EVENT_SUCCESS;
+
+ chain_success.value = value;
+
+ EINA_INLIST_FREE(pd->callbacks, cb)
+ {
+ if (cb->next)
+ {
+ chain_success.next = cb->next;
+
+ cb->success((void*) cb->data, ev);
+ }
+
+ pd->callbacks = eina_inlist_remove(pd->callbacks, pd->callbacks);
+ free(cb);
+ }
+}
+
+static void
+_efl_loop_future_failure(Efl_Event *ev, Efl_Loop_Future_Data *pd, Eina_Error error)
+{
+ Efl_Loop_Future_Callback *cb;
+ Efl_Future_Event_Failure chain_fail;
+
+ ev->info = &chain_fail;
+ ev->desc = EFL_FUTURE_EVENT_FAILURE;
+
+ chain_fail.error = error;
+
+ EINA_INLIST_FREE(pd->callbacks, cb)
+ {
+ if (cb->next)
+ {
+ chain_fail.next = cb->next;
+
+ cb->failure((void*) cb->data, ev);
+ }
+
+ pd->callbacks = eina_inlist_remove(pd->callbacks, pd->callbacks);
+ free(cb);
+ }
+}
+
+static void
+_efl_loop_future_propagate(Eo *obj, Efl_Loop_Future_Data *pd)
+{
+ Efl_Event ev;
+
+ ev.object = obj;
+
+ if (pd->fulfilled &&
+ !pd->message)
+ {
+ _efl_loop_future_failure(&ev, pd, EINA_ERROR_FUTURE_CANCEL);
+ }
+ else if (pd->message->error == 0)
+ {
+ _efl_loop_future_success(&ev, pd, pd->message->value);
+ }
+ else
+ {
+ _efl_loop_future_failure(&ev, pd, pd->message->error);
+ }
+ pd->fulfilled = EINA_TRUE;
+
+ if (!pd->delayed)
+ {
+ pd->delayed = EINA_TRUE;
+ efl_unref(obj);
+ }
+}
+
+static void
+_efl_loop_future_fulfilling(Eo *obj, Efl_Loop_Future_Data *pd)
+{
+ if (pd->fulfilled)
+ {
+ _efl_loop_future_propagate(obj, pd);
+ }
+
+ if (!pd->death)
+ {
+ pd->death = EINA_TRUE;
+ efl_del(obj);
+ }
+}
+
+static void
+_efl_loop_future_prepare_events(Efl_Loop_Future_Data *pd, Eina_Bool progress)
+{
+ if (!pd->promise) return ;
+
+ pd->promise->set.future = EINA_TRUE;
+ if (progress)
+ pd->promise->set.progress = EINA_TRUE;
+}
+
+static Efl_Future *
+_efl_loop_future_then(Eo *obj, Efl_Loop_Future_Data *pd,
+ Efl_Event_Cb success, Efl_Event_Cb failure, Efl_Event_Cb progress, const void *data)
+{
+ Efl_Loop_Future_Callback *cb;
+ Efl_Future *f;
+
+ cb = calloc(1, sizeof (Efl_Loop_Future_Callback));
+ if (!cb) return NULL;
+
+ cb->success = success;
+ cb->failure = failure;
+ cb->progress = progress;
+ cb->data = data;
+ cb->next = efl_add(EFL_PROMISE_CLASS, obj);
+
+ f = efl_promise_future_get(cb->next);
+
+ pd->callbacks = eina_inlist_append(pd->callbacks, EINA_INLIST_GET(cb));
+
+ _efl_loop_future_prepare_events(pd, !!progress);
+ _efl_loop_future_fulfilling(obj, pd);
+
+ return f;
+}
+
+static void
+_efl_loop_future_disconnect(Eo *obj, Efl_Loop_Future_Data *pd)
+{
+ Eo *promise;
+
+ if (!pd->promise) return ;
+ promise = efl_ref(pd->promise->promise);
+
+ // Disconnect from the promise
+ pd->promise->futures = eina_list_remove(pd->promise->futures, pd);
+
+ // Notify that there is no more future
+ if (!pd->promise->futures)
+ {
+ efl_event_callback_call(pd->promise->promise, EFL_PROMISE_EVENT_FUTURE_NONE, NULL);
+ }
+
+ // Unreference after propagating the failure
+ efl_data_xunref(pd->promise->promise, pd->promise, obj);
+ pd->promise = NULL;
+
+ efl_unref(promise);
+}
+
+static void
+_efl_loop_future_cancel(Eo *obj, Efl_Loop_Future_Data *pd)
+{
+ // Check state
+ if (pd->fulfilled)
+ {
+ ERR("Triggering cancel on an already fulfilled Efl.Future.");
+ return;
+ }
+
+#ifndef NDEBUG
+ if (!pd->wref)
+ {
+ WRN("Calling cancel should be only done on a weak reference. Look at efl_future_use.");
+ }
+#endif
+
+ pd->fulfilled = EINA_TRUE;
+
+ // Trigger failure
+ _efl_loop_future_propagate(obj, pd);
+
+ _efl_loop_future_disconnect(obj, pd);
+}
+
+static void
+_efl_loop_future_intercept(Eo *obj)
+{
+ Efl_Loop_Future_Data *pd;
+
+ // Just delay object death
+ efl_del_intercept_set(obj, NULL);
+
+ // Trigger events now
+ pd = efl_data_scope_get(obj, EFL_LOOP_FUTURE_CLASS);
+
+ if (!pd->promise) return ;
+ if (pd->promise->set.future && !pd->promise->set.future_triggered)
+ {
+ efl_event_callback_call(pd->promise->promise, EFL_PROMISE_EVENT_FUTURE_SET, obj);
+ pd->promise->set.future_triggered = EINA_TRUE;
+ }
+ if (pd->promise->set.progress && !pd->promise->set.progress_triggered)
+ {
+ efl_event_callback_call(pd->promise->promise, EFL_PROMISE_EVENT_FUTURE_PROGRESS_SET, obj);
+ pd->promise->set.progress_triggered = EINA_TRUE;
+ }
+}
+
+static Eo *
+_efl_loop_future_efl_object_constructor(Eo *obj, Efl_Loop_Future_Data *pd)
+{
+ obj = efl_constructor(efl_super(obj, EFL_LOOP_FUTURE_CLASS));
+
+ pd->self = obj;
+
+ efl_del_intercept_set(obj, _efl_loop_future_intercept);
+
+ return obj;
+}
+
+static void
+_efl_loop_future_efl_object_destructor(Eo *obj, Efl_Loop_Future_Data *pd)
+{
+ if (!pd->fulfilled)
+ {
+ ERR("Lost reference to a future without fulfilling it. Forcefully cancelling it.");
+ _efl_loop_future_propagate(obj, pd);
+ }
+#ifndef NDEBUG
+ else if (pd->callbacks)
+ {
+ ERR("Found referenced callbacks while destroying the future.");
+ _efl_loop_future_propagate(obj, pd);
+ }
+#endif
+
+ // Cleanup message if needed
+ if (pd->message)
+ {
+ EINA_REFCOUNT_UNREF(pd->message)
+ _efl_promise_msg_free(pd->message);
+ pd->message = NULL;
+ }
+
+ efl_destructor(efl_super(obj, EFL_LOOP_FUTURE_CLASS));
+
+ // Disconnect from the promise
+ _efl_loop_future_disconnect(obj, pd);
+}
+
+#ifndef NDEBUG
+static void
+_efl_future_wref_add(Eo *obj, Efl_Loop_Future_Data *pd, Eo **wref)
+{
+ efl_wref_add(efl_super(obj, EFL_LOOP_FUTURE_CLASS), wref);
+
+ pd->wref++;
+}
+
+static void
+_efl_future_wref_del(Eo *obj, Efl_Loop_Future_Data *pd, Eo **wref)
+{
+ efl_wref_del(efl_super(obj, EFL_LOOP_FUTURE_CLASS), wref);
+
+ pd->wref--;
+}
+#endif
+
+static Eina_Bool
+_efl_loop_future_class_initializer(Efl_Class *klass)
+{
+ EFL_OPS_DEFINE(ops,
+ EFL_OBJECT_OP_FUNC(efl_future_then, _efl_loop_future_then),
+ EFL_OBJECT_OP_FUNC(efl_future_cancel, _efl_loop_future_cancel),
+ EFL_OBJECT_OP_FUNC(efl_constructor, _efl_loop_future_efl_object_constructor),
+ EFL_OBJECT_OP_FUNC(efl_destructor, _efl_loop_future_efl_object_destructor));
+
+ return efl_class_functions_set(klass, &ops);
+};
+
+static const Efl_Class_Description _efl_loop_future_class_desc = {
+ EO_VERSION,
+ "Efl_Future",
+ EFL_CLASS_TYPE_REGULAR,
+ sizeof (Efl_Loop_Future_Data),
+ _efl_loop_future_class_initializer,
+ NULL,
+ NULL
+};
+
+EFL_DEFINE_CLASS(efl_loop_future_class_get, &_efl_loop_future_class_desc, EFL_FUTURE_CLASS, NULL);
+
+static Efl_Future *
+_efl_promise_future_get(Eo *obj, Efl_Promise_Data *pd EINA_UNUSED)
+{
+ Efl_Future *f;
+ Efl_Loop_Future_Data *fd;
+
+ // Build a new future, attach it and return it
+ f = efl_add(EFL_LOOP_FUTURE_CLASS, NULL);
+ if (!f) return NULL;
+
+ fd = efl_data_scope_get(f, EFL_LOOP_FUTURE_CLASS);
+
+ fd->promise = efl_data_xref(obj, EFL_PROMISE_CLASS, f);
+ fd->promise->futures = eina_list_append(fd->promise->futures, fd);
+
+ return f;
+}
+
+static Efl_Promise_Msg *
+_efl_promise_message_new(Efl_Promise_Data *pd)
+{
+ Efl_Promise_Msg *message;
+
+ message = calloc(1, sizeof (Efl_Promise_Msg));
+ if (!message) return NULL;
+
+ EINA_REFCOUNT_INIT(message);
+ pd->message = message;
+
+ return message;
+}
+
+static void
+_efl_promise_value_set(Eo *obj, Efl_Promise_Data *pd, void *v, Eina_Free_Cb free_cb)
+{
+ Efl_Promise_Msg *message;
+ Efl_Loop_Future_Data *f;
+ Eina_List *l, *ln;
+
+ if (pd->message)
+ {
+ ERR("This promise has already been fulfilled. You can can't set a value twice nor can you set a value after it has been cancelled.");
+ return ;
+ }
+
+ // By triggering this message, we are likely going to kill all future
+ // And a user of the promise may want to attach an event handler on the promise
+ // and destroy it, so delay that to after the loop is done.
+ efl_ref(obj);
+
+ // Create a refcounted structure where refcount == number of future + one
+ message = _efl_promise_message_new(pd);
+ if (!message) return ;
+
+ message->value = v;
+ message->free_cb = free_cb;
+
+ EINA_REFCOUNT_INIT(message);
+ pd->message = message;
+
+ // Send it to all futures
+ EINA_LIST_FOREACH_SAFE(pd->futures, l, ln, f)
+ {
+ EINA_REFCOUNT_REF(message);
+ f->message = message;
+
+ // Trigger the callback
+ _efl_loop_future_propagate(f->self, f);
+ }
+
+ // Now, we may die.
+ efl_unref(obj);
+}
+
+static void
+_efl_promise_failed(Eo *obj, Efl_Promise_Data *pd, Eina_Error err)
+{
+ Efl_Promise_Msg *message;
+ Efl_Loop_Future_Data *f;
+ Eina_List *l, *ln;
+
+ if (pd->message)
+ {
+ ERR("This promise has already been fulfilled. You can can't set a value twice nor can you set a value after it has been cancelled.");
+ return ;
+ }
+
+ // By triggering this message, we are likely going to kill all future
+ // And a user of the promise may want to attach an event handler on the promise
+ // and destroy it, so delay that to after the loop is done.
+ efl_ref(obj);
+
+ // Create a refcounted structure where refcount == number of future + one
+ message = _efl_promise_message_new(pd);
+ if (!message) return ;
+
+ message->error = err;
+
+ EINA_REFCOUNT_INIT(message);
+ pd->message = message;
+
+ // Send it to each future
+ EINA_LIST_FOREACH_SAFE(pd->futures, l, ln, f)
+ {
+ EINA_REFCOUNT_REF(message);
+ f->message = message;
+
+ // Trigger the callback
+ _efl_loop_future_propagate(f->self, f);
+ }
+
+ // Now, we may die.
+ efl_unref(obj);
+}
+
+static void
+_efl_promise_progress_set(Eo *obj, Efl_Promise_Data *pd, void *p)
+{
+ Efl_Loop_Future_Data *f;
+ Eina_List *l, *ln;
+ Efl_Future_Event_Progress chain_progress;
+ Efl_Event ev;
+
+ chain_progress.progress = p;
+
+ ev.object = obj;
+ ev.info = &chain_progress;
+ ev.desc = EFL_FUTURE_EVENT_PROGRESS;
+
+ EINA_LIST_FOREACH_SAFE(pd->futures, l, ln, f)
+ {
+ Efl_Loop_Future_Callback *cb;
+
+ EINA_INLIST_FOREACH(f->callbacks, cb)
+ {
+ if (cb->next)
+ {
+ chain_progress.next = cb->next;
+
+ cb->progress((void*) cb->data, &ev);
+ }
+ }
+ }
+}
+
+static Efl_Object *
+_efl_promise_efl_object_constructor(Eo *obj, Efl_Promise_Data *pd)
+{
+ pd->promise = obj;
+
+ return efl_constructor(efl_super(obj, EFL_PROMISE_CLASS));
+}
+
+static void
+_efl_promise_efl_object_destructor(Eo *obj, Efl_Promise_Data *pd)
+{
+ // Unref refcounted structure
+ if (!pd->message && pd->futures)
+ {
+ ERR("This promise has not been fulfilled. Forcefully cancelling %p.", obj);
+ efl_promise_failed(obj, EINA_ERROR_FUTURE_CANCEL);
+ }
+
+ if (pd->message)
+ {
+ EINA_REFCOUNT_UNREF(pd->message)
+ _efl_promise_msg_free(pd->message);
+ pd->message = NULL;
+ }
+
+ efl_destructor(efl_super(obj, EFL_PROMISE_CLASS));
+}
+
+#include "efl_promise.eo.c"
diff --git a/src/lib/ecore/efl_promise.eo b/src/lib/ecore/efl_promise.eo
new file mode 100644
index 0000000000..7d3b348797
--- /dev/null
+++ b/src/lib/ecore/efl_promise.eo
@@ -0,0 +1,44 @@
+import eina_types;
+
+class Efl.Promise (Efl.Loop_User)
+{
+ methods {
+ @property progress {
+ set {
+ }
+ values {
+ p: void_ptr;
+ }
+ }
+ @property future {
+ get {
+ [[The returned future is optional and if no then/chain_then are registered before it goes back to run in the main loop, it will be destroyed.]]
+ }
+ values {
+ f: future<void_ptr>;
+ }
+ }
+ @property value {
+ set {
+ }
+ values {
+ v: void_ptr;
+ free_cb: __builtin_free_cb;
+ }
+ }
+ failed {
+ params {
+ @in err: Eina.Error;
+ }
+ }
+ }
+ events {
+ future,set: future<void_ptr>; [[This event is triggered whenever a future is fully set to receive all events and that the user of it do not hold any more reference on it.]]
+ future,progress,set: future<void_ptr>; [[This event is triggered whenever a future has a progress callback registered and the user does not hold any more reference on it.]]
+ future,none; [[This event is triggered whenever there is no more future connected to the promise.]]
+ }
+ implements {
+ Efl.Object.destructor;
+ Efl.Object.constructor;
+ }
+}