summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLauro Moura <lauromoura@expertisesolutions.com.br>2018-04-20 18:17:26 -0300
committerLauro Moura <lauromoura@expertisesolutions.com.br>2018-05-17 16:56:25 -0300
commitfff0c86d99fa63bf7167830da27118d9f2366b4b (patch)
treeb14c316660b48ed16252f42982c5e7bb06f84ae1
parent95c8a7d28c2d2443389a3bcbffeae36966a7dc47 (diff)
efl_mono: Initial support for Futures/Promises
Summary: Promise/Future cleanup: In the promises, we use a wrapper Eina_Promise_Cancel_Cb to invalidate the wrapper if it ever gets cancelled from outside. When invalidating from C#, we can do it directly. For the futures, likewise, in order to be able to invalidate the wrapper when the chain it belongs to gets resolved we then() an internal future with a callback to invalidate the wrapper we return to C#. The return of this intermediate then() is the future we actually return to the user. Also added ECANCELED to the list of default eina.Errors Depends on D6173 Reviewers: felipealmeida Reviewed By: felipealmeida Subscribers: cedric, zmike Tags: #efl Differential Revision: https://phab.enlightenment.org/D6174
-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 = \
22 bindings/mono/eina_mono/eina_stringshare.cs \ 22 bindings/mono/eina_mono/eina_stringshare.cs \
23 bindings/mono/eina_mono/eina_error.cs \ 23 bindings/mono/eina_mono/eina_error.cs \
24 bindings/mono/eina_mono/eina_value.cs \ 24 bindings/mono/eina_mono/eina_value.cs \
25 bindings/mono/eina_mono/eina_promises.cs \
25 bindings/mono/eina_mono/eina_strbuf.cs 26 bindings/mono/eina_mono/eina_strbuf.cs
26 27
27efl_eldbus_mono_files = \ 28efl_eldbus_mono_files = \
@@ -439,6 +440,7 @@ tests_efl_mono_efl_mono_SOURCES = \
439 tests/efl_mono/FunctionPointers.cs \ 440 tests/efl_mono/FunctionPointers.cs \
440 tests/efl_mono/FunctionPointerMarshalling.cs \ 441 tests/efl_mono/FunctionPointerMarshalling.cs \
441 tests/efl_mono/Parts.cs \ 442 tests/efl_mono/Parts.cs \
443 tests/efl_mono/Promises.cs \
442 tests/efl_mono/Strbuf.cs \ 444 tests/efl_mono/Strbuf.cs \
443 tests/efl_mono/Strings.cs \ 445 tests/efl_mono/Strings.cs \
444 tests/efl_mono/Structs.cs \ 446 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)
60 || full_name == "Eina.Binbuf" 60 || full_name == "Eina.Binbuf"
61 || full_name == "Eina.Strbuf" 61 || full_name == "Eina.Strbuf"
62 || full_name == "Eina.Slice" 62 || full_name == "Eina.Slice"
63 || full_name == "Eina.Rw_Slice"; 63 || full_name == "Eina.Rw_Slice"
64 || full_name == "Eina.Promise"
65 || full_name == "Eina.Future";
64} 66}
65 67
66inline bool is_struct_blacklisted(attributes::struct_def const& struct_) 68inline 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>
19 public static Error NO_ERROR = new Error(0); 19 public static Error NO_ERROR = new Error(0);
20 public static Error EPERM = new Error(1); 20 public static Error EPERM = new Error(1);
21 public static Error ENOENT = new Error(2); 21 public static Error ENOENT = new Error(2);
22 public static Error ECANCELED = new Error(125);
22 23
23 public Error(int value) { code = value; } 24 public Error(int value) { code = value; }
24 static public implicit operator Error(int val) 25 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 @@
1using System;
2using System.Runtime.InteropServices;
3using System.Collections.Generic;
4using System.Linq;
5
6
7using static eina.EinaNative.PromiseNativeMethods;
8
9namespace eina {
10
11namespace EinaNative {
12
13static internal class PromiseNativeMethods
14{
15 internal delegate void Promise_Cancel_Cb(IntPtr data, IntPtr dead);
16
17 [DllImport(efl.Libs.Ecore)]
18 internal static extern IntPtr efl_loop_promise_new(IntPtr obj, Promise_Cancel_Cb cancel_cb, IntPtr data);
19
20 [DllImport(efl.Libs.Eina)]
21 internal static extern IntPtr eina_promise_new(IntPtr scheduler, Promise_Cancel_Cb cancel_cb, IntPtr data);
22
23 [DllImport(efl.Libs.Eina)]
24 internal static extern void eina_promise_resolve(IntPtr scheduler, eina.Value_Native value);
25
26 [DllImport(efl.Libs.Eina)]
27 internal static extern void eina_promise_reject(IntPtr scheduler, eina.Error reason);
28
29 [DllImport(efl.Libs.Eina)]
30 internal static extern IntPtr eina_future_new(IntPtr promise);
31
32 [DllImport(efl.Libs.Eina)]
33 internal static extern void eina_future_cancel(IntPtr future);
34
35 [DllImport(efl.Libs.Ecore)]
36 internal static extern IntPtr efl_loop_future_scheduler_get(IntPtr obj);
37
38 [DllImport(efl.Libs.Eina)]
39 internal static extern IntPtr eina_future_then_from_desc(IntPtr prev, FutureDesc desc);
40
41 [DllImport(efl.Libs.Eina)]
42 internal static extern IntPtr eina_future_chain_array(IntPtr prev, FutureDesc[] desc);
43
44 internal delegate eina.Value_Native FutureCb(IntPtr data, eina.Value_Native value, IntPtr dead_future);
45
46 [StructLayout(LayoutKind.Sequential)]
47 internal struct FutureDesc
48 {
49 internal FutureCb cb;
50 internal IntPtr data;
51 internal IntPtr storage; // Internal use by eina
52
53 public FutureDesc(FutureCb cb, IntPtr data, IntPtr storage)
54 {
55 this.cb = cb;
56 this.data = data;
57 this.storage = storage;
58 }
59 }
60}
61
62} // namespace EinaNative
63
64/// <summary>
65/// Promises act as placeholders for a value that may be available in the future.
66///
67/// With a Promise you can attach futures to it, which will be used to notify of the value being available.
68/// </summary>
69public class Promise
70{
71 internal IntPtr Handle;
72 private GCHandle CleanupHandle;
73
74 /// <summary>Delegate for functions that will be called upon a promise cancellation.</summary>
75 public delegate void CancelCb();
76
77 /// <summary>
78 /// Creates a new Promise with the given callback.
79 ///
80 /// Currently, creating a promise directly uses the Main Loop scheduler the source of notifications (i.e. the
81 /// future callbacks will be called mainly from a loop iteration).
82 /// </summary>
83 public Promise(CancelCb cancelCb=null)
84 {
85 efl.ILoop loop = efl.App.GetLoopMain();
86
87 // Should we be able to pass different schedulers?
88 IntPtr scheduler = efl_loop_future_scheduler_get(loop.raw_handle);
89
90 IntPtr cb_data = IntPtr.Zero;
91
92 // A safety clean callback to mark this wrapper as invalid
93 CancelCb safetyCb = () => {
94 Handle = IntPtr.Zero;
95 if (cancelCb != null)
96 cancelCb();
97 };
98
99 CleanupHandle = GCHandle.Alloc(safetyCb);
100 cb_data = GCHandle.ToIntPtr(CleanupHandle);
101
102 this.Handle = eina_promise_new(scheduler, NativeCancelCb, cb_data);
103 }
104
105 private static void NativeCancelCb(IntPtr data, IntPtr dead)
106 {
107 if (data == IntPtr.Zero)
108 return;
109
110 GCHandle handle = GCHandle.FromIntPtr(data);
111 CancelCb cb = handle.Target as CancelCb;
112 if (cb != null)
113 cb();
114 else
115 eina.Log.Info("Null promise CancelCb found");
116 handle.Free();
117 }
118
119 private void SanityChecks()
120 {
121 if (this.Handle == IntPtr.Zero)
122 throw new ObjectDisposedException(GetType().Name);
123 }
124
125 /// <summary>
126 /// Fulfills a promise with the given value.
127 ///
128 /// This will make all futures attached to it to be called with the given value as payload.
129 /// </summary>
130 public void Resolve(eina.Value value)
131 {
132 SanityChecks();
133 eina_promise_resolve(this.Handle, value);
134 this.Handle = IntPtr.Zero;
135 // Resolving a cb does *not* call its cancellation callback, so we have to release the
136 // lambda created in the constructor for cleanup.
137 CleanupHandle.Free();
138 }
139
140 /// <summary>
141 /// Rejects a promise.
142 ///
143 /// The future chain attached to this promise will be called with an eina.Value of type
144 /// eina.ValueType.Error and payload eina.Error.ECANCELED.
145 /// </summary>
146 public void Reject(eina.Error reason)
147 {
148 SanityChecks();
149 eina_promise_reject(this.Handle, reason);
150 this.Handle = IntPtr.Zero;
151 }
152}
153
154/// <summary>
155/// Futures are the structures holding the callbacks to be notified of a promise fullfillment
156/// or cancellation.
157/// </summary>
158public class Future
159{
160 /// <summary>
161 /// Callback attached to a future and to be called when resolving/rejecting a promise.
162 ///
163 /// The eina.Value as argument can come with an eina.Error.ECANCELED as payload if the
164 /// promise/future was rejected/cancelled.
165 ///
166 /// The return value usually is same as the argument, forwarded, but can be changed in
167 /// case were the chain act as a transforming pipeline.
168 /// </summary>
169 public delegate eina.Value ResolvedCb(eina.Value value);
170
171 private IntPtr Handle;
172
173 /// <summary>
174 /// Creates a Future from a native pointer.
175 /// </summary>
176 public Future(IntPtr handle)
177 {
178 Handle = ThenRaw(handle, (eina.Value value) => {
179 Handle = IntPtr.Zero;
180 return value;
181 });
182 }
183
184 /// <summary>
185 /// Creates a Future attached to the given Promise.
186 ///
187 /// Optionally a resolved callback may be provided. If so, it will be chained
188 /// before the returned future.
189 /// </summary>
190 public Future(Promise promise, ResolvedCb cb=null)
191 {
192 IntPtr intermediate = eina_future_new(promise.Handle);
193 Handle = ThenRaw(intermediate, (eina.Value value) => {
194 if (cb != null)
195 value = cb(value);
196 Handle = IntPtr.Zero;
197 return value;
198 });
199 }
200
201 private void SanityChecks()
202 {
203 if (this.Handle == IntPtr.Zero)
204 throw new ObjectDisposedException(GetType().Name);
205 }
206
207 /// <summary>
208 /// Cancels this future and the chain it belongs to, along with the promise linked against it.
209 ///
210 /// The callbacks will still be called with eina.Error.ECANCELED as payload. The promise cancellation
211 /// callback will also be called if present.
212 /// </summary>
213 public void Cancel()
214 {
215 SanityChecks();
216 eina_future_cancel(this.Handle);
217 }
218
219 /// <summary>
220 /// Creates a new future to be called after this one.
221 ///
222 /// Once the promise this future is attached to resolves, the callbacks on the chain
223 /// are called in the order they were chained.
224 ///
225 /// CAUTION: Calling Then() on a future that had it called before will replace the previous chain
226 /// from this point on.
227 /// </summary>
228 public Future Then(ResolvedCb cb)
229 {
230 SanityChecks();
231 return new Future(ThenRaw(Handle, cb));
232 }
233
234 // Helper function to attach a cb to a future without creating a new wrapper directly.
235 // It'll be used in the construtor, to attach the cleaning cb to an intermediate future.
236 private static IntPtr ThenRaw(IntPtr previous, ResolvedCb cb)
237 {
238 FutureDesc desc = new FutureDesc();
239 desc.cb = NativeResolvedCb;
240 GCHandle handle = GCHandle.Alloc(cb);
241 desc.data = GCHandle.ToIntPtr(handle);
242 return eina_future_then_from_desc(previous, desc);
243 }
244 private static eina.Value_Native NativeResolvedCb(IntPtr data, eina.Value_Native value, IntPtr dead_future)
245 {
246 GCHandle handle = GCHandle.FromIntPtr(data);
247 ResolvedCb cb = handle.Target as ResolvedCb;
248 if (cb != null)
249 value = cb(value);
250 else
251 eina.Log.Warning("Failed to get future callback.");
252 handle.Free();
253 return value;
254 }
255
256 /// <summary>
257 /// Helper method for chaining a group of callbacks in a single go.
258 ///
259 /// It is just syntatic sugar for sequential Then() calls, without creating intermediate
260 /// futures explicitly.
261 /// </summary>
262 public Future Chain(IEnumerable<ResolvedCb> cbs)
263 {
264 SanityChecks();
265 System.Collections.Generic.IList<ResolvedCb> cbsList = cbs.ToList();
266 FutureDesc[] descs = new FutureDesc[cbsList.Count() + 1]; // +1 due to the null-cb terminating descriptor.
267 int i = 0;
268 try
269 {
270 for (; i < cbsList.Count(); i++)
271 {
272 ResolvedCb cb = cbsList[i];
273 descs[i].cb = NativeResolvedCb;
274 GCHandle handle = GCHandle.Alloc(cb);
275 descs[i].data = GCHandle.ToIntPtr(handle);
276 }
277
278 descs[i].cb = null;
279 descs[i].data = IntPtr.Zero;
280 }
281 catch (Exception e)
282 {
283 for (int j = 0; j <= i; j++)
284 {
285 if (descs[i].data == IntPtr.Zero)
286 continue;
287
288 GCHandle handle = GCHandle.FromIntPtr(descs[i].data);
289 handle.Free();
290 }
291 eina.Log.Error($"Failed to create native future description for callbacks. Error: {e.ToString()}");
292 return null;
293 }
294 return new Future(eina_future_chain_array(Handle, descs));
295 }
296}
297
298} // 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 @@
1using System;
2using System.Collections.Generic;
3
4namespace TestSuite
5{
6
7class TestPromises
8{
9 public static void test_simple_cancel()
10 {
11 bool cleanCalled = false;
12 eina.Promise promise = new eina.Promise(() => { cleanCalled = true; });
13 eina.Future future = new eina.Future(promise);
14 future.Cancel();
15 Test.Assert(cleanCalled, "Promise clean callback should have been called.");
16 Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
17 Test.AssertRaises<ObjectDisposedException>(future.Cancel);
18 }
19
20 public static void test_simple_resolve()
21 {
22 bool callbackCalled = false;
23 eina.Value received_value = null;
24
25 efl.ILoop loop = efl.App.GetLoopMain();
26 eina.Promise promise = new eina.Promise();
27 eina.Future future = new eina.Future(promise);
28
29 future = future.Then((eina.Value value) => {
30 callbackCalled = true;
31 received_value = value;
32 return value;
33 } );
34
35 eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
36 reference_value.Set(1984);
37 promise.Resolve(reference_value);
38
39 loop.Iterate();
40
41 Test.Assert(callbackCalled, "Future callback should have been called.");
42 Test.AssertEquals(received_value, reference_value);
43 }
44
45 public static void test_simple_reject()
46 {
47 bool callbackCalled = false;
48 eina.Error received_error = eina.Error.NO_ERROR;
49
50 efl.ILoop loop = efl.App.GetLoopMain();
51 eina.Promise promise = new eina.Promise();
52 eina.Future future = new eina.Future(promise);
53
54 future = future.Then((eina.Value value) => {
55 callbackCalled = true;
56 value.Get(out received_error);
57 return value;
58 });
59
60 promise.Reject(eina.Error.EPERM);
61
62 loop.Iterate();
63
64 Test.Assert(callbackCalled, "Future callback should have been called.");
65 Test.AssertEquals(received_error, eina.Error.EPERM);
66
67 Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
68 Test.AssertRaises<ObjectDisposedException>(future.Cancel);
69 }
70
71 public static void test_simple_future_cancel()
72 {
73 bool callbackCalled = false;
74 bool promiseCallbackCalled = false;
75 eina.Error received_error = eina.Error.NO_ERROR;
76
77 eina.Promise promise = new eina.Promise(() => { promiseCallbackCalled = true; });
78 eina.Future future = new eina.Future(promise);
79
80 future = future.Then((eina.Value value) => {
81 callbackCalled = true;
82 value.Get(out received_error);
83 return value;
84 });
85
86 future.Cancel();
87
88 Test.Assert(promiseCallbackCalled, "Promise cancel callback should have been called.");
89 Test.Assert(callbackCalled, "Future callback should have been called.");
90 Test.AssertEquals(received_error, eina.Error.ECANCELED);
91 }
92
93
94 private delegate eina.Future.ResolvedCb FutureCbGenerator(int x);
95 public static void test_then_chaining()
96 {
97 bool[] callbacksCalled = {false, false, false, false};
98 eina.Value[] received_value = {null, null, null, null};
99
100 FutureCbGenerator genResolvedCb = (int i) => {
101 return (eina.Value value) => {
102 callbacksCalled[i] = true;
103 int x;
104 value.Get(out x);
105 value.Set(x + i);
106 received_value[i] = value;
107 return value;
108 };
109 };
110
111 efl.ILoop loop = efl.App.GetLoopMain();
112 eina.Promise promise = new eina.Promise();
113 eina.Future future = new eina.Future(promise);
114 for (int i = 0; i < 4; i++)
115 future = future.Then(genResolvedCb(i));
116
117 eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
118 reference_value.Set(0);
119 promise.Resolve(reference_value);
120
121 loop.Iterate();
122
123 int current_value = 0;
124 for (int i = 0; i < 4; i++)
125 {
126 current_value += i;
127 Test.Assert(callbacksCalled[i], $"Future callback {i} should have been called.");
128 int received;
129 received_value[i].Get(out received);
130 Test.AssertEquals(received, current_value);
131 }
132
133 Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
134 Test.AssertRaises<ObjectDisposedException>(future.Cancel);
135 }
136
137 public static void test_then_chain_array()
138 {
139 bool[] callbacksCalled = {false, false, false, false};
140 eina.Value[] received_value = {null, null, null, null};
141
142 FutureCbGenerator genResolvedCb = (int i) => {
143 return (eina.Value value) => {
144 callbacksCalled[i] = true;
145 int x;
146 value.Get(out x);
147 value.Set(x + i);
148 received_value[i] = value;
149 return value;
150 };
151 };
152
153 var cbs = new List<eina.Future.ResolvedCb>();
154 for (int i = 0; i < 4; i++)
155 cbs.Add(genResolvedCb(i));
156
157 efl.ILoop loop = efl.App.GetLoopMain();
158 eina.Promise promise = new eina.Promise();
159 eina.Future future = new eina.Future(promise);
160 future = future.Chain(cbs);
161
162 eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
163 reference_value.Set(0);
164 promise.Resolve(reference_value);
165
166 loop.Iterate();
167
168 int current_value = 0;
169 for (int i = 0; i < 4; i++)
170 {
171 current_value += i;
172 Test.Assert(callbacksCalled[i], $"Future chained callback {i} should have been called.");
173 int received;
174 received_value[i].Get(out received);
175 Test.AssertEquals(received, current_value);
176 }
177
178 Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
179 Test.AssertRaises<ObjectDisposedException>(future.Cancel);
180 }
181
182 public static void test_cancel_after_resolve()
183 {
184 bool callbackCalled = false;
185 eina.Error received_error = eina.Error.NO_ERROR;
186
187 efl.ILoop loop = efl.App.GetLoopMain();
188 eina.Promise promise = new eina.Promise();
189 eina.Future future = new eina.Future(promise);
190
191 future = future.Then((eina.Value value) => {
192 callbackCalled = true;
193 value.Get(out received_error);
194 return value;
195 });
196
197 promise.Reject(eina.Error.EPERM);
198 future.Cancel();
199
200 loop.Iterate();
201
202 Test.Assert(callbackCalled, "Future callback should have been called.");
203 Test.AssertEquals(received_error, eina.Error.ECANCELED);
204
205 Test.AssertRaises<ObjectDisposedException>(() => { promise.Resolve(null); });
206 Test.AssertRaises<ObjectDisposedException>(future.Cancel);
207 }
208
209 public static void test_constructor_with_callback()
210 {
211 bool callbackCalled = false;
212 eina.Value received_value = null;
213
214 efl.ILoop loop = efl.App.GetLoopMain();
215 eina.Promise promise = new eina.Promise();
216#pragma warning disable 0219
217 eina.Future future = new eina.Future(promise,(eina.Value value) => {
218 callbackCalled = true;
219 received_value = value;
220 return value;
221 } );
222#pragma warning restore 0219
223
224 eina.Value reference_value = new eina.Value(eina.ValueType.Int32);
225 reference_value.Set(1984);
226 promise.Resolve(reference_value);
227
228 loop.Iterate();
229
230 Test.Assert(callbackCalled, "Future callback should have been called.");
231 Test.AssertEquals(received_value, reference_value);
232 }
233}
234
235}