Use std::avriant to encode event payloads

It's a bit more complicated this way, but nwo we can easily add support
for the opcode specific structs.
This commit is contained in:
Robbert van der Helm
2020-03-08 17:47:09 +01:00
parent d434bcf682
commit ff89d12c1a
5 changed files with 119 additions and 49 deletions
+15 -4
View File
@@ -7,10 +7,21 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket,
void* data,
float option,
std::optional<std::pair<Logger&, bool>> logging) {
auto payload =
data == nullptr
? std::nullopt
: std::make_optional(std::string(static_cast<char*>(data)));
// Encode the right payload type for this event. Check the documentation for
// `EventPayload` for more information.
EventPayload payload = nullptr;
if (data != nullptr) {
// TODO: Specific structs go here
// Assume buffers are zeroed out, this is probably not the case
char* c_string = static_cast<char*>(data);
if (c_string[0] != 0) {
payload = std::string(c_string);
} else {
payload = std::array<char, max_string_length>();
}
}
if (logging.has_value()) {
auto [logger, is_dispatch] = *logging;
logger.log_event(is_dispatch, opcode, index, value, payload, option);
+20 -24
View File
@@ -134,6 +134,7 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket,
void* data,
float option,
std::optional<std::pair<Logger&, bool>> logging);
/**
* Receive an event from a socket and pass it through to some callback function.
* This is used for both the host -> plugin 'dispatch' events and the plugin ->
@@ -160,35 +161,30 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket,
if (logging.has_value()) {
auto [logger, is_dispatch] = *logging;
logger.log_event(is_dispatch, event.opcode, event.index, event.value,
event.data, event.option);
}
// The void pointer argument for the dispatch function is used for
// either:
// - Not at all, in which case it will be a null pointer
// - For passing strings as input to the event
// - For providing a buffer for the event to write results back into
char* payload = nullptr;
std::array<char, max_string_length> buffer;
if (event.data.has_value()) {
// If the data parameter was an empty string, then we're going to
// pass a larger buffer to the dispatch function instead. Otherwise
// we'll pass the data passed by the host.
if (!event.data->empty()) {
payload = const_cast<char*>(event.data->c_str());
} else {
payload = buffer.data();
}
event.payload, event.option);
}
void* data = std::visit(
overload{[&](std::nullptr_t) -> void* { return nullptr; },
[&](std::string s) -> void* {
return const_cast<char*>(s.c_str());
},
[&](std::array<char, max_string_length> buffer) -> void* {
return buffer.data();
}},
event.payload);
const intptr_t return_value = callback(plugin, event.opcode, event.index,
event.value, payload, event.option);
event.value, data, event.option);
// Only write back the value from `payload` if we were passed an empty
// buffer to write into
bool is_updated = event.data.has_value() && event.data->empty();
// Only write back data for empty buffers
// XXX: Is it possbile here that we got passed a non empty buffer (i.e.
// because it was not zeroed out by the host) for an event that should
// report some data back?
const auto response_data =
is_updated ? std::make_optional(payload) : std::nullopt;
std::holds_alternative<std::array<char, max_string_length>>(
event.payload)
? std::make_optional(static_cast<char*>(data))
: std::nullopt;
if (logging.has_value()) {
auto [logger, is_dispatch] = *logging;
+9 -10
View File
@@ -121,7 +121,7 @@ void Logger::log_event(bool is_dispatch,
int opcode,
int index,
intptr_t value,
std::optional<std::string> payload,
EventPayload payload,
float option) {
if (BOOST_UNLIKELY(verbosity >= Verbosity::events)) {
std::ostringstream message;
@@ -140,15 +140,14 @@ void Logger::log_event(bool is_dispatch,
message << "(index = " << index << ", value = " << value
<< ", option = " << option << ", data = ";
if (!payload.has_value()) {
message << "<nullptr>";
} else if (payload->empty()) {
message << "<writable_buffer>";
} else {
// Might print binary payload, maybe check for this?
message << "\"" << payload.value() << "\"";
}
message << ")";
std::visit(
overload{[&](std::nullptr_t) { message << "<nullptr>"; },
[&](std::string s) { message << "\"" << s << "\""; },
[&](std::array<char, max_string_length>) {
message << "<writeable_buffer>";
}},
payload);
log(message.str());
}
+3 -1
View File
@@ -20,6 +20,8 @@
#include <optional>
#include <ostream>
#include "serialization.h"
/**
* Super basic logging facility meant for debugging malfunctioning VST
* plugins. This is also used to redirect the output of the Wine process
@@ -93,7 +95,7 @@ class Logger {
int opcode,
int index,
intptr_t value,
std::optional<std::string> payload,
EventPayload payload,
float option);
void log_event_response(bool is_dispatch,
intptr_t return_value,
+72 -10
View File
@@ -1,10 +1,15 @@
#include <bitsery/adapter/buffer.h>
#include <bitsery/ext/std_optional.h>
#include <bitsery/ext/std_variant.h>
#include <bitsery/traits/array.h>
#include <bitsery/traits/string.h>
#include <bitsery/traits/vector.h>
#include <vestige/aeffectx.h>
#include <variant>
#pragma once
/**
* The maximum number of audio channels supported.
*/
@@ -15,10 +20,10 @@ constexpr size_t max_audio_channels = 32;
constexpr size_t max_buffer_size = 16384;
/**
* The maximum size in bytes of a string or buffer passed through a void pointer
* in one of the dispatch functions. This is used as a buffer size and also as a
* cutoff for checking if c-style strings behind a `char*` have changed.
* in one of the dispatch functions. This is used to create buffers for plugins
* to write strings to.
*/
constexpr size_t max_string_length = 128;
constexpr size_t max_string_length = 64;
/**
* A simple constant sized buffer for smaller types that can be allocated on the
@@ -33,12 +38,53 @@ using OutputAdapter = bitsery::OutputBufferAdapter<B>;
template <typename B>
using InputAdapter = bitsery::InputBufferAdapter<B>;
// The cannonical overloading template for `std::visitor`, not sure why this
// isn't part of the standard library
template <class... Ts>
struct overload : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overload(Ts...)->overload<Ts...>;
/**
* VST events are passed a void pointer that can contain a variety of different
* data types depending on the event's opcode. This is typically either:
*
* - A null pointer, used for simple events.
* - A char pointer to a null terminated string, used for passing strings to the
* plugin such as when renaming presets. Bitsery handles the serialization for
* us.
*
* NOTE: Bitsery does not support null terminated C-strings without a known
* size. We can replace `std::string` with `char*` once it does for
* clarity's sake.
*
* - Specific data structures from `aeffextx.h`. For instance an event with the
* opcode `effProcessEvents` comes with a struct containing a list of midi
* events.
*
* TODO: A lot of these are still missing, beginning with `VstEvents`.
*
* - Some empty buffer for the plugin to write its own data to, for instance for
* a plugin to report its name or the label for a certain parameter. We'll
* assume that this is the default if none of the above options apply.
*
* TODO: As a simple optimization we of course wouldn't have to send an entire
* empty array here, this should be replaced by some kind of marker
* struct. This would require some minor modifications in
* `passthrough_event()`.
*/
using EventPayload = std::
variant<std::nullptr_t, std::string, std::array<char, max_string_length>>;
/**
* An event as dispatched by the VST host. These events will get forwarded to
* the VST host process running under Wine. The fields here mirror those
* arguments sent to the `AEffect::dispatch` function.
*/
struct Event {
// TODO: Possibly update to account for VstEvents
using buffer_type = ArrayBuffer<max_string_length + 32>;
int opcode;
@@ -49,12 +95,16 @@ struct Event {
intptr_t value;
float option;
/**
* The event dispatch function has a void pointer parameter that's used to
* either send string messages to the event (e.g. for `effCanDo`) or to
* write a string back into. This value will contain an (empty) string if
* the void* parameter for the dispatch function was not a null pointer.
* The event dispatch function has a void pointer parameter that's often
* used to either pass additional data for the event or to provide a buffer
* for the plugin to write a string into.
*
* The `VstEvents` struct passed for the `effProcessEvents` event contains
* an array of pointers. This requires some special handling which is why we
* have to use an `std::variant` instead of a simple string buffer. Luckily
* Bitsery can do all the hard work for us.
*/
std::optional<std::string> data;
EventPayload payload;
template <typename S>
void serialize(S& s) {
@@ -65,8 +115,20 @@ struct Event {
// bit large pointers.
s.value8b(value);
s.value4b(option);
s.ext(data, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.text1b(v, max_string_length); });
// I couldn't get this serializer to work seperately without
// `EventPayload` in a struct
s.ext(payload,
bitsery::ext::StdVariant{
// TODO: Some of these oerlaods might not be necessary, check
// if this is the case
[](S&, std::nullptr_t&) {},
[](S& s, std::string& string) {
s.text1b(string, max_string_length);
},
[](S& s, std::array<char, max_string_length>& buffer) {
s.container1b(buffer);
}});
}
};