Improve web state transfer
This commit is contained in:
parent
2efd1de4e9
commit
bbad24674a
@ -7,7 +7,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "./param-info.h"
|
#include "../param-info.h"
|
||||||
#include "../storage/storage.h"
|
#include "../storage/storage.h"
|
||||||
#include "../ui/web-ui.h"
|
#include "../ui/web-ui.h"
|
||||||
|
|
||||||
@ -118,11 +118,23 @@ private:
|
|||||||
uint32_t crc = 0xFFFFFFFFu;
|
uint32_t crc = 0xFFFFFFFFu;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Just include the proposed draft structs here
|
||||||
|
static constexpr const char *CLAP_EXT_WEBVIEW1 = "clap.webview/1";
|
||||||
|
struct clap_plugin_webview1 {
|
||||||
|
bool(CLAP_ABI *provide_starting_uri)(const clap_plugin_t *plugin, char *out_buffer, uint32_t out_buffer_capacity);
|
||||||
|
bool(CLAP_ABI *receive)(const clap_plugin_t *plugin, const void *buffer, uint32_t size);
|
||||||
|
};
|
||||||
|
struct clap_host_webview1 {
|
||||||
|
bool(CLAP_ABI *is_open)(const clap_host_t *host);
|
||||||
|
bool(CLAP_ABI *send)(const clap_host_t *host, const void *buffer, uint32_t size);
|
||||||
|
};
|
||||||
|
|
||||||
template<template<class> class EffectSTFX>
|
template<template<class> class EffectSTFX>
|
||||||
struct Plugin : public clap_plugin {
|
struct Plugin : public clap_plugin {
|
||||||
const Plugins &plugins;
|
const Plugins &plugins;
|
||||||
const clap_host *host;
|
const clap_host *host;
|
||||||
using Effect = stfx::WebUILibraryEffect<float, EffectSTFX>;
|
const clap_host_webview1 *hostWebview1 = nullptr;
|
||||||
|
using Effect = stfx::web::WebUILibraryEffect<float, EffectSTFX>;
|
||||||
Effect effect;
|
Effect effect;
|
||||||
|
|
||||||
template<class ...Args>
|
template<class ...Args>
|
||||||
@ -183,6 +195,10 @@ struct Plugin : public clap_plugin {
|
|||||||
// CLAP plugin methods
|
// CLAP plugin methods
|
||||||
static bool plugin_init(const clap_plugin *obj) {
|
static bool plugin_init(const clap_plugin *obj) {
|
||||||
auto &plugin = *(Plugin *)obj;
|
auto &plugin = *(Plugin *)obj;
|
||||||
|
#define STFX_GET_EXT(field, extId) \
|
||||||
|
plugin.field = (decltype(plugin.field))plugin.host->get_extension(plugin.host, extId);
|
||||||
|
STFX_GET_EXT(hostWebview1, CLAP_EXT_WEBVIEW1)
|
||||||
|
#undef STFX_GET_EXT
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static void plugin_destroy(const clap_plugin *obj) {
|
static void plugin_destroy(const clap_plugin *obj) {
|
||||||
@ -240,10 +256,10 @@ struct Plugin : public clap_plugin {
|
|||||||
state_load
|
state_load
|
||||||
};
|
};
|
||||||
return &ext;
|
return &ext;
|
||||||
} else if (!std::strcmp(extId, CLAP_EXT_WEBVIEW)) {
|
} else if (!std::strcmp(extId, CLAP_EXT_WEBVIEW1)) {
|
||||||
static struct clap_plugin_webview ext{
|
static struct clap_plugin_webview1 ext{
|
||||||
webview_provide_starting_uri,
|
webview1_provide_starting_uri,
|
||||||
webview_receive
|
webview1_receive
|
||||||
};
|
};
|
||||||
return &ext;
|
return &ext;
|
||||||
}
|
}
|
||||||
@ -521,8 +537,8 @@ struct Plugin : public clap_plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StateWriter : public StorageCborWriter<true, StateWriter> {
|
struct StateWriter : public stfx::storage::StorageCborWriter<true, StateWriter> {
|
||||||
using StorageCborWriter<true, StateWriter>::StorageCborWriter;
|
using stfx::storage::StorageCborWriter<true, StateWriter>::StorageCborWriter;
|
||||||
|
|
||||||
void info(const char *, const char *) {}
|
void info(const char *, const char *) {}
|
||||||
int version(int v) {
|
int version(int v) {
|
||||||
@ -548,8 +564,8 @@ struct Plugin : public clap_plugin {
|
|||||||
}
|
}
|
||||||
void invalidate(const char *) {}
|
void invalidate(const char *) {}
|
||||||
};
|
};
|
||||||
struct StateReader : public StorageCborReader<true, StateReader> {
|
struct StateReader : public stfx::storage::StorageCborReader<true, StateReader> {
|
||||||
using StorageCborReader<true, StateReader>::StorageCborReader;
|
using stfx::storage::StorageCborReader<true, StateReader>::StorageCborReader;
|
||||||
|
|
||||||
void info(const char *, const char *) {}
|
void info(const char *, const char *) {}
|
||||||
int version(int v) {
|
int version(int v) {
|
||||||
@ -596,26 +612,30 @@ struct Plugin : public clap_plugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just include the proposed draft structs here
|
static bool webview1_provide_starting_uri(const clap_plugin_t *obj, char *startingUri, uint32_t capacity) {
|
||||||
static constexpr const char *CLAP_EXT_WEBVIEW = "clap.webview/1";
|
|
||||||
struct clap_plugin_webview {
|
|
||||||
bool(CLAP_ABI *provide_starting_uri)(const clap_plugin_t *plugin, char *out_buffer, uint32_t out_buffer_capacity);
|
|
||||||
bool(CLAP_ABI *receive)(const clap_plugin_t *plugin, const void *buffer, uint32_t size);
|
|
||||||
};
|
|
||||||
struct clap_host_webview {
|
|
||||||
bool(CLAP_ABI *send)(const clap_host_t *host, const void *buffer, uint32_t size);
|
|
||||||
};
|
|
||||||
static bool webview_provide_starting_uri(const clap_plugin_t *obj, char *startingUri, uint32_t capacity) {
|
|
||||||
auto &plugin = *(Plugin *)obj;
|
auto &plugin = *(Plugin *)obj;
|
||||||
|
if (!plugin.hostWebview1) return false;
|
||||||
if (!plugin.effect.webPage.size() + 1 > capacity) return false;
|
if (!plugin.effect.webPage.size() + 1 > capacity) return false;
|
||||||
std::strcpy(startingUri, plugin.effect.webPage.c_str());
|
std::strcpy(startingUri, plugin.effect.webPage.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static bool webview_receive(const clap_plugin_t *obj, const void *buffer, uint32_t size) {
|
static bool webview1_receive(const clap_plugin_t *obj, const void *buffer, uint32_t size) {
|
||||||
auto &plugin = *(Plugin *)obj;
|
auto &plugin = *(Plugin *)obj;
|
||||||
plugin.effect.webReceive(buffer, size);
|
plugin.effect.webReceive(buffer, size);
|
||||||
|
// TODO: *double* check we're on the main thread, for safety
|
||||||
|
plugin.sendWebMessages();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendWebMessages() {
|
||||||
|
if (!hostWebview1) return;
|
||||||
|
while (auto *m = effect.getPendingWebMessage()) {
|
||||||
|
LOG_EXPR("pending web message");
|
||||||
|
hostWebview1->send(host, m->bytes.data(), m->bytes.size());
|
||||||
|
LOG_EXPR(m->bytes.size());
|
||||||
|
m->sent();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace
|
}} // namespace
|
||||||
|
|||||||
@ -488,6 +488,11 @@ namespace stfx {
|
|||||||
justHadReset = true;
|
justHadReset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Buffers>
|
||||||
|
void process(Buffers &&buffers, int blockLength) {
|
||||||
|
process(buffers, buffers, blockLength);
|
||||||
|
}
|
||||||
|
|
||||||
/// Wraps the common `process(float** inputs, float** outputs, int length)` call into the `.process(io, config, block)`.
|
/// Wraps the common `process(float** inputs, float** outputs, int length)` call into the `.process(io, config, block)`.
|
||||||
/// It actually accepts any objects which support `inputs[channel][index]`, so you could write adapters for interleaved buffers etc.
|
/// It actually accepts any objects which support `inputs[channel][index]`, so you could write adapters for interleaved buffers etc.
|
||||||
template<class Inputs, class Outputs>
|
template<class Inputs, class Outputs>
|
||||||
|
|||||||
@ -724,76 +724,96 @@ private:
|
|||||||
struct CborWriter {
|
struct CborWriter {
|
||||||
CborWriter(std::vector<unsigned char> &bytes) : bytes(bytes) {}
|
CborWriter(std::vector<unsigned char> &bytes) : bytes(bytes) {}
|
||||||
|
|
||||||
void addUInt(uint64_t u) {
|
CborWriter & addUInt(uint64_t u) {
|
||||||
writeHead(0, u);
|
writeHead(0, u);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addInt(int64_t u) {
|
CborWriter & addInt(int64_t u) {
|
||||||
if (u >= 0) {
|
if (u >= 0) {
|
||||||
writeHead(0, u);
|
writeHead(0, u);
|
||||||
} else {
|
} else {
|
||||||
writeHead(1, -1 - u);
|
writeHead(1, -1 - u);
|
||||||
}
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTag(uint64_t u) {
|
CborWriter & addTag(uint64_t u) {
|
||||||
writeHead(6, u);
|
writeHead(6, u);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addBool(bool b) {
|
CborWriter & addBool(bool b) {
|
||||||
writeHead(7, 20 + b);
|
writeHead(7, 20 + b);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void openArray() {
|
CborWriter & openArray() {
|
||||||
bytes.push_back(0x9F);
|
bytes.push_back(0x9F);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void openArray(size_t items) {
|
CborWriter & openArray(size_t items) {
|
||||||
writeHead(4, items);
|
writeHead(4, items);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void openMap() {
|
CborWriter & openMap() {
|
||||||
bytes.push_back(0xBF);
|
bytes.push_back(0xBF);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void openMap(size_t pairs) {
|
CborWriter & openMap(size_t pairs) {
|
||||||
writeHead(5, pairs);
|
writeHead(5, pairs);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void close() {
|
CborWriter & close() {
|
||||||
bytes.push_back(0xFF);
|
bytes.push_back(0xFF);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addBytes(const void *ptr, size_t length) {
|
CborWriter & addBytes(const void *ptr, size_t length) {
|
||||||
addBytes((const unsigned char *)ptr, length);
|
addBytes((const unsigned char *)ptr, length);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addBytes(const unsigned char *ptr, size_t length) {
|
CborWriter & addBytes(const unsigned char *ptr, size_t length) {
|
||||||
writeHead(2, length);
|
writeHead(2, length);
|
||||||
bytes.insert(bytes.end(), ptr, ptr + length);
|
bytes.insert(bytes.end(), ptr, ptr + length);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void openBytes() {
|
CborWriter & openBytes() {
|
||||||
bytes.push_back(0x5F);
|
bytes.push_back(0x5F);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addUtf8(const char *ptr, size_t length) {
|
CborWriter & addUtf8(const char *ptr, size_t length) {
|
||||||
writeHead(3, length);
|
writeHead(3, length);
|
||||||
bytes.insert(bytes.end(), ptr, ptr + length);
|
bytes.insert(bytes.end(), ptr, ptr + length);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addUtf8(const char *str) {
|
CborWriter & addUtf8(const char *str) {
|
||||||
addUtf8(str, std::strlen(str));
|
addUtf8(str, std::strlen(str));
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addUtf8(const std::string &str) {
|
CborWriter & addUtf8(const std::string &str) {
|
||||||
addUtf8(str.c_str());
|
addUtf8(str.c_str());
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
#ifdef CBOR_WALKER_USE_STRING_VIEW
|
#ifdef CBOR_WALKER_USE_STRING_VIEW
|
||||||
void addUtf8(const std::string_view &str) {
|
CborWriter & addUtf8(const std::string_view &str) {
|
||||||
addUtf8(str.data(), str.size());
|
addUtf8(str.data(), str.size());
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void openUtf8() {
|
CborWriter & openUtf8() {
|
||||||
bytes.push_back(0x7F);
|
bytes.push_back(0x7F);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addNull() {
|
CborWriter & addNull() {
|
||||||
bytes.push_back(0xF6);
|
bytes.push_back(0xF6);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addUndefined() {
|
CborWriter & addUndefined() {
|
||||||
bytes.push_back(0xF7);
|
bytes.push_back(0xF7);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addSimple(unsigned char k) {
|
CborWriter & addSimple(unsigned char k) {
|
||||||
writeHead(7, k);
|
writeHead(7, k);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addFloat(float v) {
|
CborWriter & addFloat(float v) {
|
||||||
bytes.push_back(0xFA);
|
bytes.push_back(0xFA);
|
||||||
#ifdef CBOR_WALKER_USE_BIT_CAST
|
#ifdef CBOR_WALKER_USE_BIT_CAST
|
||||||
uint32_t vi = std::bit_cast<uint32_t>(v);
|
uint32_t vi = std::bit_cast<uint32_t>(v);
|
||||||
@ -805,8 +825,9 @@ struct CborWriter {
|
|||||||
auto shift = (3 - i)*8;
|
auto shift = (3 - i)*8;
|
||||||
bytes.push_back((vi>>shift)&0xFF);
|
bytes.push_back((vi>>shift)&0xFF);
|
||||||
}
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addFloat(double v) {
|
CborWriter & addFloat(double v) {
|
||||||
bytes.push_back(0xFB);
|
bytes.push_back(0xFB);
|
||||||
#ifdef CBOR_WALKER_USE_BIT_CAST
|
#ifdef CBOR_WALKER_USE_BIT_CAST
|
||||||
uint64_t vi = std::bit_cast<uint64_t>(v);
|
uint64_t vi = std::bit_cast<uint64_t>(v);
|
||||||
@ -818,32 +839,38 @@ struct CborWriter {
|
|||||||
auto shift = (7 - i)*8;
|
auto shift = (7 - i)*8;
|
||||||
bytes.push_back((vi>>shift)&0xFF);
|
bytes.push_back((vi>>shift)&0xFF);
|
||||||
}
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC-8746 tags for typed arrays
|
// RFC-8746 tags for typed arrays
|
||||||
// bits: [1, 0] = log2(elementBytes), [2] = isLittleEndian, [3, 4] = [unsigned, signed, float]
|
// bits: [1, 0] = log2(elementBytes), [2] = isLittleEndian, [3, 4] = [unsigned, signed, float]
|
||||||
void addTypedArray(const uint8_t *arr, size_t length) {
|
CborWriter & addTypedArray(const uint8_t *arr, size_t length) {
|
||||||
addTag(64);
|
addTag(64);
|
||||||
addBytes((const void *)arr, length);
|
addBytes((const void *)arr, length);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const int8_t *arr, size_t length) {
|
CborWriter & addTypedArray(const int8_t *arr, size_t length) {
|
||||||
addTag(72);
|
addTag(72);
|
||||||
addBytes((const void *)arr, length);
|
addBytes((const void *)arr, length);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const uint16_t *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const uint16_t *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 65 : 69);
|
addTag(bigEndian ? 65 : 69);
|
||||||
writeTypedBlock<uint16_t>(arr, length, bigEndian);
|
writeTypedBlock<uint16_t>(arr, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const uint32_t *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const uint32_t *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 66 : 70);
|
addTag(bigEndian ? 66 : 70);
|
||||||
writeTypedBlock<uint32_t>(arr, length, bigEndian);
|
writeTypedBlock<uint32_t>(arr, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const uint64_t *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const uint64_t *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 67 : 71);
|
addTag(bigEndian ? 67 : 71);
|
||||||
writeTypedBlock<uint64_t>(arr, length, bigEndian);
|
writeTypedBlock<uint64_t>(arr, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
// For signed ints, we make a proxy struct which casts them on-the-fly
|
// For signed ints, we make a proxy struct which casts them on-the-fly
|
||||||
void addTypedArray(const int16_t *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const int16_t *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 73 : 77);
|
addTag(bigEndian ? 73 : 77);
|
||||||
struct {
|
struct {
|
||||||
const int16_t *arr;
|
const int16_t *arr;
|
||||||
@ -852,8 +879,9 @@ struct CborWriter {
|
|||||||
}
|
}
|
||||||
} unsignedArray{arr};
|
} unsignedArray{arr};
|
||||||
writeTypedBlock<uint16_t>(unsignedArray, length, bigEndian);
|
writeTypedBlock<uint16_t>(unsignedArray, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const int32_t *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const int32_t *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 74 : 78);
|
addTag(bigEndian ? 74 : 78);
|
||||||
struct {
|
struct {
|
||||||
const int32_t *arr;
|
const int32_t *arr;
|
||||||
@ -862,8 +890,9 @@ struct CborWriter {
|
|||||||
}
|
}
|
||||||
} unsignedArray{arr};
|
} unsignedArray{arr};
|
||||||
writeTypedBlock<uint32_t>(unsignedArray, length, bigEndian);
|
writeTypedBlock<uint32_t>(unsignedArray, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const int64_t *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const int64_t *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 75 : 79);
|
addTag(bigEndian ? 75 : 79);
|
||||||
struct {
|
struct {
|
||||||
const int64_t *arr;
|
const int64_t *arr;
|
||||||
@ -872,9 +901,9 @@ struct CborWriter {
|
|||||||
}
|
}
|
||||||
} unsignedArray{arr};
|
} unsignedArray{arr};
|
||||||
writeTypedBlock<uint64_t>(unsignedArray, length, bigEndian);
|
writeTypedBlock<uint64_t>(unsignedArray, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
// Look, I'm not any happier about this than you are
|
CborWriter & addTypedArray(const float *arr, size_t length, bool bigEndian=false) {
|
||||||
void addTypedArray(const float *arr, size_t length, bool bigEndian=false) {
|
|
||||||
addTag(bigEndian ? 81 : 85);
|
addTag(bigEndian ? 81 : 85);
|
||||||
struct {
|
struct {
|
||||||
const float *arr;
|
const float *arr;
|
||||||
@ -890,8 +919,9 @@ struct CborWriter {
|
|||||||
}
|
}
|
||||||
} unsignedArray{arr};
|
} unsignedArray{arr};
|
||||||
writeTypedBlock<uint32_t>(unsignedArray, length, bigEndian);
|
writeTypedBlock<uint32_t>(unsignedArray, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
void addTypedArray(const double *arr, size_t length, bool bigEndian=false) {
|
CborWriter & addTypedArray(const double *arr, size_t length, bool bigEndian=false) {
|
||||||
addTag(bigEndian ? 82 : 86);
|
addTag(bigEndian ? 82 : 86);
|
||||||
struct {
|
struct {
|
||||||
const double *arr;
|
const double *arr;
|
||||||
@ -907,6 +937,7 @@ struct CborWriter {
|
|||||||
}
|
}
|
||||||
} unsignedArray{arr};
|
} unsignedArray{arr};
|
||||||
writeTypedBlock<uint64_t>(unsignedArray, length, bigEndian);
|
writeTypedBlock<uint64_t>(unsignedArray, length, bigEndian);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "./cbor-walker.h"
|
#include "./cbor-walker.h"
|
||||||
|
|
||||||
|
namespace stfx { namespace storage {
|
||||||
|
|
||||||
struct StorageDummy {
|
struct StorageDummy {
|
||||||
template<class V>
|
template<class V>
|
||||||
void operator()(const char *, V &) {}
|
void operator()(const char *, V &) {}
|
||||||
@ -104,6 +106,10 @@ private:
|
|||||||
cbor.addUtf8(cStr);
|
cbor.addUtf8(cStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeValue(std::string &str) {
|
||||||
|
writeValue(str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
template<class Item>
|
template<class Item>
|
||||||
void writeValue(std::vector<Item> &array) {
|
void writeValue(std::vector<Item> &array) {
|
||||||
cbor.openArray(array.size());
|
cbor.openArray(array.size());
|
||||||
@ -260,3 +266,5 @@ private:
|
|||||||
|
|
||||||
using SubClass = typename _impl::VoidFallback<StorageCborReader, SubClassCRTP>::T;
|
using SubClass = typename _impl::VoidFallback<StorageCborReader, SubClassCRTP>::T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}} // namespace
|
||||||
|
|||||||
2
stfx/ui/html/cbor.min.js
vendored
2
stfx/ui/html/cbor.min.js
vendored
File diff suppressed because one or more lines are too long
@ -11,7 +11,7 @@
|
|||||||
background: linear-gradient(#FFF, #EEE, #CCC);
|
background: linear-gradient(#FFF, #EEE, #CCC);
|
||||||
color: #000;
|
color: #000;
|
||||||
|
|
||||||
font-family: Bahnschrift, 'DIN Alternate', 'Franklin Gothic Medium', 'Nimbus Sans Narrow', sans-serif-condensed, system-ui, sans-serif;
|
font-family: Bahnschrift, 'DIN Alternate', 'Alte DIN 1451 Mittelschrift', 'D-DIN', 'OpenDin', 'Clear Sans', 'Barlow', 'Abel', 'Franklin Gothic Medium', system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
output {
|
output {
|
||||||
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||||
@ -20,26 +20,23 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{=}
|
|
||||||
<h1>{name}</h1>
|
<h1>{name}</h1>
|
||||||
|
<ul>
|
||||||
|
<template @foreach>
|
||||||
|
<li @if="${d => d.$type == 'ParamRange'}">{=}</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
<script src="cbor.min.js"></script>
|
<script src="cbor.min.js"></script>
|
||||||
<script src="matsui-bundle.min.js"></script>
|
<script src="matsui-bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
let state = Matsui.replace(document.body, {name: "..."});
|
||||||
|
state.trackMerges(merge => {
|
||||||
|
window.parent.postMessage(CBOR.encode(merge), '*');
|
||||||
|
});
|
||||||
addEventListener('message', e => {
|
addEventListener('message', e => {
|
||||||
let state = window.state = Matsui.replace(document.body, CBOR.decode(e.data));
|
state.merge(CBOR.decode(e.data));
|
||||||
state.trackMerges(merge => {
|
});
|
||||||
window.parent.postMessage(CBOR.encode(merge), '*');
|
|
||||||
}, true/*async*/, false/*include direct merges*/);
|
|
||||||
|
|
||||||
addEventListener('message', e => {
|
|
||||||
state.merge(CBOR.decode(e.data));
|
|
||||||
});
|
|
||||||
}, {once: true});
|
|
||||||
|
|
||||||
//*
|
|
||||||
window.dispatchEvent(new MessageEvent('message', {
|
|
||||||
data: CBOR.encode({name: "Hello 😀"})
|
|
||||||
}));//*/
|
|
||||||
window.parent.postMessage(CBOR.encode("ready"), '*');
|
window.parent.postMessage(CBOR.encode("ready"), '*');
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
2
stfx/ui/html/matsui-bundle.min.js
vendored
2
stfx/ui/html/matsui-bundle.min.js
vendored
File diff suppressed because one or more lines are too long
299
stfx/ui/web-ui.h
299
stfx/ui/web-ui.h
@ -1,21 +1,296 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "../storage/cbor-walker.h"
|
||||||
|
#include "../storage/storage.h"
|
||||||
|
#include "../param-info.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace stfx {
|
namespace stfx { namespace web {
|
||||||
|
|
||||||
template<typename Sample, template<class, class...> class EffectSTFX, class... ExtraArgs>
|
/* Given an STFX template which (when insantiated) has inheritance like:
|
||||||
struct WebUILibraryEffect : public LibraryEffect<Sample, EffectSTFX, ExtraArgs...> {
|
|
||||||
std::string webPage = "generic.html";
|
|
||||||
int webWidth = 640, webHeight = 480;
|
|
||||||
|
|
||||||
using LibraryEffect<Sample, EffectSTFX, ExtraArgs...>::LibraryEffect;
|
EffectSTFX : Effect
|
||||||
|
|
||||||
void webReceive(const void *message, size_t size) {
|
This produces a template with inheritance like:
|
||||||
std::cout << "received " << size << " bytes from webview\n";
|
|
||||||
}
|
WebSTFX : EffectSTFX : WebBase : Effect
|
||||||
private:
|
|
||||||
using Super = LibraryEffect<Sample, EffectSTFX, ExtraArgs...>;
|
The WebBase template replaces the ParamRange/ParamStepped classes, so that the UI can be updated when they change. The outer WebSTFX class adds methods for forwarding messages between the effect and its webview.
|
||||||
|
*/
|
||||||
|
|
||||||
|
template<template<class, class...> class EffectSTFX>
|
||||||
|
struct WebUIHelper {
|
||||||
|
|
||||||
|
struct MaybeChanged {
|
||||||
|
virtual void maybeChanged() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WebParamScanner {
|
||||||
|
MaybeChanged *effect;
|
||||||
|
|
||||||
|
// Do nothing for most types
|
||||||
|
template<class V>
|
||||||
|
void operator()(const char *key, V &v) {
|
||||||
|
if constexpr (std::is_void_v<decltype(v.state(*this))>) {
|
||||||
|
v.state(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra methods we add for STFX storage
|
||||||
|
void info(const char *, const char *) {}
|
||||||
|
int version(int v) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
template<class PR>
|
||||||
|
RangeParamInfo & range(const char *key, PR ¶m) {
|
||||||
|
param.effect = effect;
|
||||||
|
param.info = std::unique_ptr<RangeParamInfo>{
|
||||||
|
new RangeParamInfo(param)
|
||||||
|
};
|
||||||
|
return *param.info;
|
||||||
|
}
|
||||||
|
template<class PS>
|
||||||
|
SteppedParamInfo & stepped(const char *key, PS ¶m) {
|
||||||
|
param.effect = effect;
|
||||||
|
param.info = std::unique_ptr<SteppedParamInfo>{
|
||||||
|
new SteppedParamInfo(param)
|
||||||
|
};
|
||||||
|
return *param.info;
|
||||||
|
}
|
||||||
|
template<class V>
|
||||||
|
bool changed(const char *key, V &v) {
|
||||||
|
(*this)(key, v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void invalidate(const char *) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Effect>
|
||||||
|
class WebBase : public Effect {
|
||||||
|
using SuperRange = typename Effect::ParamRange;
|
||||||
|
using SuperStepped = typename Effect::ParamStepped;
|
||||||
|
public:
|
||||||
|
|
||||||
|
using Effect::Effect;
|
||||||
|
|
||||||
|
struct ParamRange : public SuperRange {
|
||||||
|
using SuperRange::SuperRange;
|
||||||
|
|
||||||
|
ParamRange & operator=(double v) {
|
||||||
|
SuperRange::operator=(v);
|
||||||
|
effect->maybeChanged();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
double prevV = *this;
|
||||||
|
SuperRange::state(storage);
|
||||||
|
|
||||||
|
if (storage.extra()) {
|
||||||
|
storage.extra("$type", "ParamRange");
|
||||||
|
std::string text, units;
|
||||||
|
info->toString((double)*this, text, units);
|
||||||
|
storage.extra("text", text);
|
||||||
|
storage.extra("textUnits", units);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevV != *this) {
|
||||||
|
effect->maybeChanged();
|
||||||
|
storage.invalidate("text");
|
||||||
|
storage.invalidate("textUnits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct WebParamScanner;
|
||||||
|
MaybeChanged *effect = nullptr;
|
||||||
|
std::unique_ptr<RangeParamInfo> info;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParamStepped : public SuperStepped {
|
||||||
|
using SuperStepped::SuperStepped;
|
||||||
|
|
||||||
|
ParamStepped & operator=(double v) {
|
||||||
|
SuperStepped::operator=(v);
|
||||||
|
effect->maybeChanged();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
int prevV = *this;
|
||||||
|
SuperStepped::state(storage);
|
||||||
|
|
||||||
|
if (storage.extra()) {
|
||||||
|
storage.extra("$type", "ParamStepped");
|
||||||
|
std::string text = info->toString((int)*this);
|
||||||
|
storage.extra("text", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevV != *this) {
|
||||||
|
effect->maybeChanged();
|
||||||
|
storage.invalidate("text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct WebParamScanner;
|
||||||
|
MaybeChanged *effect = nullptr;
|
||||||
|
std::unique_ptr<SteppedParamInfo> info;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WebStateWriter : public storage::StorageCborWriter<false, WebStateWriter> {
|
||||||
|
using Super = storage::StorageCborWriter<false, WebStateWriter>;
|
||||||
|
|
||||||
|
using Super::Super;
|
||||||
|
|
||||||
|
void info(const char *name, const char *desc) {
|
||||||
|
extra("name", name);
|
||||||
|
extra("desc", desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int version(int v) {
|
||||||
|
return v; // we don't use old states, so not worth tracking
|
||||||
|
}
|
||||||
|
|
||||||
|
bool extra() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
void extra(const char *key, V v) {
|
||||||
|
(*this)(key, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class PR>
|
||||||
|
RangeParamIgnore range(const char *key, PR ¶m) {
|
||||||
|
(*this)(key, param);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
template<class PS>
|
||||||
|
SteppedParamIgnore stepped(const char *key, PS ¶m) {
|
||||||
|
(*this)(key, param);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
template<class V>
|
||||||
|
bool changed(const char *key, V &v) {
|
||||||
|
(*this)(key, v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void invalidate(const char *) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Effect, class... ExtraArgs>
|
||||||
|
struct WebSTFX : public EffectSTFX<WebBase<Effect>, ExtraArgs...>, MaybeChanged {
|
||||||
|
// Inherit constructor(s)
|
||||||
|
using Super = EffectSTFX<WebBase<Effect>, ExtraArgs...>;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
WebSTFX(Args... args) : Super(args...) {
|
||||||
|
WebParamScanner scanner{this};
|
||||||
|
this->state(scanner);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string webPage = "html/generic.html";
|
||||||
|
int webWidth = 640, webHeight = 480;
|
||||||
|
|
||||||
|
struct WebMessage {
|
||||||
|
std::vector<unsigned char> bytes;
|
||||||
|
|
||||||
|
WebMessage() {
|
||||||
|
bytes.reserve(256);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sent() {
|
||||||
|
readyToSend.clear();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
friend struct WebSTFX;
|
||||||
|
std::atomic_flag readyToSend = ATOMIC_FLAG_INIT;
|
||||||
|
|
||||||
|
void markReady() {
|
||||||
|
readyToSend.test_and_set();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Poll on the main thread (and directly after any `.webReceive()`), calling `.sent()` after you've sent it, repeat until you get `nullptr`
|
||||||
|
WebMessage * getPendingWebMessage() {
|
||||||
|
auto &message = queue[readIndex];
|
||||||
|
if (!message.readyToSend.test()) return nullptr;
|
||||||
|
if (++readIndex == queue.size()) readIndex = 0;
|
||||||
|
|
||||||
|
if (resetQueue.test()) {
|
||||||
|
// Clear ("send") every message aside from this one
|
||||||
|
for (auto &m : queue) {
|
||||||
|
if (&m == &message) continue;
|
||||||
|
m.sent();
|
||||||
|
}
|
||||||
|
// Ready to start sending messages again, starting from the next index
|
||||||
|
writeIndex.store(readIndex);
|
||||||
|
resetQueue.clear();
|
||||||
|
// any messages (state updates) sent after this won't assume a *newer* state than what we serialise below
|
||||||
|
|
||||||
|
// Replace this one's message with the complete plugin state
|
||||||
|
WebStateWriter storage(message.bytes);
|
||||||
|
this->state(storage);
|
||||||
|
}
|
||||||
|
return &message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call when the webview posts any message back
|
||||||
|
void webReceive(const void *message, size_t size) {
|
||||||
|
auto *bytes = (const unsigned char *)message;
|
||||||
|
signalsmith::cbor::TaggedCborWalker cbor(bytes, bytes + size);
|
||||||
|
|
||||||
|
if (cbor.isUtf8()) {
|
||||||
|
if (cbor.utf8View() == "ready") {
|
||||||
|
LOG_EXPR(cbor.utf8View() == "ready");
|
||||||
|
if (auto *m = getEmptyMessage()) {
|
||||||
|
resetQueue.test_and_set();
|
||||||
|
m->markReady();
|
||||||
|
} // if this fails we're doing a reset anyway, so no worrie
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "received " << size << " bytes from webview\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void maybeChanged() override {
|
||||||
|
std::cout << "web effect maybe changed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<WebMessage> queue = std::vector<WebMessage>(64); // power of 2 so that overflow doesn't mess with the modulo
|
||||||
|
size_t readIndex = 0;
|
||||||
|
std::atomic_flag resetQueue = ATOMIC_FLAG_INIT;
|
||||||
|
|
||||||
|
// Atomic because multiple threads might write
|
||||||
|
std::atomic<size_t> writeIndex = 0;
|
||||||
|
WebMessage * getEmptyMessage() {
|
||||||
|
if (resetQueue.test()) return nullptr;
|
||||||
|
auto reservedIndex = writeIndex.fetch_add(1);
|
||||||
|
auto &message = queue[reservedIndex%queue.size()];
|
||||||
|
if (message.readyToSend.test()) {
|
||||||
|
std::cerr << "Web message queue full - sending entire state instead\n";
|
||||||
|
// When our queue is full, we drop the entire thing and re-send the entire state
|
||||||
|
resetQueue.test_and_set();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
message.bytes.resize(0);
|
||||||
|
return &message;
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
// Use this instead of a plain LibraryEffect
|
||||||
|
template<typename Sample, template<class, class...> class EffectSTFX, class... ExtraArgs>
|
||||||
|
using WebUILibraryEffect = LibraryEffect<Sample, WebUIHelper<EffectSTFX>::template WebSTFX, ExtraArgs...>;
|
||||||
|
|
||||||
|
}} // namespace
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user