-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[jnigen] Enable "listener" callbacks #1568
Comments
Tricky. Then a flag would be The flag would only exist for methods that are |
Only issue with a bool is that we're adding more Fwiw, the way ffigen handles this is by having 2 implementation methods, one which uses blocking callbacks for everything, and the other which uses listener callbacks for all the void methods. If the user wants more granularity than that, they can implement individual methods either as listeners or as blocking callbacks using the /// MyProtocol
abstract final class MyProtocol {
/// Builds an object that implements the MyProtocol protocol. To implement
/// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
static objc.ObjCObjectBase implement(
{int Function(SomeStruct)? optionalMethod_,
void Function(int)? voidMethod_,
required objc.NSString Function(objc.NSString, double)
instanceMethod_withDouble_}) {
final builder = objc.ObjCProtocolBuilder();
MyProtocol.optionalMethod_.implement(builder, optionalMethod_);
MyProtocol.voidMethod_.implement(builder, voidMethod_);
MyProtocol.instanceMethod_withDouble_
.implement(builder, instanceMethod_withDouble_);
return builder.build();
}
/// Builds an object that implements the MyProtocol protocol. To implement
/// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. All
/// methods that can be implemented as listeners will be.
static objc.ObjCObjectBase implementAsListener(
{int Function(SomeStruct)? optionalMethod_,
void Function(int)? voidMethod_,
required objc.NSString Function(objc.NSString, double)
instanceMethod_withDouble_}) {
final builder = objc.ObjCProtocolBuilder();
MyProtocol.optionalMethod_.implement(builder, optionalMethod_);
MyProtocol.voidMethod_.implementAsListener(builder, voidMethod_);
MyProtocol.instanceMethod_withDouble_
.implement(builder, instanceMethod_withDouble_);
return builder.build();
}
static void addToBuilder(...
static void addToBuilderAsListener(...
/// optionalMethod:
static final optionalMethod_ =
objc.ObjCProtocolMethod<int Function(SomeStruct)>(...);
/// voidMethod:
static final voidMethod_ =
objc.ObjCProtocolListenableMethod<void Function(int)>(...);
/// instanceMethod:withDouble:
static final instanceMethod_withDouble_ =
objc.ObjCProtocolMethod<objc.NSString Function(objc.NSString, double)>(...);
} |
If we add "
This is convenient. The slight issue I have with it is the fact that it implies that everything is a listener. Of course we know that the functions returning a value cannot be listeners but the user might not. @lrhn What do you think about using |
I considered doing this for ObjC, but there are some details that make this not quite work (eg the block's invoke function pointer has a different signature to the user's function). Not sure if you'd have similar issues in jnigen. |
Yes, for starters every java object is a void pointer instead. Still, not sure if the enum value will be the best. Maybe it's best if we generate wrappers with multiple constructors like NativeCallable. And maybe factor out any potential compiler special cases for things like temporary isolate callables to be available anywhere with some pragma so we can also use them in the generated wrappers. |
I've already implemented the This is the check it uses to see if the (($impl is _$MyRunnable &&
($impl as _$MyRunnable)._run is _$core.Future<void>
Function() &&
($impl as _$MyRunnable)._run is! _$core.Never Function()) ||
($impl.run is _$core.Future<void> Function() &&
$impl.run is! _$core.Never Function())) // If the implementation is using the callback passing style, look at the
// actual passed callback instead of the wrapper function. The wrapper is
// always going to return `FutureOr<void>`.
//
// If the callback simply throws its return type will be `Never`. As any
// function `R <F>` is a subtype of `Never <F>`, we should have a special
// case for it. It's not possible to throw from a listener function, so
// functions that only throw and hence have a `Never` return type, will be
// considered to be sync. Also I could have used |
Actually FutureOr<void> Function() f = () { return Future<void>.delayed(Duration(seconds: 1)); }; but this isn't: void Function() f = () { return Future<void>.delayed(Duration(seconds: 1)); }; While this is: void Function() f = () async { return Future<void>.delayed(Duration(seconds: 1)); }; So it really makes it dependent on the existence of |
The problems here come from inference, but inference is hard to avoid with function expressions, since you cannot write a return type. void Function() f = () { return Future<void>.delayed(Duration(seconds: 1)); }; is an error is that the function return type is inferred to be I generally try to avoid deducing anything from a function return type because it is so hard to control the return type. And you do need to consider a function type like If I understand this correctly, after trying to swap it back in, the problem is:
We can't know if a Dart For a If the context type is (If we want to save space, use shorthands and shorter names. var javaComputeRunnable = Runnable.of(run: block((x) => compute(x)));
var javaAsyncComputeeRunnable = Runnable.of(run: blockAwait((x) async => await computeMore(x)));
var javaCallbackRunnable = Runnable.of(run: schedule((x) => laterAction(x))); where (If the behavior is chosen at runtime, based on the So JRunnable<T Function()> block<T>(T Function() dartFunction) => ...
JRunnable<T Function()> blockAwait<T>(Future<T> Function() dartFunction) => ...
JRunnable<void Function()> schedule(void Function() dartFunction) => ... and |
The functions can have arguments as well, so they should look more like: JCallable<T> block<T extends Function>(T dartFunction) => ... Which doesn't allow us to limit the return types.
I think I'll use the initial idea to use an extra enum value per void function then. Shorter and no magic. |
Continuing in the path to get "interface implementation" feature out of experimental in JNIgen.
JNIgen currently generates an
implement
factory on interface types like so:If we pass
Runnable.implement(...)
to Java and it tries to call.run
method, it will block until Dart actually runs it. However sometimes we don't have to block when the callback is intended to be a listener.Of course, making a method into a "listener" is only possible to do if the return type of the said method is
void
(or maybe handling other cases like Java'sFuture
).We could add an enum parameter per (viable) method to communicate which methods have to be blocking vs. listener:
enum JCallMode { blocking, listener }
.Alternatively, a la
NativeCallable
, we could create aJCallable<T extends Function>
and instead of requiring a simple function, require aJCallable
. ThenJCallable
can have two factories:.listener
, and.blocking
.The issue here is that a simple callback implementation will be quite verbose:
And non-void returning methods cannot be
JCallable.listener
anyways so it's unnecessary in those cases.@liamappelbe @dcharkes @mkustermann @lrhn Which syntax do you prefer? Or maybe something different.
The text was updated successfully, but these errors were encountered: