/* * Copyright 2019 by its authors. See AUTHORS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Linq; using System.ComponentModel; using static Eina.EinaNative.PromiseNativeMethods; namespace Eina { namespace EinaNative { [EditorBrowsable(EditorBrowsableState.Never)] 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.ValueNative value); [DllImport(efl.Libs.Eina)] internal static extern void eina_promise_reject(IntPtr scheduler, Eina.Error reason); [DllImport(efl.Libs.CustomExports)] internal static extern void efl_mono_thread_safe_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.ValueNative FutureCb(IntPtr data, Eina.ValueNative 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 /// /// 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. /// /// Since Efl 1.23. /// public class Promise : IDisposable { internal IntPtr Handle; private GCHandle CleanupHandle; /// Delegate for functions that will be called upon a promise cancellation. /// Since EFL 1.23. /// public delegate void CancelCb(); /// /// 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). /// Since EFL 1.23. /// public Promise(CancelCb cancelCb = null) { Efl.Loop loop = Efl.App.AppMain; // Should we be able to pass different schedulers? IntPtr scheduler = efl_loop_future_scheduler_get(loop.NativeHandle); 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(); } /// Dispose this promise, causing its cancellation if it isn't already fulfilled. /// Since EFL 1.23. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Finalizer to be called from the Garbage Collector. /// Since EFL 1.23. /// ~Promise() { Dispose(false); } /// Disposes of this wrapper, rejecting the native promise with . /// Since EFL 1.23. /// /// True if this was called from public method. False if /// called from the C# finalizer. protected virtual void Dispose(bool disposing) { if (Handle != IntPtr.Zero) { if (disposing) { eina_promise_reject(Handle, Eina.Error.ECANCELED); } else { efl_mono_thread_safe_promise_reject(Handle, Eina.Error.ECANCELED); } Handle = IntPtr.Zero; } } private void SanityChecks() { if (this.Handle == IntPtr.Zero) { throw new ObjectDisposedException(GetType().Name); } } /// /// Fulfills a promise with the given value. /// /// This will make all futures attached to it to be called with the given value as payload. /// Since EFL 1.23. /// public void Resolve(Eina.Value value) { SanityChecks(); eina_promise_resolve(this.Handle, value); // Promise will take care of releasing this value correctly. value.ReleaseOwnership(); 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(); } /// /// Rejects a promise. /// /// The future chain attached to this promise will be called with an Eina.Value of type /// and payload . /// Since EFL 1.23. /// public void Reject(Eina.Error reason) { SanityChecks(); eina_promise_reject(this.Handle, reason); this.Handle = IntPtr.Zero; } } /// /// Futures are the structures holding the callbacks to be notified of a promise fullfillment /// or cancellation. /// Since EFL 1.23. /// public class Future { /// /// Callback attached to a future and to be called when resolving/rejecting a promise. /// /// The Eina.Value as argument can come with an 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. /// Since EFL 1.23. /// public delegate Eina.Value ResolvedCb(Eina.Value value); internal IntPtr Handle; /// /// Creates a Future from a native pointer. /// Since EFL 1.23. /// [EditorBrowsable(EditorBrowsableState.Never)] public Future(IntPtr handle) { handle = ThenRaw(handle, (Eina.Value value) => { Handle = IntPtr.Zero; return value; }); Handle = handle; } /// /// 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. /// Since EFL 1.23. /// /// The which rejection or resolution will cause /// the future to trigger. /// The callback to be called when the attached promise resolves. 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); } } /// /// Cancels this future and the chain it belongs to, along with the promise linked against it. /// /// The callbacks will still be called with as payload. The promise cancellation /// callback will also be called if present. /// Since EFL 1.23. /// public void Cancel() { SanityChecks(); eina_future_cancel(this.Handle); } /// /// 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. /// Since EFL 1.23. /// /// The callback to be called when this future is resolved. /// A new future in the chain after registering the callback. 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 = NativeResolvedCbDelegate; GCHandle handle = GCHandle.Alloc(cb); desc.data = GCHandle.ToIntPtr(handle); return eina_future_then_from_desc(previous, desc); } private static FutureCb NativeResolvedCbDelegate = new FutureCb(NativeResolvedCb); private static Eina.ValueNative NativeResolvedCb(IntPtr data, Eina.ValueNative value, IntPtr dead_future) { GCHandle handle = GCHandle.FromIntPtr(data); ResolvedCb cb = handle.Target as ResolvedCb; if (cb != null) { Eina.Value managedValue = cb(value); // Both `value` and `managedValue` will point to the same internal value data. // Avoid C# wrapper invalidating the underlying C Eina_Value as the eina_future.c // code will release it. value = managedValue.GetNative(); managedValue.ReleaseOwnership(); } else { Eina.Log.Warning("Failed to get future callback."); } handle.Free(); return value; } /// /// 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. /// Since EFL 1.23. /// /// An enumerable with the callbacks to be chained together. /// The future representing the chain. public Future Chain(IEnumerable cbs) { SanityChecks(); System.Collections.Generic.IList 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 = NativeResolvedCbDelegate; 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)); } } /// Custom marshaler to convert between managed and native . /// Internal usage in generated code. [EditorBrowsable(EditorBrowsableState.Never)] public class FutureMarshaler : ICustomMarshaler { ///Wrap the native future with a managed wrapper. ///Handle to the native future. ///An wrapping the native future. public object MarshalNativeToManaged(IntPtr pNativeData) { return new Future(pNativeData); } ///Extracts the native future from a managed wrapper. ///The managed wrapper. If it is not an , the value returned ///is . ///A pointing to the native future. public IntPtr MarshalManagedToNative(object managedObj) { Future f = managedObj as Future; if (f == null) { return IntPtr.Zero; } return f.Handle; } ///Not implemented. The code receiving the native data is in charge of releasing it. ///The native pointer to be released. public void CleanUpNativeData(IntPtr pNativeData) { } ///Not implemented. The runtime takes care of releasing it. ///The managed object to be cleaned. public void CleanUpManagedData(object managedObj) { } ///Size of the native data size returned ///The size of the data. public int GetNativeDataSize() { return -1; } ///Gets an instance of this marshaller. ///A name that could be used to customize the returned marshaller. Currently not used. ///The instance that will marshall the data. public static ICustomMarshaler GetInstance(string cookie) { if (marshaler == null) { marshaler = new FutureMarshaler(); } return marshaler; } private static FutureMarshaler marshaler; } } // namespace eina