From b268cc6c620fbc3b2b4464915cde804f0f3a9431 Mon Sep 17 00:00:00 2001 From: Atsushi Eno Date: Mon, 13 Nov 2023 18:10:39 +0000 Subject: [PATCH] RemotePluginInstance can now provide its own way to handle host extension. `AAPXSDefinition.process_incoming_host_aapxs_request` was never used because there was nothing that could provide valid host extensions in the host (RemotePluginInstance). Now there is a function delegate `getHostExtension`, it is technically possible for host app developers to provide their own way to handle extensions. For example, Parameter update notification handler could be implemented by anyone. --- .../main/cpp/core/aapxs/parameters-aapxs.cpp | 11 +++-- .../cpp/core/hosting/PluginInstance.Local.cpp | 1 + .../core/hosting/PluginInstance.Remote.cpp | 43 +++++++++++++------ docs/design/EXTENSIBILITY.md | 9 +++- external/cmidi2 | 2 +- include/aap/core/host/plugin-instance.h | 5 +++ 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/androidaudioplugin/src/main/cpp/core/aapxs/parameters-aapxs.cpp b/androidaudioplugin/src/main/cpp/core/aapxs/parameters-aapxs.cpp index 8bf455fd..8fccb1dd 100644 --- a/androidaudioplugin/src/main/cpp/core/aapxs/parameters-aapxs.cpp +++ b/androidaudioplugin/src/main/cpp/core/aapxs/parameters-aapxs.cpp @@ -58,13 +58,16 @@ void aap::xs::AAPXSDefinition_Parameters::aapxs_parameters_process_incoming_host struct AAPXSDefinition *feature, AAPXSRecipientInstance *aapxsInstance, AndroidAudioPluginHost *host, AAPXSRequestContext *request) { auto ext = (aap_parameters_host_extension_t*) host->get_extension(host, AAP_PARAMETERS_EXTENSION_URI); - if (!ext) - return; // FIXME: should there be any global error handling? switch (request->opcode) { - case OPCODE_NOTIFY_PARAMETERS_CHANGED: - ext->notify_parameters_changed(ext, host); + case OPCODE_NOTIFY_PARAMETERS_CHANGED: { + if (ext) + ext->notify_parameters_changed(ext, host); + //else { + // FIXME: log warning? + //} aapxsInstance->send_aapxs_reply(aapxsInstance, request); break; + } default: // FIXME: log warning? break; diff --git a/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Local.cpp b/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Local.cpp index 763f6917..f1702560 100644 --- a/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Local.cpp +++ b/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Local.cpp @@ -29,6 +29,7 @@ aap::LocalPluginInstance::LocalPluginInstance( aapxs_out_merge_buffer = calloc(1, event_midi2_buffer_size); aapxs_midi2_processor.setExtensionCallback([&](aap_midi2_aapxs_parse_context* context) { + // FIXME: this should be implemented in LocalPluginInstance. auto aapxsInstance = getAAPXSDispatcher().getPluginAAPXSByUri(context->uri); // We need to copy extension data buffer before calling it. memcpy(aapxsInstance->serialization->data, (int32_t*) context->data, context->dataSize); diff --git a/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Remote.cpp b/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Remote.cpp index 6f381477..91106f3f 100644 --- a/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Remote.cpp +++ b/androidaudioplugin/src/main/cpp/core/hosting/PluginInstance.Remote.cpp @@ -19,17 +19,7 @@ aap::RemotePluginInstance::RemotePluginInstance(PluginClient* client, shared_memory_store = new ClientPluginSharedMemoryStore(); aapxs_session.setReplyHandler([&](aap_midi2_aapxs_parse_context* context) { - auto aapxs = feature_registry->items()->getByUri(context->uri); - if (aapxs) { - auto aapxsInstance = getAAPXSDispatcher().getPluginAAPXSByUri(context->uri); - AAPXSRequestContext request{nullptr, nullptr, aapxsInstance->serialization, context->urid, context->uri, context->request_id, context->opcode}; - if (aapxs->process_incoming_plugin_aapxs_reply) - aapxs->process_incoming_plugin_aapxs_reply(aapxs, aapxsInstance, plugin, &request); - else - aap::a_log_f(AAP_LOG_LEVEL_WARN, LOG_TAG, "AAPXS %s does not have a reply handler (opcode: %d)", context->uri, context->opcode); - } - else - aap::a_log_f(AAP_LOG_LEVEL_WARN, LOG_TAG, "AAPXS for %s is not registered (opcode: %d)", context->uri, context->opcode); + handleAAPXSReply(context); }); } @@ -165,12 +155,14 @@ void aap::RemotePluginInstance::process(int32_t frameCount, int32_t timeoutInNan void * aap::RemotePluginInstance::internalGetHostExtension(AndroidAudioPluginHost *host, const char *uri) { + auto instance = (RemotePluginInstance *) host->context; if (strcmp(uri, AAP_PLUGIN_INFO_EXTENSION_URI) == 0) { - auto instance = (RemotePluginInstance *) host->context; instance->host_plugin_info.get = get_plugin_info; return &instance->host_plugin_info; } - // FIXME: implement more host extensions + // look for user-implemented host extensions + if (instance->getHostExtension) + return instance->getHostExtension(host, uri); return nullptr; } @@ -282,3 +274,28 @@ aap::xs::AAPXSDefinitionClientRegistry *aap::RemotePluginInstance::getAAPXSRegis void aap::RemotePluginInstance::setupAAPXS() { standards = std::make_unique(); } + +void aap::RemotePluginInstance::handleAAPXSReply(aap_midi2_aapxs_parse_context *context) { + auto aapxs = feature_registry->items()->getByUri(context->uri); + if (aapxs) { + if (context->opcode >= 0) { + // plugin AAPXS reply + auto aapxsInstance = getAAPXSDispatcher().getPluginAAPXSByUri(context->uri); + AAPXSRequestContext request{nullptr, nullptr, aapxsInstance->serialization, context->urid, context->uri, context->request_id, context->opcode}; + if (aapxs->process_incoming_plugin_aapxs_reply) + aapxs->process_incoming_plugin_aapxs_reply(aapxs, aapxsInstance, plugin, &request); + else + aap::a_log_f(AAP_LOG_LEVEL_WARN, LOG_TAG, "AAPXS %s does not have a reply handler (opcode: %d)", context->uri, context->opcode); + } else { + // host AAPXS request + auto aapxsInstance = getAAPXSDispatcher().getHostAAPXSByUri(context->uri); + AAPXSRequestContext request{nullptr, nullptr, aapxsInstance->serialization, context->urid, context->uri, context->request_id, context->opcode}; + if (aapxs->process_incoming_host_aapxs_request) + aapxs->process_incoming_host_aapxs_request(aapxs, aapxsInstance, &plugin_host_facade, &request); + else + aap::a_log_f(AAP_LOG_LEVEL_WARN, LOG_TAG, "AAPXS %s does not have a reply handler (opcode: %d)", context->uri, context->opcode); + } + } + else + aap::a_log_f(AAP_LOG_LEVEL_WARN, LOG_TAG, "AAPXS for %s is not registered (opcode: %d)", context->uri, context->opcode); +} diff --git a/docs/design/EXTENSIBILITY.md b/docs/design/EXTENSIBILITY.md index 3e4b98b4..c2f0cc8a 100644 --- a/docs/design/EXTENSIBILITY.md +++ b/docs/design/EXTENSIBILITY.md @@ -53,6 +53,8 @@ This will make extension implementation independent of specific AAP versions to Typical plugin extension APIs are usually synchronous i.e. their functions block throughout the execution, but AAP extension foundation is designed to be "async ready", especially when the plugin enters ACTIVE = realtime mode. +It is because AAP, unlike those desktop plugin APIs, needs interaction between the host and plugin and keeping a thread per extension function call costs potentially a lot of synchronization constructs e.g. 100 locks per 100 `getPreset()` calls or potentially more (100 locks in the host + 100 locks in the plugin + 100 locks in AAPXS). + They can be designed and implemented in synchronous manner (we actually have synchronous API as some transitive solutions), but then there is no assured realtime safety. @@ -110,13 +112,13 @@ There should be two kinds of accesses to AAPXS from `RemotePluginInstance`: AAPX ### vNext Hosting entrypoint to AAPXS Runtime API -Since AAP sends extension controllers either via Binder IPC (non-RT) or AAPXS SysEx8 (RT), unknown extensions can be still *supported*. For untyped extension accesses, `RemotePluginInstance` provides `sendExtensionRequest()` (`sendExtensionMessage()` in the existing implementation). +Since AAP sends extension controllers either via Binder IPC (non-RT) or AAPXS SysEx8 (RT), unknown extensions can be still *supported*. For untyped extension accesses, `RemotePluginInstance` provides `sendExtensionRequest()`. `sendExtensionRequest()` takes `AAPXSRequestContext` which contains *already serialized* requests as a binary chunk in its member `AAPXSSerializationContext`. Then it dispatches the request to the appropriate handler: Binder IPC at INACTIVE (non-RT) state, or AAPXS SysEx8 at ACTIVE RT state. Serialization is handled by each AAPXS. For such a hosting implementation that does not directly support the extension (or a plugin implementation that does not directly suppot the host extension), there is untyped AAPXS API that is implemented without strongly-typed extension API. The host can still convey and even invoke its dynamically assigned extension invocation handler in `AAPXSDefinition`. -At AAPXS Runtime level, there are utility functions in `aap_midi2_helper.h` for AAPXS parsing and generation in C, and `AAPXSMidi2Processor` and `AAPXSMidi2ClientSession` as the internal helpers in C++. To support asynchronous invocation under control, a host can assign an async callback `aapxs_completion_callback` to `AAPXSRequestContext`, which is then invoked when `AAPXSMidi2ClientSession` receives a corresponding reply to the request. +At AAPXS Runtime level, there are utility functions in `aap_midi2_helper.h` for AAPXS parsing and generation in C, and `AAPXSMidi2RecipientSession` and `AAPXSMidi2InitiatorSession` as the internal helpers in C++ in `aapxs-hosting-runtime.h`. To support asynchronous invocation under control, a host can assign an async callback `aapxs_completion_callback` to `AAPXSRequestContext`, which is then invoked when `AAPXSMidi2InitiatorSession` receives a corresponding reply to the request. ## vNext: what AAPXS developer writes @@ -134,6 +136,8 @@ Each AAPXS developer provides the following stuff: - Strongly typed plugin extension client (optionally) to help plugin client development. - Strongly typed host extension client (optionally) to help plugin service development. +`samples/aapxssample` is an example plugin that provides its own AAPXS (no dedicated host client app there yet though). + ### vNext: Untyped Extension Handler implementation We need AAPXS implementation by extension API developer at: @@ -144,3 +148,4 @@ We need AAPXS implementation by extension API developer at: - weakly-typed plugin service AAPXS reply sender: Once the plugin extension function does its job, then the result should be serialized by `process_incoming_plugin_aapxs_request()` or whatever it asynchronously invokes at its completion. It should then call `send_aapxs_reply()` function (in `AAPXSServiceInstance`) which is assigned by the host. It takes `requestId` for correlation. - weakly-typed plugin client AAPXS reply handler: a caller would need to deserialize the AAPXS binary and continue the client extension call, but it is totally optional. - If it was an asynchronous call, then the `aapxs_completion_callback` should be invoked. + diff --git a/external/cmidi2 b/external/cmidi2 index 0351d8c8..ff6e22d6 160000 --- a/external/cmidi2 +++ b/external/cmidi2 @@ -1 +1 @@ -Subproject commit 0351d8c8ece4c91be899683c098dab5962fcc5f5 +Subproject commit ff6e22d64d91ad1acae47ca30e64618897dd953d diff --git a/include/aap/core/host/plugin-instance.h b/include/aap/core/host/plugin-instance.h index 3bdfe88b..fbefce0c 100644 --- a/include/aap/core/host/plugin-instance.h +++ b/include/aap/core/host/plugin-instance.h @@ -335,6 +335,11 @@ namespace aap { // AAPXS v2 registry xs::AAPXSDefinitionClientRegistry* getAAPXSRegistry(); xs::StandardExtensions &getStandardExtensions() override { return *standards; } + + void handleAAPXSReply(aap_midi2_aapxs_parse_context *context); + + // Host developers can override this function to return their own extensions. + std::function getHostExtension; }; }