efl_mono: Start generating eina future in eolian_mono.

Summary:
Besides the normal methods returning Futures, we now generate
a wrapper with the "Async" suffix. This wrapper returns a
Systems.Threading.Tasks.Task which can be awaited on and reflect the
status of the Future.

When an eina.Future fails with ECANCELED, TaskCanceledException is
raised in the Task. Otherwise, an efl.FutureException(eina.Error) is
raised.
Depends on D6174

Reviewers: felipealmeida

Reviewed By: felipealmeida

Subscribers: cedric, zmike

Tags: #efl

Differential Revision: https://phab.enlightenment.org/D6175
This commit is contained in:
Lauro Moura 2018-05-07 19:22:59 -03:00
parent fff0c86d99
commit c8fbc31ee2
11 changed files with 535 additions and 12 deletions

View File

@ -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 \

View File

@ -0,0 +1,145 @@
#ifndef EOLIAN_MONO_ASYNC_FUNCTION_DEFINITION_HH
#define EOLIAN_MONO_ASYNC_FUNCTION_DEFINITION_HH
#include <Eina.hh>
#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<typename T>
bool operator()(T const&) const
{
return false;
}
};
struct async_function_declaration_generator
{
template<typename OutputIterator, typename Context>
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<eina.Value> " << 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 <typename OutputIterator, typename Context>
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<std::string> 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<eina.Value> " << 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<int, 1> {};
template <>
struct attributes_needed< ::eolian_mono::async_function_definition_generator> : std::integral_constant<int, 1> {};
template <>
struct attributes_needed< ::eolian_mono::async_function_definition_parameterized> : std::integral_constant<int, 1> {};
}
} } }
#endif

View File

@ -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;
}

View File

@ -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;
}
};

View File

@ -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;

View File

@ -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, {}});

View File

@ -168,7 +168,7 @@ public class Future
/// </summary>
public delegate eina.Value ResolvedCb(eina.Value value);
private IntPtr Handle;
public IntPtr Handle { get; internal set; }
/// <summary>
/// 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

View File

@ -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<eina.Value> 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<eina.Value>();
// 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
/// <summary>General exception for errors inside the binding.</summary>
public class EflException : Exception
{
/// <summary>Create a new EflException with the given.
public EflException(string message) : base(message)
{
}
}
/// <summary>Exception to be raised when a Task fails due to a failed eina.Future.</summary>
public class FutureException : EflException
{
/// <summary>The error code returned by the failed eina.Future.</summary>
public eina.Error Error { get; private set; }
/// <summary>Construct a new exception from the eina.Error stored in the given eina.Value.</summary>
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

View File

@ -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<eina.Value> 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<eina.Value> 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<eina.Value> 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<eina.Value> 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.");
}
}
}

View File

@ -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"

View File

@ -1615,6 +1615,24 @@ class Test.Testing (Efl.Object, Efl.Part) {
prop: int;
}
}
/* Futures */
get_future {
return: future<any_value_ptr>;
}
fulfill_promise {
params {
@in data: int;
}
}
reject_promise {
params {
@in error: Eina.Error;
}
}
}
implements {
class.constructor;