diff --git a/src/Makefile_Efl_Mono.am b/src/Makefile_Efl_Mono.am index 64b4a7cbf2..a4376c0761 100644 --- a/src/Makefile_Efl_Mono.am +++ b/src/Makefile_Efl_Mono.am @@ -68,6 +68,7 @@ bin_eolian_mono_eolian_mono_SOURCES = \ bin/eolian_mono/eolian/mono/documentation.hh \ bin/eolian_mono/eolian/mono/type.hh \ bin/eolian_mono/eolian/mono/marshall_annotation.hh \ + bin/eolian_mono/eolian/mono/async_function_definition.hh \ bin/eolian_mono/eolian/mono/function_pointer.hh \ bin/eolian_mono/eolian/mono/function_definition.hh \ bin/eolian_mono/eolian/mono/name_helpers.hh \ @@ -434,6 +435,7 @@ tests_efl_mono_efl_mono_SOURCES = \ tests/efl_mono/Eina.cs \ tests/efl_mono/Eldbus.cs \ tests/efl_mono/Eo.cs \ + tests/efl_mono/EoPromises.cs \ tests/efl_mono/Errors.cs \ tests/efl_mono/Evas.cs \ tests/efl_mono/Events.cs \ diff --git a/src/bin/eolian_mono/eolian/mono/async_function_definition.hh b/src/bin/eolian_mono/eolian/mono/async_function_definition.hh new file mode 100644 index 0000000000..3030c74e1e --- /dev/null +++ b/src/bin/eolian_mono/eolian/mono/async_function_definition.hh @@ -0,0 +1,145 @@ +#ifndef EOLIAN_MONO_ASYNC_FUNCTION_DEFINITION_HH +#define EOLIAN_MONO_ASYNC_FUNCTION_DEFINITION_HH + +#include + +#include "grammar/generator.hpp" +#include "grammar/klass_def.hpp" + +#include "grammar/indentation.hpp" +#include "grammar/list.hpp" +#include "grammar/alternative.hpp" +#include "grammar/attribute_reorder.hpp" +#include "logging.hh" +#include "type.hh" +#include "name_helpers.hh" +#include "helpers.hh" +#include "function_helpers.hh" +#include "marshall_type.hh" +#include "parameter.hh" +#include "documentation.hh" +#include "using_decl.hh" +#include "generation_contexts.hh" +#include "blacklist.hh" + +namespace eolian_mono { + +struct is_future +{ + typedef is_future visitor_type; + typedef bool result_type; + + bool operator()(grammar::attributes::complex_type_def const& c) const + { + return c.outer.base_type == "future"; + } + + template + bool operator()(T const&) const + { + return false; + } +}; + +struct async_function_declaration_generator +{ + template + bool generate(OutputIterator sink, attributes::function_def const& f, Context const& context) const + { + if (f.is_static) + return true; + if (blacklist::is_function_blacklisted(f.c_name)) + return true; + if (!f.return_type.original_type.visit(is_future{})) + return true; + + if (!as_generator( + scope_tab << "System.Threading.Tasks.Task " << name_helpers::managed_async_method_name(f) << "(" << *(parameter << ",") << + " System.Threading.CancellationToken token=default(System.Threading.CancellationToken));\n" + ).generate(sink, f.parameters, context)) + return false; + + return true; + } +} const async_function_declaration {}; + +struct async_function_definition_generator +{ + async_function_definition_generator(bool do_super = false) + : do_super(do_super) + {} + + template + bool generate(OutputIterator sink, attributes::function_def const& f, Context const& context) const + { + EINA_CXX_DOM_LOG_DBG(eolian_mono::domain) << "async_function_definition_generator: " << f.c_name; + + if(do_super && f.is_static) // Static methods goes only on Concrete classes. + return true; + if(blacklist::is_function_blacklisted(f.c_name)) + return true; + if(!f.return_type.original_type.visit(is_future{})) + return true; + + auto parameter_forwarding = [](attributes::parameter_def const& param) { + return direction_modifier(param) + " " + name_helpers::escape_keyword(param.param_name); + }; + std::vector param_forwarding; + + std::transform(f.parameters.begin(), f.parameters.end(), std::back_inserter(param_forwarding), parameter_forwarding); + + if(!as_generator( + scope_tab << "public System.Threading.Tasks.Task " << name_helpers::managed_async_method_name(f) << "(" << *(parameter << ",") << " System.Threading.CancellationToken token)\n" + << scope_tab << "{\n" + << scope_tab << scope_tab << "eina.Future future = " << name_helpers::managed_method_name(f) << "(" << (string % ",") << ");\n" + << scope_tab << scope_tab << "return efl.eo.Globals.WrapAsync(future, token);\n" + << scope_tab << "}\n" + ).generate(sink, std::make_tuple(f.parameters, param_forwarding), context)) + return false; + return true; + } + + bool do_super; +}; + +struct async_function_definition_parameterized +{ + async_function_definition_generator operator()(bool do_super=false) const + { + return {do_super}; + } +} const async_function_definition; +async_function_definition_generator as_generator(async_function_definition_parameterized) +{ + return {}; +} + +} + +namespace efl { namespace eolian { namespace grammar { + +template <> +struct is_eager_generator< ::eolian_mono::async_function_declaration_generator> : std::true_type {}; +template <> +struct is_generator< ::eolian_mono::async_function_declaration_generator> : std::true_type {}; + +template <> +struct is_eager_generator< ::eolian_mono::async_function_definition_generator> : std::true_type {}; +template <> +struct is_generator< ::eolian_mono::async_function_definition_generator> : std::true_type {}; +template <> +struct is_generator< ::eolian_mono::async_function_definition_parameterized> : std::true_type {}; + +namespace type_traits { +template <> +struct attributes_needed< ::eolian_mono::async_function_declaration_generator> : std::integral_constant {}; + +template <> +struct attributes_needed< ::eolian_mono::async_function_definition_generator> : std::integral_constant {}; +template <> +struct attributes_needed< ::eolian_mono::async_function_definition_parameterized> : std::integral_constant {}; +} + +} } } + +#endif diff --git a/src/bin/eolian_mono/eolian/mono/klass.hh b/src/bin/eolian_mono/eolian/mono/klass.hh index b2d2d411a6..e85654756d 100644 --- a/src/bin/eolian_mono/eolian/mono/klass.hh +++ b/src/bin/eolian_mono/eolian/mono/klass.hh @@ -11,6 +11,7 @@ #include "grammar/alternative.hpp" #include "type.hh" #include "name_helpers.hh" +#include "async_function_definition.hh" #include "function_definition.hh" #include "function_registration.hh" #include "function_declaration.hh" @@ -144,6 +145,9 @@ struct klass if(!as_generator(*(scope_tab << function_declaration)).generate(sink, cls.functions, iface_cxt)) return false; + if(!as_generator(*(scope_tab << async_function_declaration)).generate(sink, cls.functions, iface_cxt)) + return false; + if(!as_generator(*(event_declaration)).generate(sink, cls.events, iface_cxt)) return false; @@ -251,6 +255,10 @@ struct klass if(!as_generator(*(function_definition)) .generate(sink, methods, concrete_cxt)) return false; + // Async wrappers + if(!as_generator(*(async_function_definition)).generate(sink, methods, concrete_cxt)) + return false; + if(!as_generator(*(event_argument_wrapper)).generate(sink, cls.events, context)) return false; @@ -354,6 +362,10 @@ struct klass if(!as_generator(*(function_definition(true))) .generate(sink, methods, inherit_cxt)) return false; + // Async wrappers + if(!as_generator(*(async_function_definition(true))).generate(sink, methods, inherit_cxt)) + return false; + if(!as_generator("}\n").generate(sink, attributes::unused, inherit_cxt)) return false; } diff --git a/src/bin/eolian_mono/eolian/mono/marshall_annotation.hh b/src/bin/eolian_mono/eolian/mono/marshall_annotation.hh index 576ccdb4c9..a2426569ab 100644 --- a/src/bin/eolian_mono/eolian/mono/marshall_annotation.hh +++ b/src/bin/eolian_mono/eolian/mono/marshall_annotation.hh @@ -149,8 +149,13 @@ struct marshall_annotation_visitor_generate << string << ", efl.eo." << (klass_name.base_qualifier & qualifier_info::is_own ? "OwnTag" : "NonOwnTag") << ">))]" ).generate(sink, name_helpers::klass_full_concrete_name(klass_name), *context); } - bool operator()(attributes::complex_type_def const&) const + bool operator()(attributes::complex_type_def const& c) const { + if (c.outer.base_type == "future") + { + std::string prefix = is_return ? "return: " : ""; + return as_generator("[" << prefix << "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(eina.FutureMarshaler))]").generate(sink, nullptr, *context); + } return true; } }; @@ -254,8 +259,13 @@ struct marshall_native_annotation_visitor_generate << string << ", efl.eo." << (klass_name.base_qualifier & qualifier_info::is_own ? "OwnTag" : "NonOwnTag") << ">))]" ).generate(sink, name_helpers::klass_full_concrete_name(klass_name), *context); } - bool operator()(attributes::complex_type_def const&) const + bool operator()(attributes::complex_type_def const& c) const { + if (c.outer.base_type == "future") + { + std::string prefix = is_return ? "return: " : ""; + return as_generator("[" << prefix << "MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(eina.FutureMarshaler))]").generate(sink, nullptr, *context); + } return true; } }; diff --git a/src/bin/eolian_mono/eolian/mono/name_helpers.hh b/src/bin/eolian_mono/eolian/mono/name_helpers.hh index 912909b4a8..b1a03056ef 100644 --- a/src/bin/eolian_mono/eolian/mono/name_helpers.hh +++ b/src/bin/eolian_mono/eolian/mono/name_helpers.hh @@ -192,6 +192,10 @@ inline std::string alias_full_eolian_name(attributes::alias_def const& alias) return join_namespaces(alias.namespaces, '.') + alias.eolian_name; } +inline std::string managed_async_method_name(attributes::function_def const& f) +{ + return managed_method_name(f) + "Async"; +} inline std::string function_ptr_full_eolian_name(attributes::function_def const& func) { return join_namespaces(func.namespaces, '.') + func.name; diff --git a/src/bin/eolian_mono/eolian/mono/type_impl.hh b/src/bin/eolian_mono/eolian/mono/type_impl.hh index ee44460169..1028a76ad9 100644 --- a/src/bin/eolian_mono/eolian/mono/type_impl.hh +++ b/src/bin/eolian_mono/eolian/mono/type_impl.hh @@ -282,16 +282,10 @@ struct visitor_generate complex_type_def c = complex; c.outer.base_type = "eina.Hash"; return c; - }} - , {"promise", nullptr, nullptr, [&] - { - return replace_outer - (complex, regular_type_def{" ::efl::promise", complex.outer.base_qualifier, {}}); - } - } + }} , {"future", nullptr, nullptr, [&] { - (*this)(regular_type_def{" int", complex.outer.base_qualifier, {}}); + (*this)(regular_type_def{" eina.Future", complex.outer.base_qualifier, {}}); return attributes::type_def::variant_type(); // return replace_outer // (complex, regular_type_def{" ::efl::shared_future", complex.outer.base_qualifier, {}}); diff --git a/src/bindings/mono/eina_mono/eina_promises.cs b/src/bindings/mono/eina_mono/eina_promises.cs index 46fe800869..c280b400f3 100644 --- a/src/bindings/mono/eina_mono/eina_promises.cs +++ b/src/bindings/mono/eina_mono/eina_promises.cs @@ -168,7 +168,7 @@ public class Future /// public delegate eina.Value ResolvedCb(eina.Value value); - private IntPtr Handle; + public IntPtr Handle { get; internal set; } /// /// Creates a Future from a native pointer. @@ -295,4 +295,40 @@ public class Future } } +public class FutureMarshaler : ICustomMarshaler +{ + + public object MarshalNativeToManaged(IntPtr pNativeData) + { + return new Future(pNativeData); + } + + public IntPtr MarshalManagedToNative(object managedObj) + { + Future f = managedObj as Future; + if (f == null) + return IntPtr.Zero; + return f.Handle; + } + + public void CleanUpNativeData(IntPtr pNativeData) { } + + public void CleanUpManagedData(object managedObj) { } + + public int GetNativeDataSize() + { + return -1; + } + + public static ICustomMarshaler GetInstance(string cookie) { + if (marshaler == null) + { + marshaler = new FutureMarshaler(); + } + return marshaler; + } + + private static FutureMarshaler marshaler; +} + } // namespace eina diff --git a/src/bindings/mono/eo_mono/iwrapper.cs b/src/bindings/mono/eo_mono/iwrapper.cs index 569ee58d08..bb7a45b291 100644 --- a/src/bindings/mono/eo_mono/iwrapper.cs +++ b/src/bindings/mono/eo_mono/iwrapper.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; using static eina.NativeCustomExportFunctions; @@ -179,7 +180,51 @@ public class Globals { GCHandle handle = GCHandle.FromIntPtr(ptr); handle.Free(); } -} + + public static System.Threading.Tasks.Task WrapAsync(eina.Future future, CancellationToken token) + { + // Creates a task that will wait for SetResult for completion. + // TaskCompletionSource is used to create tasks for 'external' Task sources. + var tcs = new System.Threading.Tasks.TaskCompletionSource(); + + // Flag to be passed to the cancell callback + bool fulfilled = false; + + future.Then((eina.Value received) => { + lock (future) + { + // Convert an failed Future to a failed Task. + if (received.GetValueType() == eina.ValueType.Error) + { + eina.Error err; + received.Get(out err); + if (err == eina.Error.ECANCELED) + tcs.SetCanceled(); + else + tcs.TrySetException(new efl.FutureException(received)); + } + else + { + // Will mark the returned task below as completed. + tcs.SetResult(received); + } + fulfilled = true; + return received; + } + }); + // Callback to be called when the token is cancelled. + token.Register(() => { + lock (future) + { + // Will trigger the Then callback above with an eina.Error + if (!fulfilled) + future.Cancel(); + } + }); + + return tcs.Task; + } +} // Globals public static class Config { @@ -449,11 +494,30 @@ public class StrbufKeepOwnershipMarshaler: ICustomMarshaler { } // namespace eo +/// General exception for errors inside the binding. public class EflException : Exception { + /// Create a new EflException with the given. public EflException(string message) : base(message) { } } +/// Exception to be raised when a Task fails due to a failed eina.Future. +public class FutureException : EflException +{ + /// The error code returned by the failed eina.Future. + public eina.Error Error { get; private set; } + + /// Construct a new exception from the eina.Error stored in the given eina.Value. + public FutureException(eina.Value value) : base("Future failed.") + { + if (value.GetValueType() != eina.ValueType.Error) + throw new ArgumentException("FutureException must receive an eina.Value with eina.Error."); + eina.Error err; + value.Get(out err); + Error = err; + } +} + } // namespace efl diff --git a/src/tests/efl_mono/EoPromises.cs b/src/tests/efl_mono/EoPromises.cs new file mode 100644 index 0000000000..0c7c2e95e8 --- /dev/null +++ b/src/tests/efl_mono/EoPromises.cs @@ -0,0 +1,195 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace TestSuite +{ + +class TestEoPromises +{ + public static void test_simple_task_run() + { + efl.ILoop loop = efl.App.GetLoopMain(); + eina.Future future = loop.Idle(); + + bool callbackCalled = false; + int ret_code = 1992; + + future.Then((eina.Value value) => { + callbackCalled = true; + eina.Value v = new eina.Value(eina.ValueType.Int32); + v.Set(ret_code); + loop.Quit(v); + return value; + }); + eina.Value ret_value = loop.Begin(); + + Test.Assert(callbackCalled, "Future loop callback must have been called."); + + Test.AssertEquals(ret_value.GetValueType(), eina.ValueType.Int32); + + int ret_from_value; + Test.Assert(ret_value.Get(out ret_from_value)); + Test.AssertEquals(ret_from_value, ret_code); + + } + + public static void test_object_promise() + { + efl.ILoop loop = efl.App.GetLoopMain(); + test.Testing obj = new test.Testing(); + + eina.Future future = obj.GetFuture(); + + bool callbackCalled = false; + int receivedValue = -1; + int sentValue = 1984; + future.Then((eina.Value value) => { + callbackCalled = true; + Test.AssertEquals(value.GetValueType(), eina.ValueType.Int32); + value.Get(out receivedValue); + + return value; + }); + + obj.FulfillPromise(sentValue); + + loop.Iterate(); + Test.Assert(callbackCalled, "Future callback must have been called."); + Test.AssertEquals(receivedValue, sentValue); + } + + public static void test_object_promise_cancel() + { + efl.ILoop loop = efl.App.GetLoopMain(); + test.Testing obj = new test.Testing(); + + eina.Future future = obj.GetFuture(); + + bool callbackCalled = false; + eina.Error receivedError = -1; + eina.Error sentError = 120; + future.Then((eina.Value value) => { + callbackCalled = true; + Test.AssertEquals(value.GetValueType(), eina.ValueType.Error); + value.Get(out receivedError); + + return value; + }); + + obj.RejectPromise(sentError); + + loop.Iterate(); + Test.Assert(callbackCalled, "Future callback must have been called."); + Test.AssertEquals(receivedError, sentError); + } + +} + +class LoopConsumer +{ + public static async Task Consume(efl.ILoop loop) + { + Task task = loop.IdleAsync(); + eina.Value v = await task; + loop.Quit(v); + } +} + +class TestLoopEoAsyncMethods +{ + public static void test_simple_async() + { + efl.ILoop loop = efl.App.GetLoopMain(); + Task t = LoopConsumer.Consume(loop); + + loop.Begin(); + Test.Assert(t.Wait(1000), "Task should have been completed in time."); + } +} + +class TestEoAsyncMethods +{ + + public static void test_async_fulfill() + { + efl.ILoop loop = efl.App.GetLoopMain(); + test.ITesting obj = new test.Testing(); + + Task task = obj.GetFutureAsync(); + + int sentValue = 1337; + + obj.FulfillPromise(sentValue); + loop.Iterate(); + + eina.Value v = task.Result; + Test.AssertEquals(v.GetValueType(), eina.ValueType.Int32); + + int receivedValue; + v.Get(out receivedValue); + Test.AssertEquals(receivedValue, sentValue); + } + + public static void test_async_cancel() + { + efl.ILoop loop = efl.App.GetLoopMain(); + test.ITesting obj = new test.Testing(); + + CancellationTokenSource cancelSrc = new CancellationTokenSource(); + Task task = obj.GetFutureAsync(cancelSrc.Token); + + cancelSrc.Cancel(); + loop.Iterate(); + + bool raised = false; + try + { + eina.Value v = task.Result; + } + catch (AggregateException ae) + { + raised = true; + ae.Handle((x) => + { + Test.Assert(x is TaskCanceledException, "AggregateException must have been TaskCanceledException"); + return true; + }); + } + + Test.Assert(raised, "AggregateException must have been raised."); + } + + public static void test_async_reject() + { + efl.ILoop loop = efl.App.GetLoopMain(); + test.ITesting obj = new test.Testing(); + + Task task = obj.GetFutureAsync(); + + eina.Error sentError = 1337; + obj.RejectPromise(sentError); + + loop.Iterate(); + + bool raised = false; + try + { + eina.Value v = task.Result; + } + catch (AggregateException ae) + { + raised = true; + ae.Handle((x) => + { + Test.Assert(x is efl.FutureException, "AggregateException must have been TaskCanceledException"); + efl.FutureException ex = x as efl.FutureException; + Test.AssertEquals(ex.Error, sentError); + return true; + }); + } + + Test.Assert(raised, "AggregateException must have been raised."); + } +} +} diff --git a/src/tests/efl_mono/libefl_mono_native_test.c b/src/tests/efl_mono/libefl_mono_native_test.c index 1c8f0885d5..085188bcde 100644 --- a/src/tests/efl_mono/libefl_mono_native_test.c +++ b/src/tests/efl_mono/libefl_mono_native_test.c @@ -51,6 +51,7 @@ typedef struct Test_Testing_Data int stored_int; Eo *part1; Eo *part2; + Eina_Promise *promise; } Test_Testing_Data; typedef struct Test_Numberwrapper_Data @@ -3799,6 +3800,48 @@ void _test_testing_klass_prop_set(Eo *klass, EINA_UNUSED void *pd, int value) _test_testing_klass_prop = value; } +static void _promise_cancelled(void *data, EINA_UNUSED const Eina_Promise *p) +{ + Test_Testing_Data *pd = data; + pd->promise = NULL; +} + +Eina_Future* _test_testing_get_future(EINA_UNUSED Eo *obj, Test_Testing_Data *pd) +{ + if (pd->promise == NULL) + { + Eo *loop = efl_app_loop_main_get(EFL_APP_CLASS); + Eina_Future_Scheduler *scheduler = efl_loop_future_scheduler_get(loop); + pd->promise = eina_promise_new(scheduler, _promise_cancelled, pd); + } + return eina_future_new(pd->promise); +} + +void _test_testing_fulfill_promise(Eo *obj, Test_Testing_Data *pd, int data) +{ + if (pd->promise == NULL) + { + EINA_LOG_ERR("Can't fulfill an object without a valid promise."); + return; + } + Eina_Value v; + eina_value_setup(&v, EINA_VALUE_TYPE_INT); + eina_value_set(&v, data); + eina_promise_resolve(pd->promise, v); +} + +void _test_testing_reject_promise(Eo *obj, Test_Testing_Data *pd, Eina_Error err) +{ + if (pd->promise == NULL) + { + EINA_LOG_ERR("Can't fulfill an object without a valid promise."); + return; + } + + eina_promise_reject(pd->promise, err); +} + + #include "test_testing.eo.c" #include "test_numberwrapper.eo.c" diff --git a/src/tests/efl_mono/test_testing.eo b/src/tests/efl_mono/test_testing.eo index 3c15652ba5..d19024f435 100644 --- a/src/tests/efl_mono/test_testing.eo +++ b/src/tests/efl_mono/test_testing.eo @@ -1615,6 +1615,24 @@ class Test.Testing (Efl.Object, Efl.Part) { prop: int; } } + + /* Futures */ + + get_future { + return: future; + } + + fulfill_promise { + params { + @in data: int; + } + } + + reject_promise { + params { + @in error: Eina.Error; + } + } } implements { class.constructor;