diff --git a/CMakeLists.txt b/CMakeLists.txt index dd38c7d..29d9a7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,7 @@ if(DEFINED ANDROID_NDK) set(USE_SPEEX OFF CACHE BOOL "" FORCE) set(USE_G719 OFF CACHE BOOL "" FORCE) set(USE_VORBIS OFF CACHE BOOL "" FORCE) + set(BUILD_FMT ON CACHE BOOL "" FORCE) endif() if (BUILD_SDL) set(SDL_SHARED OFF CACHE BOOL "" FORCE) diff --git a/add-mime-types.sh b/add-mime-types.sh new file mode 100755 index 0000000..f0a0960 --- /dev/null +++ b/add-mime-types.sh @@ -0,0 +1,5 @@ +#!/bin/sh +OLDDIR="$(pwd)" +cd "$(dirname "$0")" +xdg-mime install assets/zsm-mime.xml +cd "$OLDDIR" diff --git a/assets/com.complecwaft.Looper.desktop b/assets/com.complecwaft.Looper.desktop index 9328c4f..b0a40d2 100755 --- a/assets/com.complecwaft.Looper.desktop +++ b/assets/com.complecwaft.Looper.desktop @@ -7,7 +7,7 @@ GenericName=Looping audio player Exec=looper -n %f Icon=looper StartupWMClass=looper;com.complecwaft.Looper;com.complecwaft.Looper.GTK -MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod; +MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;audio/x-zsound; Categories=Audio;AudioVideo; Terminal=false SingleMainWindow=true diff --git a/assets/zsm-mime.xml b/assets/zsm-mime.xml new file mode 100644 index 0000000..d7374c7 --- /dev/null +++ b/assets/zsm-mime.xml @@ -0,0 +1,11 @@ + + + + ZSound file for the Commander X16 + + + + + + + diff --git a/backends/playback/zsm/properties.inc b/backends/playback/zsm/properties.inc new file mode 100644 index 0000000..379722b --- /dev/null +++ b/backends/playback/zsm/properties.inc @@ -0,0 +1,10 @@ +#define BOOL_PROPERTY(name, default_value) _PROPERTY(name, bool, default_value) +#define DOUBLE_PROPERTY(name, default_value) _PROPERTY(name, double, default_value) +BOOL_PROPERTY(pcm_enable, true) +BOOL_PROPERTY(psg_enable, true) +BOOL_PROPERTY(fm_enable, true) +DOUBLE_PROPERTY(pcm_volume, 1.0) +DOUBLE_PROPERTY(psg_volume, 1.0) +DOUBLE_PROPERTY(fm_volume, 1.0) +#undef BOOL_PROPERTY +#undef DOUBLE_PROPERTY diff --git a/backends/playback/zsm/zsm_backend.cpp b/backends/playback/zsm/zsm_backend.cpp index 91cb1d4..c9b497d 100644 --- a/backends/playback/zsm/zsm_backend.cpp +++ b/backends/playback/zsm/zsm_backend.cpp @@ -17,17 +17,26 @@ extern "C" { #define HZ (AUDIO_SAMPLERATE) #define BUFFERS 32 #define _PROPERTY(name, type, default_value) \ -bool ZsmBackend::name() { \ +type ZsmBackend::name() { \ std::optional value_maybe = get(#name); \ if (value_maybe.has_value()) { \ - return resolve_##type(value_maybe.value()); \ + return resolve_value(value_maybe.value()); \ } \ return default_value; \ } -#define BOOL_PROPERTY(name, default_value) _PROPERTY(name, bool, default_value) -BOOL_PROPERTY(pcm_enable, true) -BOOL_PROPERTY(psg_enable, true) -BOOL_PROPERTY(fm_enable, true) + +#include "properties.inc" +#undef _PROPERTY +std::vector ZsmBackend::get_property_list() { + std::vector properties; + properties.push_back(make_property(PropertyType::Boolean, "Enable PCM channel", "pcm_enable")); + properties.push_back(make_property(PropertyType::Boolean, "Enable PSG channels", "psg_enable")); + properties.push_back(make_property(PropertyType::Boolean, "Enable FM channels", "fm_enable")); + properties.push_back(make_property(PropertyType::Double, "Volume of PCM channel", "pcm_volume", make_hint(0.0, 1.0))); + properties.push_back(make_property(PropertyType::Double, "Volume of PSG channels", "psg_volume", make_hint(0.0, 1.0))); + properties.push_back(make_property(PropertyType::Double, "Volume of FM channels", "fm_volume", make_hint(0.0, 1.0))); + return properties; +} void ZsmBackend::load(const char *filename) { memset(&spec, 0, sizeof(spec)); spec.format = AUDIO_S16SYS; @@ -129,6 +138,22 @@ void ZsmBackend::load(const char *filename) { loop_start = this->loop_pos; fm_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, YM_FREQ, AUDIO_S16SYS, 2, PSG_FREQ); DEBUG.writefln("fm_stream: %ld -> %ld", YM_FREQ, PSG_FREQ); +#define _PROPERTY(name, type, default_value) \ + { \ + std::string type_str = #type; \ + google::protobuf::Any value; \ + if (type_str == "bool") { \ + BooleanProperty value_b; \ + value_b.set_value(default_value); \ + value.PackFrom(value_b); \ + } else if (type_str == "double") { \ + DoubleProperty value_d; \ + value_d.set_value(default_value); \ + value.PackFrom(value_d); \ + } \ + property_defaults[#name] = value; \ + } +#include "properties.inc" } extern SDL_AudioSpec obtained; void ZsmBackend::switch_stream(int idx) { @@ -399,3 +424,61 @@ double ZsmBackend::get_position() { int ZsmBackend::get_stream_idx() { return 0; } + +void ZsmBackend::audio_step(size_t samples) { + if (samples == 0) return; + while (remain != 0 && pcm_fifo_avail() < samples) { + if (pcm_read_rate() == 0) break; + if ((--remain) == 0) { + if (islooped) { + cur = loop; + remain = loop_rem; + } else { + break; + } + } + size_t oldpos = file->get_pos(); + uint8_t sample = audio_sample[cur++]; + pcm_write_fifo(sample); + } + samples *= 2; + int16_t *psg_ptr = psg_buf.get_item_sized(samples); + int16_t *pcm_ptr = pcm_buf.get_item_sized(samples); + psg_render(psg_ptr, samples / 2); + pcm_render(pcm_ptr, samples / 2); + int16_t *out_ptr = out_buf.get_item_sized(samples); + // The exact amount of samples needed for the stream. + double ratio = ((double)YM_FREQ) / ((double)PSG_FREQ); + size_t needed_samples = ((size_t)std::floor(samples * ratio)) / 2; + int16_t *ym_ptr = ym_buf.get_item_sized(needed_samples * 2); + YM_stream_update(ym_ptr, needed_samples); + assert(SDL_AudioStreamPut(fm_stream, ym_ptr, needed_samples * 2 * sizeof(int16_t)) == 0); + while (SDL_AudioStreamAvailable(fm_stream) < ((samples + 2) * sizeof(int16_t))) { + YM_stream_update(ym_ptr, 1); + assert(SDL_AudioStreamPut(fm_stream, ym_ptr, 2 * sizeof(int16_t)) == 0); + } + int16_t *ym_resample_ptr = ym_resample_buf.get_item_sized(samples); + ssize_t ym_resample_len = SDL_AudioStreamGet(fm_stream, ym_resample_ptr, (samples + 2) * sizeof(int16_t)); + assert(ym_resample_len >= 0); + ym_resample_len /= sizeof(int16_t); + for (size_t i = 0; i < samples / 2; i++) { + size_t j = i * 2; + int16_t psg[2] = {(int16_t)(psg_ptr[j] >> 1), (int16_t)(psg_ptr[j + 1] >> 1)}; + int16_t pcm[2] = {(int16_t)(pcm_ptr[j] >> 2), (int16_t)(pcm_ptr[j + 1] >> 2)}; + if (!pcm_enable()) memset(pcm, 0, sizeof(pcm)); + if (!psg_enable()) memset(psg, 0, sizeof(psg)); + pcm[0] *= pcm_volume(); + pcm[1] *= pcm_volume(); + psg[0] *= psg_volume(); + psg[1] *= psg_volume(); + int16_t vera[2] = {(int16_t)(psg[0] + pcm[0]), (int16_t)(psg[1] + pcm[1])}; + int16_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]}; + if (!fm_enable()) memset(fm, 0, sizeof(fm)); + fm[0] *= fm_volume(); + fm[1] *= fm_volume(); + int16_t mix[2] = {(int16_t)(vera[0] + (fm[0] >> 1)), (int16_t)(vera[1] + (fm[1] >> 1))}; + out_ptr[j++] = mix[0]; + out_ptr[j++] = mix[1]; + } + audio_buf.push(out_ptr, samples); +} diff --git a/backends/playback/zsm/zsm_backend.hpp b/backends/playback/zsm/zsm_backend.hpp index 2b5cf54..90ecfa7 100644 --- a/backends/playback/zsm/zsm_backend.hpp +++ b/backends/playback/zsm/zsm_backend.hpp @@ -96,60 +96,10 @@ class ZsmBackend : public PlaybackBackend { bool pcm_enable(); bool psg_enable(); bool fm_enable(); - int16_t combine_audio(int16_t a, int16_t b) { - return (int16_t)((((int32_t)a) + ((int32_t)b)) >> 1); - } - void audio_step(size_t samples) { - if (samples == 0) return; - while (remain != 0 && pcm_fifo_avail() < samples) { - if (pcm_read_rate() == 0) break; - if ((--remain) == 0) { - if (islooped) { - cur = loop; - remain = loop_rem; - } else { - break; - } - } - size_t oldpos = file->get_pos(); - uint8_t sample = audio_sample[cur++]; - pcm_write_fifo(sample); - } - samples *= 2; - int16_t *psg_ptr = psg_buf.get_item_sized(samples); - int16_t *pcm_ptr = pcm_buf.get_item_sized(samples); - psg_render(psg_ptr, samples / 2); - pcm_render(pcm_ptr, samples / 2); - int16_t *out_ptr = out_buf.get_item_sized(samples); - // The exact amount of samples needed for the stream. - double ratio = ((double)YM_FREQ) / ((double)PSG_FREQ); - size_t needed_samples = ((size_t)std::floor(samples * ratio)) / 2; - int16_t *ym_ptr = ym_buf.get_item_sized(needed_samples * 2); - YM_stream_update(ym_ptr, needed_samples); - assert(SDL_AudioStreamPut(fm_stream, ym_ptr, needed_samples * 2 * sizeof(int16_t)) == 0); - while (SDL_AudioStreamAvailable(fm_stream) < ((samples + 2) * sizeof(int16_t))) { - YM_stream_update(ym_ptr, 1); - assert(SDL_AudioStreamPut(fm_stream, ym_ptr, 2 * sizeof(int16_t)) == 0); - } - int16_t *ym_resample_ptr = ym_resample_buf.get_item_sized(samples); - ssize_t ym_resample_len = SDL_AudioStreamGet(fm_stream, ym_resample_ptr, (samples + 2) * sizeof(int16_t)); - assert(ym_resample_len >= 0); - ym_resample_len /= sizeof(int16_t); - for (size_t i = 0; i < samples / 2; i++) { - size_t j = i * 2; - int16_t psg[2] = {psg_ptr[j] >> 1, psg_ptr[j + 1] >> 1}; - int16_t pcm[2] = {pcm_ptr[j] >> 1, pcm_ptr[j + 1] >> 1}; - if (!pcm_enable()) memset(pcm, 0, sizeof(pcm)); - if (!psg_enable()) memset(psg, 0, sizeof(psg)); - int16_t vera[2] = {psg[0] + pcm[0], psg[1] + pcm[1]}; - int16_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]}; - if (!fm_enable()) memset(fm, 0, sizeof(fm)); - int16_t mix[2] = {vera[0] + (fm[0] >> 1), vera[1] + (fm[1] >> 1)}; - out_ptr[j++] = mix[0]; - out_ptr[j++] = mix[1]; - } - audio_buf.push(out_ptr, samples); - } + double pcm_volume(); + double psg_volume(); + double fm_volume(); + void audio_step(size_t samples); inline void *reserve(size_t len) { return (void*)audio_buf.reserve(len); } @@ -183,6 +133,7 @@ class ZsmBackend : public PlaybackBackend { inline std::string get_name() override { return "ZSM player"; } + std::vector get_property_list() override; void seek(double position) override; void load(const char *filename) override; void switch_stream(int idx) override; diff --git a/backends/ui/imgui/main.cpp b/backends/ui/imgui/main.cpp index 2ef410c..08d430d 100644 --- a/backends/ui/imgui/main.cpp +++ b/backends/ui/imgui/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "imgui/imgui.h" #include "ui_backend.hpp" #include "thirdparty/CLI11.hpp" #include @@ -141,6 +142,9 @@ void MainLoop::FileLoaded() { SetWindowTitle("Looper"); } streams = playback->get_streams(); + properties = playback->get_property_list(); + boolean_properties.clear(); + double_properties.clear(); } void MainLoop::GuiFunction() { #if defined(__EMSCRIPTEN__)||defined(__ANDROID__) @@ -195,10 +199,10 @@ void MainLoop::GuiFunction() { show_demo_window = !show_demo_window; set_option("ui.imgui.demo_window", show_demo_window); } - ImGui::EndMenu(); if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Edit properties"), nullptr, property_editor)) { property_editor = !property_editor; } + ImGui::EndMenu(); } if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_INFO_CIRCLE, "Main menu", "Help"))) { if (ImGui::MenuItem(_TRI_CTX(ICON_FK_INFO, "Main menu | Help", "About"), nullptr, about_window)) { @@ -304,17 +308,88 @@ void MainLoop::GuiFunction() { ImGui::SetNextWindowDockID(dockid); ImGui::Begin("Property Editor", &property_editor); { - static std::string property_name; - static bool property_value; - ImGui::InputText("Property Name", &property_name); - ImGui::Checkbox("Enabled", &property_value); - if (ImGui::Button("Set")) { - BooleanProperty property; - property.set_value(property_value); - google::protobuf::Any value; - value.PackFrom(property); - playback->set_property(property_name, value); - } + for (auto property : properties) { + ImGui::PushID(property.path().c_str()); + bool valid = false; + switch (property.type()) { + case PropertyType::Double: { + std::optional min; + std::optional max; + double value = 0.0; + if (double_properties.contains(property.path())) { + value = double_properties[property.path()]; + } else { + auto value_to_resolve = playback->get_property(property.path()); + if (value_to_resolve.has_value()) { + value = resolve_value(value_to_resolve.value()); + } + double_properties[property.path()] = value; + } + if (property.has_hint() && property.hint().has_range()) { + auto range = property.hint().range(); + if (range.has_min() && range.has_max()) { + float flt = (float)value; + if (ImGui::SliderFloat(property.path().c_str(), &flt, (float)range.min(), (float)range.max())) { + double_properties[property.path()] = flt; + } + valid = true; + } else { + if (range.has_min()) min = range.min(); + if (range.has_max()) max = range.max(); + } + } + if (!valid) { + ImGui::InputDouble(property.path().c_str(), &value); + if (min.has_value() && value < min) { + value = min.value(); + } + if (max.has_value() && value > max) { + value = max.value(); + } + double_properties[property.path()] = value; + valid = true; + } + } break; + case PropertyType::Boolean: { + bool value = false; + if (boolean_properties.contains(property.path())) { + value = boolean_properties[property.path()]; + } else { + auto value_to_resolve = playback->get_property(property.path()); + if (value_to_resolve.has_value()) { + value = resolve_value(value_to_resolve.value()); + } + boolean_properties[property.path()] = value; + } + if (ImGui::Checkbox(property.path().c_str(), &value)) { + boolean_properties[property.path()] = value; + } + valid = true; + } break; + } + if (valid) { + ImGui::SameLine(); + if (ImGui::Button("Set")) { + switch (property.type()) { + case PropertyType::Double: { + DoubleProperty property_d; + property_d.set_value(double_properties[property.path()]); + google::protobuf::Any value; + value.PackFrom(property_d); + playback->set_property(property.path(), value); + } break; + case PropertyType::Boolean: { + BooleanProperty property_b; + property_b.set_value(boolean_properties[property.path()]); + google::protobuf::Any value; + value.PackFrom(property_b); + playback->set_property(property.path(), value); + } break; + } + } + } + ImGui::PopID(); + } } ImGui::End(); } diff --git a/backends/ui/imgui/main.h b/backends/ui/imgui/main.h index d368792..5f68451 100644 --- a/backends/ui/imgui/main.h +++ b/backends/ui/imgui/main.h @@ -12,7 +12,7 @@ #include #include #include - +#include #include #include #include @@ -53,6 +53,9 @@ class MainLoop : public RendererBackend { UIBackend *cur_backend; friend class ImGuiUIBackend; std::atomic_bool exit_flag; + std::map boolean_properties; + std::map double_properties; + std::vector properties; std::vector streams; public: Playback *playback; diff --git a/ipc/common.proto b/ipc/common.proto index 70488ef..c527f84 100644 --- a/ipc/common.proto +++ b/ipc/common.proto @@ -27,6 +27,36 @@ message BooleanProperty { message BytesProperty { bytes value = 1; }; +message PropertyHintRange { + optional double min = 1; + optional double max = 2; +}; +message PropertyHintEnum { + repeated string data = 1; +}; +message PropertyHint { + oneof hint { + PropertyHintRange range = 1; + fixed64 max_length = 2; + PropertyHintEnum enum = 3; + }; +}; +enum PropertyType { + Double = 0; + Boolean = 1; + String = 2; + Bytes = 3; + Stream = 4; + AudioSpec = 5; + StreamID = 6; +} +message Property { + PropertyType type = 1; + optional PropertyId id = 5; + optional string path = 2; + optional string name = 4; + optional PropertyHint hint = 3; +}; message StreamId { uint64 id = 504; }; @@ -38,9 +68,6 @@ message Stream { message StreamList { repeated Stream streams = 500; } -message Position { - double pos = 508; -}; enum EndianID { LITTLE = 0; BIG = 1; diff --git a/ipc/internal.proto b/ipc/internal.proto index 69faf73..f16fa79 100644 --- a/ipc/internal.proto +++ b/ipc/internal.proto @@ -31,6 +31,9 @@ message SimpleAckResponse { message InitCommand { string filename = 1; uint64 idx = 2; +}; +message GetPropertyListCommand { + }; message RPCCall { uint64 cmdid = 1; @@ -41,8 +44,12 @@ message RPCCall { ResetProperty reset = 5; QuitCmd quit = 6; InitCommand init = 7; + GetPropertyListCommand get_property_list = 8; }; }; +message PropertyList { + repeated Property list = 1; +} message RPCResponse { uint64 cmdid = 1; oneof response { @@ -51,5 +58,6 @@ message RPCResponse { PropertyData data = 4; ResetResponse reset = 5; ErrorResponse err = 6; + PropertyList property_list = 7; }; }; diff --git a/playback.cpp b/playback.cpp index 0addb19..3db224e 100644 --- a/playback.cpp +++ b/playback.cpp @@ -684,3 +684,7 @@ std::optional PlaybackInstance::get_property(std::string std::optional PlaybackInstance::reset_property(std::string path) { return {}; } +std::vector PlaybackInstance::get_property_list() { + if (process == nullptr) return {}; + return process->get_property_list(); +} diff --git a/playback.h b/playback.h index e1fd6d1..1790c8c 100644 --- a/playback.h +++ b/playback.h @@ -24,6 +24,7 @@ extern "C" { #include "config.hpp" #include "playback_process.hpp" #include +#include using namespace soundtouch; using std::span; using std::optional; @@ -220,6 +221,9 @@ class Playback { LoopFunction(); } } + virtual std::vector get_property_list() { + return {}; + } static Playback *Create(bool *daemon_found, bool daemon = false); }; @@ -333,6 +337,7 @@ public: void set_property(std::string path, google::protobuf::Any) override; std::optional get_property(std::string path) override; std::optional reset_property(std::string path) override; + std::vector get_property_list() override; float volume; float speed; float tempo; diff --git a/playback_backend.hpp b/playback_backend.hpp index 98cfafe..adaa8be 100644 --- a/playback_backend.hpp +++ b/playback_backend.hpp @@ -15,16 +15,6 @@ struct PlaybackStream { int id; }; class PlaybackBackendHelper; -enum PropertyType { - Double, - String, - Bytes, - Boolean -}; -struct Property { - PropertyType type; - std::string path; -}; class PlaybackBackend { double prevRate; size_t minSamples; @@ -87,6 +77,9 @@ class PlaybackBackend { inline double get_rate() { return this->rate; } + inline virtual std::vector get_property_list() { + return std::vector(); + } inline virtual bool set(std::string path, google::protobuf::Any value) {properties[path] = value; return true;} inline virtual std::optional get(std::string path) {return properties.contains(path) ? properties[path] : std::optional();} inline virtual std::optional reset(std::string path) { diff --git a/playback_process.cpp b/playback_process.cpp index 713e241..a6f7924 100644 --- a/playback_process.cpp +++ b/playback_process.cpp @@ -296,6 +296,17 @@ MaybeError PlaybackProcessServiceImpl::Quit(const QuitCmd *request) { cur_backend_lock.get_unsafe()->cleanup(); return MaybeError(); } +PropertyList PlaybackProcessServiceImpl::GetPropertyList(const GetPropertyListCommand *cmd) { + std::vector list; + { + list = cur_backend_lock.lock()->get_property_list(); + } + PropertyList output; + for (auto el : list) { + output.add_list()->CopyFrom(el); + } + return output; +} MaybeError PlaybackProcessServiceImpl::Init(const InitCommand *cmd) { MaybeError ret; MaybeError *response = &ret; @@ -485,6 +496,9 @@ RPCResponse PlaybackProcess::handle_command(RPCCall &call) { } else { resp.mutable_render()->CopyFrom(output.output()); } + } else if (call.has_get_property_list()) { + PropertyList output = impl.GetPropertyList(&call.get_property_list()); + resp.mutable_property_list()->CopyFrom(output); } if (!resp.has_ack()) { delete ack; @@ -701,3 +715,18 @@ double PlaybackProcess::get_cached_rate() { void PlaybackProcess::set_rate(double value) { set_property_double(PropertyId::PlaybackRate, value); } +std::vector PlaybackProcess::get_property_list() { + RPCCall call; + GetPropertyListCommand cmd; + call.mutable_get_property_list()->CopyFrom(cmd); + RPCResponse resp = SendCommand(&call); + if (resp.has_property_list()) { + std::vector output; + for (size_t i = 0; i < resp.property_list().list_size(); i++) { + output.push_back(resp.property_list().list(i)); + } + return output; + } else { + return std::vector(); + } +} diff --git a/playback_process.hpp b/playback_process.hpp index a274c06..380de52 100644 --- a/playback_process.hpp +++ b/playback_process.hpp @@ -30,6 +30,7 @@ class PlaybackProcessServiceImpl { ResetResponse Reset(const ResetProperty *request); MaybeError Quit(const QuitCmd *request); MaybeError Init(const InitCommand *cmd); + PropertyList GetPropertyList(const GetPropertyListCommand *cmd); }; //#define DEBUG_PRINT_IPC void print_ipc_message(const google::protobuf::Message &msg, size_t level = 0); @@ -118,6 +119,7 @@ class PlaybackProcess { std::optional get_property(std::string path); bool set_property(std::string path, google::protobuf::Any value); + std::vector get_property_list(); PlaybackProcess(std::string filename, int idx = 0); ~PlaybackProcess(); }; diff --git a/util.cpp b/util.cpp index 2500ed6..3be20a1 100644 --- a/util.cpp +++ b/util.cpp @@ -179,4 +179,27 @@ int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_d SDL_FreeAudioStream(stream); return retval; -} \ No newline at end of file +} +PropertyHint make_hint(double min, double max) { + PropertyHint output; + auto range = output.mutable_range(); + range->set_min(min); + range->set_max(max); + return output; +} +Property make_property(PropertyType type, std::string name, PropertyId id, std::optional path, std::optional hint) { + Property output; + output.set_id(id); + output.set_name(name); + output.set_type(type); + if (path.has_value()) output.set_path(path.value()); + if (hint.has_value()) output.mutable_hint()->CopyFrom(hint.value()); + return output; +} +Property make_property(PropertyType type, std::string name, std::string path, std::optional hint) { + return make_property(type, name, PropertyId::BackendSpecific, path, hint) +} +Property make_property(PropertyType type, std::string name, PropertyId id, std::optional hint) { + return make_property(type, name, id, {}, hint) + +} diff --git a/util.hpp b/util.hpp index ebb9b1c..f4f9b22 100644 --- a/util.hpp +++ b/util.hpp @@ -334,6 +334,9 @@ class DynPtr { DynPtr(size_t len) : DynPtr() { resize(len); } + DynPtr(std::string input) : DynPtr(input.length()) { + memcpy(this->ptr, input.data(), input.length()); + } /// @brief Gets the pointer, but does not reallocate it. /// @param T The type of pointer to return. May be void to return void*. /// @returns The pointer, owned by the DynPtr object. @@ -493,28 +496,65 @@ inline T *resolve_any(google::protobuf::Any value) { value.UnpackTo(output); return output; } -inline bool resolve_bool(google::protobuf::Any value) { +template +inline T resolve_value(google::protobuf::Any value) { + throw std::exception(); +} +template<> +inline bool resolve_value(google::protobuf::Any value) { BooleanProperty *property = resolve_any(value); bool output = property->value(); delete property; return output; } -inline std::string resolve_string(google::protobuf::Any value) { +template<> +inline std::string resolve_value(google::protobuf::Any value) { StringProperty *property = resolve_any(value); std::string output = property->value(); delete property; return output; } -inline std::string resolve_bytes(google::protobuf::Any value) { +template<> +inline DynPtr resolve_value(google::protobuf::Any value) { BytesProperty *property = resolve_any(value); std::string output = property->value(); delete property; - return output; + return DynPtr(output); } -inline double resolve_double(google::protobuf::Any value) { +template<> +inline double resolve_value(google::protobuf::Any value) { DoubleProperty *property = resolve_any(value); double output = property->value(); delete property; return output; } +template +inline google::protobuf::Any value_to_any(T value) { + google::protobuf::Any output; + output.PackFrom(value); + return output; +} +template<> +inline google::protobuf::Any value_to_any(double value) { + DoubleProperty output; + output.set_value(value); + return value_to_any(output); +} + +template<> +inline google::protobuf::Any value_to_any(bool value) { + BooleanProperty output; + output.set_value(value); + return value_to_any(output); +} + +template<> +inline google::protobuf::Any value_to_any(std::string value) { + StringProperty output; + output.set_value(value); + return value_to_any(output); +} +Property make_property(PropertyType type, std::string name, PropertyId id, std::optional hint); +Property make_property(PropertyType type, std::string name, std::string path, std::optional hint = {}); +Property make_property(PropertyType type, std::string name, PropertyId id = PropertyId::BackendSpecific, std::optional path = {}, std::optional hint = {});