aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Makefile_Efl_Mono.am2
-rw-r--r--src/bin/eolian_mono/eolian/mono/blacklist.hh4
-rw-r--r--src/bindings/mono/eina_mono/eina_error.cs1
-rw-r--r--src/bindings/mono/eina_mono/eina_promises.cs298
-rw-r--r--src/tests/efl_mono/Promises.cs235
5 files changed, 539 insertions, 1 deletions
diff --git a/src/Makefile_Efl_Mono.am b/src/Makefile_Efl_Mono.am
index 4afc577d4a..64b4a7cbf2 100644
--- a/src/Makefile_Efl_Mono.am
+++ b/src/Makefile_Efl_Mono.am
@@ -22,6 +22,7 @@ efl_eina_mono_files = \
bindings/mono/eina_mono/eina_stringshare.cs \
bindings/mono/eina_mono/eina_error.cs \
bindings/mono/eina_mono/eina_value.cs \
+ bindings/mono/eina_mono/eina_promises.cs \
bindings/mono/eina_mono/eina_strbuf.cs
efl_eldbus_mono_files = \
@@ -439,6 +440,7 @@ tests_efl_mono_efl_mono_SOURCES = \
tests/efl_mono/FunctionPointers.cs \
tests/efl_mono/FunctionPointerMarshalling.cs \
tests/efl_mono/Parts.cs \
+ tests/efl_mono/Promises.cs \
tests/efl_mono/Strbuf.cs \
tests/efl_mono/Strings.cs \
tests/efl_mono/Structs.cs \
diff --git a/src/bin/eolian_mono/eolian/mono/blacklist.hh b/src/bin/eolian_mono/eolian/mono/blacklist.hh
index 6fe4d584c4..1db4c6108a 100644
--- a/src/bin/eolian_mono/eolian/mono/blacklist.hh
+++ b/src/bin/eolian_mono/eolian/mono/blacklist.hh
@@ -60,7 +60,9 @@ inline bool is_struct_blacklisted(std::string const& full_name)
|| full_name == "Eina.Binbuf"
|| full_name == "Eina.Strbuf"
|| full_name == "Eina.Slice"
- || full_name == "Eina.Rw_Slice";
+ || full_name == "Eina.Rw_Slice"
+ || full_name == "Eina.Promise"
+ || full_name == "Eina.Future";
}
inline bool is_struct_blacklisted(attributes::struct_def const& struct_)
diff --git a/src/bindings/mono/eina_mono/eina_error.cs b/src/bindings/mono/eina_mono/eina_error.cs
index 58495aa904..4271df8cea 100644
--- a/src/bindings/mono/eina_mono/eina_error.cs
+++ b/src/bindings/mono/eina_mono/eina_error.cs
@@ -19,6 +19,7 @@ public struct Error : IComparable<Error>
public static Error NO_ERROR = new Error(0);
public static Error EPERM = new Error(1);
public static Error ENOENT = new Error(2);
+ public static Error ECANCELED = new Error(125);
public Error(int value) { code = value; }
static public implicit operator Error(int val)
diff --git a/src/bindings/mono/eina_mono/eina_promises.cs b/src/bindings/mono/eina_mono/eina_promises.cs
new file mode 100644
index 0000000000..46fe800869
--- /dev/null
+++ b/src/bindings/mono/eina_mono/eina_promises.cs
@@ -0,0 +1,298 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using System.Linq;
+
+
+using static eina.EinaNative.PromiseNativeMethods;
+
+namespace eina {
+
+namespace EinaNative {
+
+static internal class PromiseNativeMethods
+{
+ internal delegate void Promise_Cancel_Cb(IntPtr data, IntPtr dead);
+
+ [DllImport(efl.Libs.Ecore)]
+ internal static extern IntPtr efl_loop_promise_new(IntPtr obj, Promise_Cancel_Cb cancel_cb, IntPtr data);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern IntPtr eina_promise_new(IntPtr scheduler, Promise_Cancel_Cb cancel_cb, IntPtr data);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern void eina_promise_resolve(IntPtr scheduler, eina.Value_Native value);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern void eina_promise_reject(IntPtr scheduler, eina.Error reason);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern IntPtr eina_future_new(IntPtr promise);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern void eina_future_cancel(IntPtr future);
+
+ [DllImport(efl.Libs.Ecore)]
+ internal static extern IntPtr efl_loop_future_scheduler_get(IntPtr obj);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern IntPtr eina_future_then_from_desc(IntPtr prev, FutureDesc desc);
+
+ [DllImport(efl.Libs.Eina)]
+ internal static extern IntPtr eina_future_chain_array(IntPtr prev, FutureDesc[] desc);
+
+ internal delegate eina.Value_Native FutureCb(IntPtr data, eina.Value_Native value, IntPtr dead_future);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FutureDesc
+ {
+ internal FutureCb cb;
+ internal IntPtr data;
+ internal IntPtr storage; // Internal use by eina
+
+ public FutureDesc(FutureCb cb, IntPtr data, IntPtr storage)
+ {
+ this.cb = cb;
+ this.data = data;
+ this.storage = storage;
+ }
+ }
+}
+
+} // namespace EinaNative
+
+/// <summary>
+/// Promises act as placeholders for a value that may be available in the future.
+///
+/// With a Promise you can attach futures to it, which will be used to notify of the value being available.
+/// </summary>
+public class Promise
+{
+ internal IntPtr Handle;
+ private GCHandle CleanupHandle;
+
+ /// <summary>Delegate for functions that will be called upon a promise cancellation.</summary>
+ public delegate void CancelCb();
+
+ /// <summary>
+ /// Creates a new Promise with the given callback.
+ ///
+ /// Currently, creating a promise directly uses the Main Loop scheduler the source of notifications (i.e. the
+ /// future callbacks will be called mainly from a loop iteration).
+ /// </summary>
+ public Promise(CancelCb cancelCb=null)
+ {
+ efl.ILoop loop = efl.App.GetLoopMain();
+
+ // Should we be able to pass different schedulers?
+ IntPtr scheduler = efl_loop_future_scheduler_get(loop.raw_handle);
+
+ IntPtr cb_data = IntPtr.Zero;
+
+ // A safety clean callback to mark this wrapper as invalid
+ CancelCb safetyCb = () => {
+ Handle = IntPtr.Zero;
+ if (cancelCb != null)
+ cancelCb();
+ };
+
+ CleanupHandle = GCHandle.Alloc(safetyCb);
+ cb_data = GCHandle.ToIntPtr(CleanupHandle);
+
+ this.Handle = eina_promise_new(scheduler, NativeCancelCb, cb_data);
+ }
+
+ private static void NativeCancelCb(IntPtr data, IntPtr dead)
+ {
+ if (data == IntPtr.Zero)
+ return;
+
+ GCHandle handle = GCHandle.FromIntPtr(data);
+ CancelCb cb = handle.Target as CancelCb;
+ if (cb != null)
+ cb();
+ else
+ eina.Log.Info("Null promise CancelCb found");
+ handle.Free();
+ }
+
+ private void SanityChecks()
+ {
+ if (this.Handle == IntPtr.Zero)
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ /// <summary>
+ /// Fulfills a promise with the given value.
+ ///
+ /// This will make all futures attached to it to be called with the given value as payload.
+ /// </summary>
+ public void Resolve(eina.Value value)
+ {
+ SanityChecks();
+ eina_promise_resolve(this.Handle, value);
+ this.Handle = IntPtr.Zero;
+ // Resolving a cb does *not* call its cancellation callback, so we have to release the
+ // lambda created in the constructor for cleanup.
+ CleanupHandle.Free();
+ }
+
+ /// <summary>
+ /// Rejects a promise.
+ ///
+ /// The future chain attached to this promise will be called with an eina.Value of type
+ /// eina.ValueType.Error and payload eina.Error.ECANCELED.
+ /// </summary>
+ public void Reject(eina.Error reason)
+ {
+ SanityChecks();
+ eina_promise_reject(this.Handle, reason);
+ this.Handle = IntPtr.Zero;
+ }
+}
+
+/// <summary>
+/// Futures are the structures holding the callbacks to be notified of a promise fullfillment
+/// or cancellation.
+/// </summary>
+public class Future
+{
+ /// <summary>
+ /// Callback attached to a future and to be called when resolving/rejecting a promise.
+ ///
+ /// The eina.Value as argument can come with an eina.Error.ECANCELED as payload if the
+ /// promise/future was rejected/cancelled.
+ ///
+ /// The return value usually is same as the argument, forwarded, but can be changed in
+ /// case were the chain act as a transforming pipeline.
+ /// </summary>
+ public delegate eina.Value ResolvedCb(eina.Value value);
+
+ private IntPtr Handle;
+
+ /// <summary>
+ /// Creates a Future from a native pointer.
+ /// </summary>
+ public Future(IntPtr handle)
+ {
+ Handle = ThenRaw(handle, (eina.Value value) => {
+ Handle = IntPtr.Zero;
+ return value;
+ });
+ }
+
+ /// <summary>
+ /// Creates a Future attached to the given Promise.
+ ///
+ /// Optionally a resolved callback may be provided. If so, it will be chained
+ /// before the returned future.
+ /// </summary>
+ public Future(Promise promise, ResolvedCb cb=null)
+ {
+ IntPtr intermediate = eina_future_new(promise.Handle);
+ Handle = ThenRaw(intermediate, (eina.Value value) => {
+ if (cb != null)
+ value = cb(value);
+ Handle = IntPtr.Zero;
+ return value;
+ });
+ }
+
+ private void SanityChecks()
+ {
+ if (this.Handle == IntPtr.Zero)
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ /// <summary>
+ /// Cancels this future and the chain it belongs to, along with the promise linked against it.
+ ///
+ /// The callbacks will still be called with eina.Error.ECANCELED as payload. The promise cancellation
+ /// callback will also be called if present.
+ /// </summary>
+ public void Cancel()
+ {
+ SanityChecks();
+ eina_future_cancel(this.Handle);
+ }
+
+ /// <summary>
+ /// Creates a new future to be called after this one.
+ ///
+ /// Once the promise this future is attached to resolves, the callbacks on the chain
+ /// are called in the order they were chained.
+ ///
+ /// CAUTION: Calling Then() on a future that had it called before will replace the previous chain
+ /// from this point on.
+ /// </summary>
+ public Future Then(ResolvedCb cb)
+ {
+ SanityChecks();
+ return new Future(ThenRaw(Handle, cb));
+ }
+
+ // Helper function to attach a cb to a future without creating a new wrapper directly.
+ // It'll be used in the construtor, to attach the cleaning cb to an intermediate future.
+ private static IntPtr ThenRaw(IntPtr previous, ResolvedCb cb)
+ {
+ FutureDesc desc = new FutureDesc();
+ desc.cb = NativeResolvedCb;
+ GCHandle handle = GCHandle.Alloc(cb);
+ desc.data = GCHandle.ToIntPtr(handle);
+ return eina_future_then_from_desc(previous, desc);
+ }
+ private static eina.Value_Native NativeResolvedCb(IntPtr data, eina.Value_Native value, IntPtr dead_future)
+ {
+ GCHandle handle = GCHandle.FromIntPtr(data);
+ ResolvedCb cb = handle.Target as ResolvedCb;
+ if (cb != null)
+ value = cb(value);
+ else
+ eina.Log.Warning("Failed to get future callback.");
+ handle.Free();
+ return value;
+ }
+
+ /// <summary>
+ /// Helper method for chaining a group of callbacks in a single go.
+ ///
+ /// It is just syntatic sugar for sequential Then() calls, without creating intermediate
+ /// futures explicitly.
+ /// </summary>
+ public Future Chain(IEnumerable<ResolvedCb> cbs)
+ {
+ SanityChecks();
+ System.Collections.Generic.IList<ResolvedCb> cbsList = cbs.ToList();
+ FutureDesc[] descs = new FutureDesc[cbsList.Count() + 1]; // +1 due to the null-cb terminating descriptor.
+ int i = 0;
+ try
+ {
+ for (; i < cbsList.Count(); i++)
+ {
+ ResolvedCb cb = cbsList[i];
+ descs[i].cb = NativeResolvedCb;
+ GCHandle handle = GCHandle.Alloc(cb);
+ descs[i].data = GCHandle.ToIntPtr(handle);
+ }
+
+ descs[i].cb = null;
+ descs[i].data = IntPtr.Zero;
+ }
+ catch (Exception e)
+ {
+ for (int j = 0; j <= i; j++)
+ {
+ if (descs[i].data == IntPtr.Zero)
+ continue;
+
+ GCHandle handle = GCHandle.FromIntPtr(descs[i].data);
+ handle.Free();
+ }
+ eina.Log.Error($"Failed to create native future description for callbacks. Error: {e.ToString()}");
+ return null;
+ }
+ return new Future(eina_future_chain_array(Handle, descs));
+ }
+}
+
+} // namespace eina
diff --git a/src/tests/efl_mono/Promises.cs b/src/tests/efl_mono/Promises.cs
new file mode 100644
index 0000000000..5759f56800
--- /dev/null
+++ b/src/tests/efl_mono/Promises.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+
+namespace TestSuite
+{
+
+class TestPromises
+{
+ public static void test_simple_cancel()
+ {
+ bool cleanCalled = false;
+ eina.Promise promise = new eina.Promise(() => { cleanCalled = true; });
+ eina.Future future = new eina.Future(promise);
+ future.Cancel();
+ Test.Assert(cleanCalled, "Promise clean callback should have been called.");
+ Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
+ Test.AssertRaises<ObjectDisposedException>(future.Cancel);
+ }
+
+ public static void test_simple_resolve()
+ {
+ bool callbackCalled = false;
+ eina.Value received_value = null;
+
+ efl.ILoop loop = efl.App.GetLoopMain();
+ eina.Promise promise = new eina.Promise();
+ eina.Future future = new eina.Future(promise);
+
+ future = future.Then((eina.Value value) => {
+ callbackCalled = true;
+ received_value = value;
+ return value;
+ } );
+
+ eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
+ reference_value.Set(1984);
+ promise.Resolve(reference_value);
+
+ loop.Iterate();
+
+ Test.Assert(callbackCalled, "Future callback should have been called.");
+ Test.AssertEquals(received_value, reference_value);
+ }
+
+ public static void test_simple_reject()
+ {
+ bool callbackCalled = false;
+ eina.Error received_error = eina.Error.NO_ERROR;
+
+ efl.ILoop loop = efl.App.GetLoopMain();
+ eina.Promise promise = new eina.Promise();
+ eina.Future future = new eina.Future(promise);
+
+ future = future.Then((eina.Value value) => {
+ callbackCalled = true;
+ value.Get(out received_error);
+ return value;
+ });
+
+ promise.Reject(eina.Error.EPERM);
+
+ loop.Iterate();
+
+ Test.Assert(callbackCalled, "Future callback should have been called.");
+ Test.AssertEquals(received_error, eina.Error.EPERM);
+
+ Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
+ Test.AssertRaises<ObjectDisposedException>(future.Cancel);
+ }
+
+ public static void test_simple_future_cancel()
+ {
+ bool callbackCalled = false;
+ bool promiseCallbackCalled = false;
+ eina.Error received_error = eina.Error.NO_ERROR;
+
+ eina.Promise promise = new eina.Promise(() => { promiseCallbackCalled = true; });
+ eina.Future future = new eina.Future(promise);
+
+ future = future.Then((eina.Value value) => {
+ callbackCalled = true;
+ value.Get(out received_error);
+ return value;
+ });
+
+ future.Cancel();
+
+ Test.Assert(promiseCallbackCalled, "Promise cancel callback should have been called.");
+ Test.Assert(callbackCalled, "Future callback should have been called.");
+ Test.AssertEquals(received_error, eina.Error.ECANCELED);
+ }
+
+
+ private delegate eina.Future.ResolvedCb FutureCbGenerator(int x);
+ public static void test_then_chaining()
+ {
+ bool[] callbacksCalled = {false, false, false, false};
+ eina.Value[] received_value = {null, null, null, null};
+
+ FutureCbGenerator genResolvedCb = (int i) => {
+ return (eina.Value value) => {
+ callbacksCalled[i] = true;
+ int x;
+ value.Get(out x);
+ value.Set(x + i);
+ received_value[i] = value;
+ return value;
+ };
+ };
+
+ efl.ILoop loop = efl.App.GetLoopMain();
+ eina.Promise promise = new eina.Promise();
+ eina.Future future = new eina.Future(promise);
+ for (int i = 0; i < 4; i++)
+ future = future.Then(genResolvedCb(i));
+
+ eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
+ reference_value.Set(0);
+ promise.Resolve(reference_value);
+
+ loop.Iterate();
+
+ int current_value = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ current_value += i;
+ Test.Assert(callbacksCalled[i], $"Future callback {i} should have been called.");
+ int received;
+ received_value[i].Get(out received);
+ Test.AssertEquals(received, current_value);
+ }
+
+ Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
+ Test.AssertRaises<ObjectDisposedException>(future.Cancel);
+ }
+
+ public static void test_then_chain_array()
+ {
+ bool[] callbacksCalled = {false, false, false, false};
+ eina.Value[] received_value = {null, null, null, null};
+
+ FutureCbGenerator genResolvedCb = (int i) => {
+ return (eina.Value value) => {
+ callbacksCalled[i] = true;
+ int x;
+ value.Get(out x);
+ value.Set(x + i);
+ received_value[i] = value;
+ return value;
+ };
+ };
+
+ var cbs = new List<eina.Future.ResolvedCb>();
+ for (int i = 0; i < 4; i++)
+ cbs.Add(genResolvedCb(i));
+
+ efl.ILoop loop = efl.App.GetLoopMain();
+ eina.Promise promise = new eina.Promise();
+ eina.Future future = new eina.Future(promise);
+ future = future.Chain(cbs);
+
+ eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
+ reference_value.Set(0);
+ promise.Resolve(reference_value);
+
+ loop.Iterate();
+
+ int current_value = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ current_value += i;
+ Test.Assert(callbacksCalled[i], $"Future chained callback {i} should have been called.");
+ int received;
+ received_value[i].Get(out received);
+ Test.AssertEquals(received, current_value);
+ }
+
+ Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
+ Test.AssertRaises<ObjectDisposedException>(future.Cancel);
+ }
+
+ public static void test_cancel_after_resolve()
+ {
+ bool callbackCalled = false;
+ eina.Error received_error = eina.Error.NO_ERROR;
+
+ efl.ILoop loop = efl.App.GetLoopMain();
+ eina.Promise promise = new eina.Promise();
+ eina.Future future = new eina.Future(promise);
+
+ future = future.Then((eina.Value value) => {
+ callbackCalled = true;
+ value.Get(out received_error);
+ return value;
+ });
+
+ promise.Reject(eina.Error.EPERM);
+ future.Cancel();
+
+ loop.Iterate();
+
+ Test.Assert(callbackCalled, "Future callback should have been called.");
+ Test.AssertEquals(received_error, eina.Error.ECANCELED);
+
+ Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
+ Test.AssertRaises<ObjectDisposedException>(future.Cancel);
+ }
+
+ public static void test_constructor_with_callback()
+ {
+ bool callbackCalled = false;
+ eina.Value received_value = null;
+
+ efl.ILoop loop = efl.App.GetLoopMain();
+ eina.Promise promise = new eina.Promise();
+#pragma warning disable 0219
+ eina.Future future = new eina.Future(promise,(eina.Value value) => {
+ callbackCalled = true;
+ received_value = value;
+ return value;
+ } );
+#pragma warning restore 0219
+
+ eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
+ reference_value.Set(1984);
+ promise.Resolve(reference_value);
+
+ loop.Iterate();
+
+ Test.Assert(callbackCalled, "Future callback should have been called.");
+ Test.AssertEquals(received_value, reference_value);
+ }
+}
+
+}