summaryrefslogtreecommitdiff
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 = \
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}