mirror of
https://github.com/mpv-player/mpv
synced 2024-11-14 22:48:35 +01:00
ipc: implement asynchronous commands
I decided to make this explicit. The alternative would have been making all commands asynchronous always, like a small note in the manpage threatened. I think that could have caused compatibility issues. As a design decision, this does not send a reply if an async command started. This could be a good or bad idea, but in any case, it will make async command look almost like synchronous ones, except they don't block the IPC protocol.
This commit is contained in:
parent
a9e6b9ea36
commit
b05550fe55
@ -136,9 +136,6 @@ Would generate this response:
|
||||
|
||||
If you don't specify a ``request_id``, command replies will set it to 0.
|
||||
|
||||
Commands may run asynchronously in the future, instead of blocking the socket
|
||||
until a reply is sent.
|
||||
|
||||
All commands, replies, and events are separated from each other with a line
|
||||
break character (``\n``).
|
||||
|
||||
@ -150,6 +147,38 @@ with ``#`` and empty lines are ignored.
|
||||
Currently, embedded 0 bytes terminate the current line, but you should not
|
||||
rely on this.
|
||||
|
||||
Asynchronous commands
|
||||
---------------------
|
||||
|
||||
Command can be run asynchronously. This behaves exactly as with normal command
|
||||
execution, except that execution is not blocking. Other commands can be sent
|
||||
while it's executing, and command completion can be arbitrarily reordered.
|
||||
|
||||
The ``async`` field controls this. If present, it must be a boolean. If missing,
|
||||
``false`` is assumed.
|
||||
|
||||
For example, this initiates an asynchronous command:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["screenshot"], "request_id": 123, "async": true }
|
||||
|
||||
And this is the completion:
|
||||
|
||||
::
|
||||
|
||||
{"request_id":123,"error":"success","data":null}
|
||||
|
||||
By design, you will not get a confirmation that the command was started. If a
|
||||
command is long running, sending the message will lead to any reply until much
|
||||
later when the command finishes.
|
||||
|
||||
Some commands execute synchronously, but these will behave like asynchronous
|
||||
commands that finished execution immediately.
|
||||
|
||||
Cancellation of asynchronous commands is available in the libmpv API, but has
|
||||
not yet been implemented in the IPC protocol.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
|
52
input/ipc.c
52
input/ipc.c
@ -109,8 +109,28 @@ static void mpv_node_map_add_string(void *ta_parent, mpv_node *src, const char *
|
||||
mpv_node_map_add(ta_parent, src, key, &val_node);
|
||||
}
|
||||
|
||||
// This is supposed to write a reply that looks like "normal" command execution.
|
||||
static void mpv_format_command_reply(void *ta_parent, mpv_event *event,
|
||||
mpv_node *dst)
|
||||
{
|
||||
assert(event->event_id == MPV_EVENT_COMMAND_REPLY);
|
||||
mpv_event_command *cmd = event->data;
|
||||
|
||||
mpv_node_map_add_int64(ta_parent, dst, "request_id", event->reply_userdata);
|
||||
|
||||
mpv_node_map_add_string(ta_parent, dst, "error",
|
||||
mpv_error_string(event->error));
|
||||
|
||||
mpv_node_map_add(ta_parent, dst, "data", &cmd->result);
|
||||
}
|
||||
|
||||
static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst)
|
||||
{
|
||||
if (event->event_id == MPV_EVENT_COMMAND_REPLY) {
|
||||
mpv_format_command_reply(ta_parent, event, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
mpv_node_map_add_string(ta_parent, dst, "event", mpv_event_name(event->event_id));
|
||||
|
||||
if (event->reply_userdata)
|
||||
@ -193,6 +213,10 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
|
||||
mpv_node msg_node;
|
||||
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
|
||||
mpv_node *reqid_node = NULL;
|
||||
int64_t reqid = 0;
|
||||
mpv_node *async_node = NULL;
|
||||
bool async = false;
|
||||
bool send_reply = true;
|
||||
|
||||
rc = json_parse(ta_parent, &msg_node, &src, 50);
|
||||
if (rc < 0) {
|
||||
@ -206,11 +230,28 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
|
||||
goto error;
|
||||
}
|
||||
|
||||
async_node = node_map_get(&msg_node, "async");
|
||||
if (async_node) {
|
||||
if (async_node->format != MPV_FORMAT_FLAG) {
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
goto error;
|
||||
}
|
||||
async = async_node->u.flag;
|
||||
}
|
||||
|
||||
reqid_node = node_map_get(&msg_node, "request_id");
|
||||
if (reqid_node && reqid_node->format != MPV_FORMAT_INT64) {
|
||||
if (reqid_node) {
|
||||
if (reqid_node->format == MPV_FORMAT_INT64) {
|
||||
reqid = reqid_node->u.int64;
|
||||
} else if (async) {
|
||||
mp_err(log, "'request_id' must be an integer for async commands.\n");
|
||||
rc = MPV_ERROR_INVALID_PARAMETER;
|
||||
goto error;
|
||||
} else {
|
||||
mp_warn(log, "'request_id' must be an integer. Using other types is "
|
||||
"deprecated and will trigger an error in the future!\n");
|
||||
}
|
||||
}
|
||||
|
||||
mpv_node *cmd_node = node_map_get(&msg_node, "command");
|
||||
if (!cmd_node ||
|
||||
@ -396,10 +437,16 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
|
||||
} else {
|
||||
mpv_node result_node;
|
||||
|
||||
if (async) {
|
||||
rc = mpv_command_node_async(client, reqid, cmd_node);
|
||||
if (rc >= 0)
|
||||
send_reply = false;
|
||||
} else {
|
||||
rc = mpv_command_node(client, cmd_node, &result_node);
|
||||
if (rc >= 0)
|
||||
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
/* If the request contains a "request_id", copy it back into the response.
|
||||
@ -415,8 +462,11 @@ error:
|
||||
mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
|
||||
|
||||
char *output = talloc_strdup(ta_parent, "");
|
||||
|
||||
if (send_reply) {
|
||||
json_write(&output, &reply_node);
|
||||
output = ta_talloc_strdup_append(output, "\n");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user