-
Notifications
You must be signed in to change notification settings - Fork 6k
Add method channel support to Linux shell #18118
Add method channel support to Linux shell #18118
Conversation
|
This PR doesn't have the tests completed, as I haven't worked out how to build and run them yet. Please let me know what I should do here! Everything else should be mostly there, so I figured it's worth reviewing now. Example code of using this: FlMethodChannel *method_channel;
static void
method_cb (FlMethodChannel *channel, const gchar *method, FlValue *args, FlBinaryMessengerResponseHandle *response_handle, gpointer user_data)
{
g_autoptr(GError) error = NULL;
g_autoptr(FlValue) result = fl_value_string_new ("test-response-from-gtk");
if (!fl_method_channel_respond (channel, response_handle, result, &error))
g_printerr ("Failed to send method response: %s\n", error->message);
}
static void
method_response_cb (GObject *object, GAsyncResult *result, gpointer user_data)
{
g_autofree gchar *error_code = NULL;
g_autofree gchar *error_message = NULL;
g_autoptr(FlValue) response = NULL;
g_autoptr(GError) error = NULL;
if (!fl_method_channel_invoke_finish (FL_METHOD_CHANNEL (object),
result,
&error_code,
&error_message,
&response,
&error)) {
g_printerr ("Failed to send platform message: %s\n", error->message);
return;
}
if (error_code == NULL)
handle_success (response);
else
handle_failure (error_code, error_message, response);
}
void
some_external_event (gpointer user_data)
{
fl_method_channel_invoke (method_channel, "Test.test", NULL, NULL, method_response_cb, NULL);
}
int
main (int argc, char **argv)
{
...
g_autoptr(FlDartProject) project = fl_dart_project_new (".");
view = fl_view_new (project);
method_channel = fl_method_channel_new (fl_engine_get_binary_messenger (fl_view_get_engine (view)),
"example.com/gtk-test-method",
FL_METHOD_CODEC (fl_standard_method_codec_new ()));
fl_method_channel_set_callback (method_channel, method_cb, NULL);
...
}Notes:
|
97d5bcc to
a7f1681
Compare
|
Neat!
Here is an example test runner you can look at: Line 231 in 2622cb9
You can build it with I read through your example usage and I have a nit, the signature for the method channel callback has a
'Json' is the proper naming according to the Google C++ style guide. Why did you chose to use This probably deserved a design doc to help you avoid shifting around a big PR. It should have an issue associated with it, too. |
6ecf0b3 to
b21d5ae
Compare
|
Thanks @gaaclarke for the review. I didn't use
The issue for this feature is flutter/flutter#54859. |
|
I haven't had time to review yet, but some initial comments on some of the high-level points before I do:
Another option would be to see if we could use the library suggested in flutter/flutter#54693. If so, this could be the first stage in a migration away from rapidjson.
The other side of the system channels is under our control, but the data isn't always. For instance, one of the things we need to implement for alpha using this is the copy/paste channel, which will contain arbitrary strings, so if a custom parser has a bug in string handling that's a serious issue.
Obj-C naming conventions use all caps for acronyms, C++ Google style doesn't. Since these APIs are mostly following GLib conventions, we should use theirs (if there is one; if not we should default to the C++ guide).
We should use whatever the standard generic boxed type is. Codecs are designed to allow arbitrary implementations, which means I should be able to make a codec that handles arbitrary types of my own creation. That does mean that unhandled types are a runtime error rather than a compile-time error, but that's how the ObjC and Java implementations work (see here for instance). The only reason the C++ implementation doesn't do that is that there's no generic root type in C++, so it's template-based instead (and there's still an open bug about the fact that the standard codec, which is supposed to allow extensions, currently doesn't in the C++ implementation precisely because it uses a custom type like FlValue.)
I had some of those issues with the C++ code as well; IIRC I just ended up picking the names used on the Dart side when there was doubt, but I wasn't entirely convinced that was the best option. If there are conventions in GLib, I would say to use those preferentially (e.g., since there's a
Standard is the only other codec we need for the foreseeable future. I have not yet seen a case in my desktop work where we needed the string codec. |
b21d5ae to
db995c5
Compare
|
I split the JSON codecs into #18158 which should make this PR more reviewable. |
db995c5 to
dacd4c8
Compare
dacd4c8 to
7c31124
Compare
|
This is how I'd expect an API user would be able to read/write custom data types using the Standard codec: G_DEFINE_TYPE(CustomCodec, custom_codec, FlStandardCodec)
enum {
DATE_VALUE,
FOO_VALUE,
};
static gboolean
custom_codec_write_value (FlStandardCodec *codec, GByteArray *buffer, FlValue *value, GError **error) {
if (fl_value_get_type (value) == FL_VALUE_TYPE_CUSTOM) {
if (fl_value_get_custom_id (value) == DATE_VALUE) {
GDateTime *date = fl_value_get_custom (value);
fl_standard_codec_write_code (codec, FL_STANDARD_CODEC_EXTENSION_CODE + DATE_VALUE);
...
return TRUE;
}
else if (fl_value_get_custom_id (value) == FOO_VALUE) {
Foo *foo = fl_value_get_custom (value);
fl_standard_codec_write_code (codec, FL_STANDARD_CODEC_EXTENSION_CODE + FOO_VALUE);
...
return TRUE;
}
}
return FL_STANDARD_CODEC_CLASS(custom_codec_parent_class)->write_value(codec, buffer, value, error);
}
static FlValue *
custom_codec_read_value_data (FlStandardCodec *codec, GBytes *buffer, size_t *offset, uint8_t code, GError **error) {
if (code == FL_STANDARD_CODEC_EXTENSION_CODE + DATE_VALUE) {
...
return fl_value_new_custom (g_date_time_new (...), g_date_time_unref);
}
if (code == FL_STANDARD_CODEC_EXTENSION_CODE + FOO_VALUE) {
...
return fl_value_new_custom (foo_new (...), g_object_unref);
}
return FL_CODEC_CLASS(custom_codec_parent_class)->read_value_data(codec, buffer, offset, code, error);
}
void
custom_codec_class_init(CustomCodecKlass *klass) {
FL_CODEC_CLASS(klass)->write_value = custom_codec_write_custom_value;
FL_STANDARD_CODEC_CLASS(klass)->read_value_data = custom_codec_read_value_data;
}
void
custom_codec_init(CustomCodec *codec) {}
static void
method_response_cb (GObject *object, GAsyncResult *result, gpointer user_data)
{
g_autofree gchar *error_code = NULL;
g_autofree gchar *error_message = NULL;
g_autoptr(FlValue) response = NULL;
g_autoptr(GError) error = NULL;
if (!fl_method_channel_invoke_finish (FL_METHOD_CHANNEL (object),
result,
&error_code,
&error_message,
&response,
&error)) {
g_printerr ("Failed to send platform message: %s\n", error->message);
return;
}
if (error_code == NULL)
handle_success (response);
else
handle_failure (error_code, error_message, response);
}
int
main (int argc, char **argv)
{
...
method_channel = fl_method_channel_new (fl_engine_get_binary_messenger (fl_view_get_engine (view)),
"example.com/gtk-test-method",
FL_METHOD_CODEC (custom_codec_new ()));
g_autoptr(FlValue) args = NULL;
args = fl_value_list_new ();
fl_value_list_add_take (args, fl_value_list_string_new ("Hello World"));
fl_value_list_add_take (args, fl_value_custom_new (DATE_VALUE, &event.date, NULL)); // Not copied
fl_value_list_add_take (args, fl_value_custom_new (FOO_VALUE, g_object_ref (event.foo), g_object_unref)); // Copied with GDestroyNotify
fl_method_channel_invoke (method_channel, "Test.test", args, NULL, method_response_cb, NULL);
...
} |
983b578 to
95d590e
Compare
95d590e to
85f0d41
Compare
8c5dc09 to
bf8dd1b
Compare
bf8dd1b to
21a1be9
Compare
stuartmorgan-g
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments below from a partial pass; I started to review the implementation, then realized I should do the public headers first, which is why there are only comments on a few implementation files. I need to take a break since it's a huge review, then I can come back. I did most of the interface, but haven't done FlValue yet.
Leading to: could you pull FlValue out into a separate PR? It's conceptually distinct enough that it should be easy for it to be a stand-alone PR. (In theory I could review it all at once, but the wall-of-code effect here is definitely real, and I think there's enough to discuss in that API surface that it would be nice to do it as a unit.)
|
|
||
| FlValue* value = | ||
| FL_CODEC_GET_CLASS(self)->read_value(self, buffer, &o, error); | ||
| if (value != nullptr && offset != nullptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If offset is an i/o param, why is it valid for it to be null? It seems like this should just be an assertion at the start of the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using it as an optional in/out param, i.e. NULL is "start at zero and I don't care what the result is". It could be asserted though, I wasn't 100% sure which way to go on this. I do find it annoying in APIs when you have to provide something like this and you don't care.
This has been refactored though with the FlMessageCodec changes, so it may not be signficant anymore. I'll keep it in mind and we can bring up again if it remains in a a later PR.
| * | ||
| * Return: %TRUE if successfully decoded. | ||
| */ | ||
| gboolean (*decode_method_call)(FlMethodCodec* codec, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this is using an out-param to be consistent with decode_response's two out params?
Another option would be to return a value here, and do something like DecodeAndProcessResponseEnvelope in the C++ code for the envelope decoding case. (I don't really love that solution either though.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this type of pattern is always awful in C, all the solutions have annoying downsides. Let's bring this up in a later PR when we get to this code.
| GError** error); | ||
|
|
||
| G_END_DECLS | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you send a not-implemented response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I hadn't seen that response. I'll have to research that...
| * @error_message: (transfer full): location to write error message | ||
| * @result: (transfer full): location to write call response, or error details | ||
| * if an error. | ||
| * @error: (allow-none): #GError location to store the error occurring, or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here about error/error confusion.
| * an error. | ||
| * @error: (allow-none): #GError location to store the error occurring, or %NULL | ||
| * to ignore. | ||
| * |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is not-implemented communicated here?
| * @error: (allow-none): #GError location to store the error occurring, or %NULL | ||
| * to ignore. | ||
| * | ||
| * Complete request started with fl_method_channel_invoke(). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This, and/or some of the other methods in this class, would benefit from some high-level documentation about how method calls work (see other platforms for examples). Right now it seems like you already need to know how method channels work conceptually (E.g., that you'll get one of three possible outcomes to a method call) to use this class.
While that is consistent with my experiences so far with GTK documentation, that's an area where I'd rather we do better rather than be consistent 😉
That definitely looks good. What would the "making a custom FlValue" part look like? (Maybe something to discuss in the split-out PR) |
|
The wall of code effect is definitely real and also affecting me :) I think this PR has achieved it's value, in that I've got some good initial feedback on the changes and I think I have got my head around the whole architecture/API. What I'll do is make a series of PRs that bring these changes across in more manageable chunks:
I'll respond to the changes requested here, then let's consider this PR obsolete and future work will go into the PRs mentioned above. |
This change implements the standard and JSON codecs, and the method channel to allow calls to and from Dart.