cross-language-cpp / djinni-generator

Command-line tool that generates gluecode from a djinni-IDL file
https://djinni.xlcpp.dev/djinni-generator/setup
Apache License 2.0
94 stars 19 forks source link

Add methods to optionally automatically convert lambdas to single-method interfaces (C++) #116

Closed mutagene closed 2 years ago

mutagene commented 3 years ago

Sorry this draft proof-of-concept is a bit messy.

In our code, we frequently call native methods from c++ with single-method interfaces that act as callbacks. As a consequence, we have boilerplate code for bridging c++ lambdas to work with djinni-generated interfaces.

The point of this PR was to see what could be involved with a fairly minimal automatic generation of such bridging "lambda adapters" along with helper methods so that c++ lambdas could be more seamlessly used with djinni interfaces.

a4z commented 3 years ago

I wonder if that boiler plate really requires some djinni code, or if it not can be done with standard C++ by adding some generic code between some djinni generated class and and implementation class.

I try to sketch up some total dummy code, even if I know that is hard to understand if there is now real show case for it

/*
Callback = interface +c {
    callback_method(success: bool);
}

*/
template <typename DjinniCB, typename CallBack> Binder : public DjinniCB {
    CallBack cb;
    public:
    Binder(CallBack cb_): cb{std::move(cb_)}{}

};

class MyClass : Binder<djinni::Callback> {
    use Binder<djinni::DjinniClass> ;

    // what ever is required
};

I know that is a bit vague, but I could imagine that something like that, or some other template magic, could also be a solution ....

If you have a small example project, that shows/uses some boiler plate code, I could have a look at it to see what is possible with onboard C++

mutagene commented 3 years ago

@a4z Thanks for the feedback! Sorry I'm a bit slow getting back to you.

In general terms, the issues are:

  1. It's a bit annoying writing std::make_shared<FooBarInterfaceLambdaAdaptor>(/* lambda */) instead of just writing the lambda that you wanted to use in the first place. It is not to my knowledge possible to implicitly convert the lambda to the correct djinni type.
  2. In order to reduce boilerplate and make things easier to generify, we can come up with conventions like "whatever the interface is, its method should be of the form onValueChanged(const T& value)" - but we can't really enforce that convention in code and it's frustrating if/when you just want to use a lambda in the first place.

I'll see if I can come up with an example that persuasively illustrates the motivation for the PR. If there are some nice tricks to circumvent 1 and/or 2 without changing djinni, that would be great; they aren't obvious to me 💦

finalpatch commented 3 years ago

This is great idea and I have thought about it. IMO the key here is to provide a true lambda type in Djinni IDL, something like:

myMethod(callback: (i32,string)=>i32);

Currently, we have to define each callback as a separate djinni interface, even though they are all extremely similar. This is very repetitive and also causes a lot of code to be generated.

If I understand correctly, your proposal addresses the automatic conversion in platform languages from lambda to djinni interfaces (similar to java8 lambda conversion), but does not eliminate the need to define lots of callback interfaces.

mutagene commented 3 years ago

IMO the key here is to provide a true lambda type in Djinni IDL, something like:

myMethod(callback: (i32,string)=>i32);

This would be ideal, certainly. I started the afternoon thinking I would take a stab at that, and then thought I'd try something a bit simpler to implement which would (in my case) provide pretty much the same benefit. Almost all of our async calls (maybe even all of them?) are from c++ into native code, and in the event there's something from Java8/Kotlin it gets handled by SAM conversion anyway. That leaves ObjC and Swift, I guess.

If I understand correctly, your proposal addresses the automatic conversion in platform languages from lambda to djinni interfaces (similar to java8 lambda conversion), but does not eliminate the need to define lots of callback interfaces.

Yes, this is the approach taken with this PR. It bothers me that it pollutes the abstract djinni-generated c++ classes with the non virtual lambda-conversion methods. But as a c++ client of the djinni-generated API, it's pretty much what I want.