From 335135b9d8707bc8bf9ef782ebbba67f3f2fa06c Mon Sep 17 00:00:00 2001 From: jatin Date: Thu, 28 Nov 2024 00:25:02 -0800 Subject: [PATCH 01/20] Re-working plugin state serialization --- .../JSONUtils/chowdsp_JSONAdaptors.h | 52 +++++++++++++++++++ .../JSONUtils/chowdsp_StringAdapter.h | 21 -------- modules/common/chowdsp_json/chowdsp_json.h | 3 +- .../ParamUtils/chowdsp_ParameterTypes.cpp | 2 +- .../ParamUtils/chowdsp_ParameterTypes.h | 10 +++- .../Backend/chowdsp_NonParamState.cpp | 18 +++++++ .../Backend/chowdsp_ParamHolder.cpp | 40 ++++++++++++-- .../Backend/chowdsp_ParamHolder.h | 2 + .../Backend/chowdsp_ParameterTypeHelpers.h | 9 ++++ .../Backend/chowdsp_PluginStateImpl.cpp | 20 +++++++ .../Backend/chowdsp_StateValue.h | 21 ++++++++ .../chowdsp_plugin_state.h | 4 ++ .../Backend/chowdsp_PresetState.cpp | 21 ++++++++ .../Backend/chowdsp_PresetState.h | 8 +++ tests/common_tests/CMakeLists.txt | 1 + .../chowdsp_json_test/JSONTest.cpp | 15 ++++++ .../StateSerializationTest.cpp | 10 ++-- 17 files changed, 222 insertions(+), 35 deletions(-) create mode 100644 modules/common/chowdsp_json/JSONUtils/chowdsp_JSONAdaptors.h delete mode 100644 modules/common/chowdsp_json/JSONUtils/chowdsp_StringAdapter.h diff --git a/modules/common/chowdsp_json/JSONUtils/chowdsp_JSONAdaptors.h b/modules/common/chowdsp_json/JSONUtils/chowdsp_JSONAdaptors.h new file mode 100644 index 000000000..27ece8209 --- /dev/null +++ b/modules/common/chowdsp_json/JSONUtils/chowdsp_JSONAdaptors.h @@ -0,0 +1,52 @@ +#pragma once + +#if JUCE_MODULE_AVAILABLE_chowdsp_version +#include +#endif + +#ifndef DOXYGEN +namespace nlohmann +{ +/** Adapter so that nlohmann::json can serialize juce::String */ +template <> +struct adl_serializer +{ + static void to_json (json& j, const juce::String& s) + { + j = s.toUTF8(); + } + + static void from_json (const json& j, juce::String& s) + { + s = j.get(); + } +}; + +#if JUCE_MODULE_AVAILABLE_chowdsp_version +/** Adapter so that nlohmann::json can serialize chowdsp::Version */ +template <> +struct adl_serializer<::chowdsp::Version> +{ + static void to_json (json& j, const ::chowdsp::Version& version) + { + j = version.getVersionHint(); + } + + static void from_json (const json& j, ::chowdsp::Version& version) + { + if (! j.is_number_integer()) + { + version = {}; + return; + } + + const auto versionHint = j.get(); + const auto major = versionHint / 10000; + const auto minor = (versionHint % 10000) / 100; + const auto patch = versionHint % 100; + version = { major, minor, patch }; + } +}; +#endif +} // namespace nlohmann +#endif diff --git a/modules/common/chowdsp_json/JSONUtils/chowdsp_StringAdapter.h b/modules/common/chowdsp_json/JSONUtils/chowdsp_StringAdapter.h deleted file mode 100644 index 2cf2eb216..000000000 --- a/modules/common/chowdsp_json/JSONUtils/chowdsp_StringAdapter.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#ifndef DOXYGEN -namespace nlohmann -{ -/** Adapter so that nlohmann::json can serialize juce::String */ -template <> -struct adl_serializer -{ - static void to_json (json& j, const juce::String& s) - { - j = s.toUTF8(); - } - - static void from_json (const json& j, juce::String& s) - { - s = j.get(); - } -}; -} // namespace nlohmann -#endif diff --git a/modules/common/chowdsp_json/chowdsp_json.h b/modules/common/chowdsp_json/chowdsp_json.h index 26ded9f65..d49f7a24c 100644 --- a/modules/common/chowdsp_json/chowdsp_json.h +++ b/modules/common/chowdsp_json/chowdsp_json.h @@ -32,6 +32,5 @@ namespace chowdsp using json = nlohmann::json; } // namespace chowdsp -#include "JSONUtils/chowdsp_StringAdapter.h" - +#include "JSONUtils/chowdsp_JSONAdaptors.h" #include "JSONUtils/chowdsp_JSONUtils.h" diff --git a/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.cpp b/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.cpp index 646a6dafa..9ccc409c3 100644 --- a/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.cpp +++ b/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.cpp @@ -33,7 +33,7 @@ FloatParameter::FloatParameter (const ParameterID& parameterID, { return valueToTextFunction (v); }) .withValueFromStringFunction (std::move (textToValueFunction))), #endif - unsnappedDefault (valueRange.convertTo0to1 (defaultFloatValue)), + defaultValueInRange (defaultFloatValue), normalisableRange (valueRange) { } diff --git a/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.h b/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.h index 96afdcfc7..1f97afac3 100644 --- a/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.h +++ b/modules/plugin/chowdsp_parameters/ParamUtils/chowdsp_ParameterTypes.h @@ -64,7 +64,10 @@ class FloatParameter : public juce::AudioParameterFloat, void setParameterValue (float newValue) { AudioParameterFloat::operator= (newValue); } /** Returns the default value for the parameter. */ - float getDefaultValue() const override { return unsnappedDefault; } + float getDefaultValue() const override { return normalisableRange.convertTo0to1 (defaultValueInRange); } + + /** Returns the default value for the parameter (in range). */ + float getDefault() const { return defaultValueInRange; } /** TRUE! */ bool supportsMonophonicModulation() override { return true; } @@ -79,7 +82,7 @@ class FloatParameter : public juce::AudioParameterFloat, operator float() const noexcept { return getCurrentValue(); } // NOSONAR, NOLINT(google-explicit-constructor): we want to be able to do implicit conversion here private: - const float unsnappedDefault; + const float defaultValueInRange; const juce::NormalisableRange normalisableRange; float modulationAmount = 0.0f; @@ -180,6 +183,9 @@ class BoolParameter : public juce::AudioParameterBool, */ void setParameterValue (bool newValue) { AudioParameterBool::operator= (newValue); } + /** Returns the default value for the parameter (in range). */ + bool getDefault() const { return static_cast (*this).getDefaultValue() > 0.5f; } + private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BoolParameter) }; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index c0d609de5..670dd1173 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -44,15 +44,32 @@ inline void NonParamState::validateStateValues() const template typename Serializer::SerializedType NonParamState::serialize (const NonParamState& state) { +#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + auto serial = nlohmann::json::object(); + for (const auto& value : state.values) + serial[value->name] = value->serialize(); + return serial; +#else auto serial = Serializer::createBaseElement(); for (const auto& value : state.values) value->serialize (serial); return serial; +#endif } template void NonParamState::deserialize (typename Serializer::DeserializedType deserial, const NonParamState& state) { +#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + for (auto& value : state.values) + { + auto iter = deserial.find (value->name); + if (iter != deserial.end()) + value->deserialize (*iter); + else + value->reset(); + } +#else std::vector namesThatHaveBeenDeserialized {}; if (const auto numNamesAndVals = Serializer::getNumChildElements (deserial); numNamesAndVals % 2 == 0) { @@ -85,5 +102,6 @@ void NonParamState::deserialize (typename Serializer::DeserializedType deserial, value->reset(); } } +#endif } } // namespace chowdsp diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index 0a250ced6..d2daca053 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -13,7 +13,9 @@ inline ParamHolder::ParamHolder (ParamHolder* parent, std::string_view phName, b return OptionalPointer { static_cast (1024) }; }(), }, +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION allParamsMap { MapAllocator { arena } }, +#endif name { arena::alloc_string (*arena, phName) }, isOwning { phIsOwning } { @@ -21,7 +23,9 @@ inline ParamHolder::ParamHolder (ParamHolder* parent, std::string_view phName, b inline ParamHolder::ParamHolder (ChainedArenaAllocator& alloc, std::string_view phName, bool phIsOwning) : arena { &alloc, false }, +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION allParamsMap { MapAllocator { arena } }, +#endif name { arena::alloc_string (*arena, phName) }, isOwning { phIsOwning } { @@ -55,10 +59,12 @@ template std::enable_if_t, void> ParamHolder::add (OptionalPointer& floatParam, OtherParams&... others) { - const auto paramID = toStringView (floatParam->paramID); ThingPtr paramPtr { reinterpret_cast (isOwning ? floatParam.release() : floatParam.get()), getFlags (FloatParam, isOwning) }; +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + const auto paramID = toStringView (floatParam->paramID); allParamsMap.insert ({ paramID, paramPtr }); +#endif things.insert (std::move (paramPtr)); add (others...); } @@ -67,10 +73,12 @@ template std::enable_if_t, void> ParamHolder::add (OptionalPointer& choiceParam, OtherParams&... others) { - const auto paramID = toStringView (choiceParam->paramID); ThingPtr paramPtr { reinterpret_cast (isOwning ? choiceParam.release() : choiceParam.get()), getFlags (ChoiceParam, isOwning) }; +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + const auto paramID = toStringView (choiceParam->paramID); allParamsMap.insert ({ paramID, paramPtr }); +#endif things.insert (std::move (paramPtr)); add (others...); } @@ -79,10 +87,12 @@ template std::enable_if_t, void> ParamHolder::add (OptionalPointer& boolParam, OtherParams&... others) { - const auto paramID = toStringView (boolParam->paramID); ThingPtr paramPtr { reinterpret_cast (isOwning ? boolParam.release() : boolParam.get()), getFlags (BoolParam, isOwning) }; +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + const auto paramID = toStringView (boolParam->paramID); allParamsMap.insert ({ paramID, paramPtr }); +#endif things.insert (std::move (paramPtr)); add (others...); } @@ -114,12 +124,13 @@ std::enable_if_t, void> template void ParamHolder::add (ParamHolder& paramHolder, OtherParams&... others) { +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION // This should be the parent of the holder being added. - // Maybe we can relax this restriction if we no longer need the allParamsMap. jassert (arena == paramHolder.arena); allParamsMap.merge (paramHolder.allParamsMap); jassert (paramHolder.allParamsMap.empty()); // assuming no duplicate parameter IDs, all the parameters should be moved in the merge! +#endif things.insert (ThingPtr { reinterpret_cast (¶mHolder), Holder }); add (others...); } @@ -133,6 +144,7 @@ std::enable_if_t, void> add (others...); } + [[nodiscard]] inline int ParamHolder::count() const noexcept { auto count = static_cast (things.count()); @@ -266,6 +278,16 @@ size_t ParamHolder::doForAllParameters (Callable&& callable, size_t index) const template typename Serializer::SerializedType ParamHolder::serialize (const ParamHolder& paramHolder) { +#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + auto serial = nlohmann::json::object(); + paramHolder.doForAllParameters ( + [&serial] (auto& param, size_t) + { + const auto paramID = toStringView (param.paramID); + serial[paramID] = ParameterTypeHelpers::getValue (param); + }); + return serial; +#else auto serial = Serializer::createBaseElement(); paramHolder.doForAllParameters ( [&serial] (auto& param, size_t) @@ -273,11 +295,20 @@ typename Serializer::SerializedType ParamHolder::serialize (const ParamHolder& p ParameterTypeHelpers::serializeParameter (serial, param); }); return serial; +#endif } template void ParamHolder::deserialize (typename Serializer::DeserializedType deserial, ParamHolder& paramHolder) { +#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + paramHolder.doForAllParameters ( + [&deserial] (auto& param, size_t) + { + const auto paramID = toStringView (param.paramID); + ParameterTypeHelpers::setValue (deserial.value (paramID, ParameterTypeHelpers::getDefaultValue (param)), param); + }); +#else std::vector paramIDsThatHaveBeenDeserialized {}; if (const auto numParamIDsAndVals = Serializer::getNumChildElements (deserial); numParamIDsAndVals % 2 == 0) { @@ -336,6 +367,7 @@ void ParamHolder::deserialize (typename Serializer::DeserializedType deserial, P ParameterTypeHelpers::resetParameter (param); }); } +#endif } inline void ParamHolder::applyVersionStreaming (const Version& version) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h index bf6961975..85e498c52 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h @@ -139,11 +139,13 @@ class ParamHolder return static_cast (type | (shouldDelete ? ShouldDelete : 0)); } +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION using MapKey = std::string_view; using MapValue = ThingPtr; using MapAllocator = STLArenaAllocator, ChainedArenaAllocator>; using AllParamsMap = std::unordered_map, std::equal_to<>, MapAllocator>; AllParamsMap allParamsMap; +#endif std::string_view name; bool isOwning; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParameterTypeHelpers.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParameterTypeHelpers.h index 9c887485f..43615910c 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParameterTypeHelpers.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParameterTypeHelpers.h @@ -50,6 +50,15 @@ namespace ParameterTypeHelpers return param.getIndex(); } + template + ParameterElementType getDefaultValue (const ParamType& param) + { + if constexpr (std::is_base_of_v || std::is_base_of_v) + return param.getDefault(); + else if constexpr (std::is_base_of_v) + return param.getDefaultIndex(); + } + /** Resets a parameter to it's default value. */ inline void resetParameter (juce::AudioProcessorParameter& param) { diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index acb2d4b09..7bfe07d20 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -42,6 +42,17 @@ template typename Serializer::SerializedType PluginStateImpl::serialize (const PluginStateImpl& object) { +#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + return { +#if defined JucePlugin_VersionString + { "version", currentPluginVersion }, +#else + { "version", chowdsp::Version {} }, +#endif + { "params", Serializer::template serialize (object.params) }, + { "non-params", Serializer::template serialize (object.nonParams) }, + }; +#else auto serial = Serializer::createBaseElement(); #if defined JucePlugin_VersionString @@ -51,6 +62,7 @@ typename Serializer::SerializedType PluginStateImpl (object.nonParams)); Serializer::addChildElement (serial, Serializer::template serialize (object.params)); return serial; +#endif } /** Deserializer */ @@ -58,6 +70,13 @@ template void PluginStateImpl::deserialize (typename Serializer::DeserializedType serial, PluginStateImpl& object) { +#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION + jassert (serial.find ("version") != serial.end()); + object.pluginStateVersion = serial.value ("version", Version {}); + + Serializer::template deserialize (serial.at ("non-params"), object.nonParams); + Serializer::template deserialize (serial.at ("params"), object.params); +#else enum { #if defined JucePlugin_VersionString @@ -83,6 +102,7 @@ void PluginStateImpl::deserialize Serializer::template deserialize (Serializer::getChildElement (serial, nonParamStateChildIndex), object.nonParams); Serializer::template deserialize (Serializer::getChildElement (serial, paramStateChildIndex), object.params); +#endif } template diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h index 3001625b2..da0ca1c55 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h @@ -11,8 +11,13 @@ struct StateValueBase virtual void reset() {} +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION virtual void serialize (JSONSerializer::SerializedType&) const {} virtual void deserialize (JSONSerializer::DeserializedType) {} +#else + [[nodiscard]] virtual nlohmann::json serialize () const { return {}; } + virtual void deserialize (const nlohmann::json&) {} +#endif const std::string_view name {}; Broadcaster changeBroadcaster {}; @@ -69,6 +74,7 @@ struct StateValue : StateValueBase /** Resets the value to its default state */ void reset() override { set (defaultValue); } +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION /** JSON Serializer */ void serialize (JSONSerializer::SerializedType& serial) const override { @@ -80,10 +86,24 @@ struct StateValue : StateValueBase { deserialize (deserial, *this); } +#else + /** JSON Serializer */ + [[nodiscard]] nlohmann::json serialize () const override + { + return get(); + } + + /** JSON Deserializer */ + void deserialize (const nlohmann::json& deserial) override + { + set (deserial.get()); + } +#endif const element_type defaultValue; private: +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION template static void serialize (typename Serializer::SerializedType& serial, const StateValue& value) { @@ -98,6 +118,7 @@ struct StateValue : StateValueBase Serialization::deserialize (deserial, val); value.set (val); } +#endif T currentValue; diff --git a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h index 22919c92c..54d39c458 100644 --- a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h +++ b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h @@ -20,6 +20,10 @@ BEGIN_JUCE_MODULE_DECLARATION #pragma once +#ifndef CHOWDSP_USE_LEGACY_STATE_SERIALIZATION +#define CHOWDSP_USE_LEGACY_STATE_SERIALIZATION 0 +#endif + #include #include #include diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp index ac4ddfa9e..af27fe8e7 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp @@ -37,6 +37,7 @@ void PresetState::reset() set ({}); } +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION void PresetState::serialize (JSONSerializer::SerializedType& serial) const { JSONSerializer::addChildElement (serial, name); @@ -56,6 +57,26 @@ void PresetState::deserialize (JSONSerializer::DeserializedType deserial) set (PresetPtr { deserial }); } +#else +nlohmann::json PresetState::serialize () const +{ + if (preset != nullptr) + return preset->toJson(); + + return {}; +} + +void PresetState::deserialize (const nlohmann::json& deserial) +{ + if (deserial.is_null()) + { + reset(); + return; + } + + set (PresetPtr { deserial }); +} +#endif bool operator== (const PresetState& presetState, std::nullptr_t) { diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h index 94910732f..431573a53 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h @@ -41,11 +41,19 @@ class PresetState : public StateValueBase /** Internal use only! */ void reset() override; +#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION /** Internal use only! */ void serialize (JSONSerializer::SerializedType& serial) const override; /** Internal use only! */ void deserialize (JSONSerializer::DeserializedType deserial) override; +#else + /** Internal use only! */ + [[nodiscard]] nlohmann::json serialize () const override; + + /** Internal use only! */ + void deserialize (const nlohmann::json& deserial) override; +#endif private: PresetPtr preset {}; diff --git a/tests/common_tests/CMakeLists.txt b/tests/common_tests/CMakeLists.txt index 52a0b547d..2f39384b3 100644 --- a/tests/common_tests/CMakeLists.txt +++ b/tests/common_tests/CMakeLists.txt @@ -6,6 +6,7 @@ setup_juce_lib(common_tests_lib chowdsp::chowdsp_logging chowdsp::chowdsp_json chowdsp::chowdsp_units + chowdsp::chowdsp_version ) add_subdirectory(chowdsp_core_test) diff --git a/tests/common_tests/chowdsp_json_test/JSONTest.cpp b/tests/common_tests/chowdsp_json_test/JSONTest.cpp index 524d2dd09..445644c63 100644 --- a/tests/common_tests/chowdsp_json_test/JSONTest.cpp +++ b/tests/common_tests/chowdsp_json_test/JSONTest.cpp @@ -14,6 +14,21 @@ TEST_CASE ("JSON Test", "[common][json]") REQUIRE_MESSAGE (returnString == testString, "JSON serialized string is incorrect!"); } + SECTION ("Version Serialization Test") + { + { + chowdsp::json parent; + chowdsp::Version testVersion { 2, 5, 7 }; + parent["key"] = testVersion; + auto returnVersion = parent["key"].get(); + REQUIRE (returnVersion == testVersion); + } + { + const auto badVersion = chowdsp::json::object().get(); + REQUIRE (badVersion == chowdsp::Version{}); + } + } + SECTION ("JSON File Test") { chowdsp::json jTest = { diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp index c7dbafa95..07488fda9 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp @@ -185,11 +185,11 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") um.beginNewTransaction(); um.perform (new DummyAction {}); state.deserialize (block); - REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); - REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); - REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); - REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); - REQUIRE_MESSAGE (! um.canUndo(), "Undo manager was not cleared after loading new state!"); + // REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); + // REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); + // REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); + // REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); + // REQUIRE_MESSAGE (! um.canUndo(), "Undo manager was not cleared after loading new state!"); } SECTION ("Save/Load Non-Parameters Test") From 5cd410cd12f1d6f7f6e44bbe327ef4977902ca6f Mon Sep 17 00:00:00 2001 From: jatin Date: Thu, 28 Nov 2024 00:58:02 -0800 Subject: [PATCH 02/20] Remove dead comments --- .../StateSerializationTest.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp index 07488fda9..c7dbafa95 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp @@ -185,11 +185,11 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") um.beginNewTransaction(); um.perform (new DummyAction {}); state.deserialize (block); - // REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); - // REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); - // REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); - // REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); - // REQUIRE_MESSAGE (! um.canUndo(), "Undo manager was not cleared after loading new state!"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); + REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); + REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); + REQUIRE_MESSAGE (! um.canUndo(), "Undo manager was not cleared after loading new state!"); } SECTION ("Save/Load Non-Parameters Test") From 2660c1471a84db5d15911160b442d6da2f244193 Mon Sep 17 00:00:00 2001 From: jatin Date: Fri, 29 Nov 2024 12:18:58 -0800 Subject: [PATCH 03/20] Better backrds compatibility --- .../Backend/chowdsp_NonParamState.cpp | 39 +++--- .../Backend/chowdsp_NonParamState.h | 9 +- .../Backend/chowdsp_ParamHolder.cpp | 91 +++--------- .../Backend/chowdsp_ParamHolder.h | 17 +-- .../Backend/chowdsp_PluginState.h | 4 +- .../Backend/chowdsp_PluginStateImpl.cpp | 91 ++++++------ .../Backend/chowdsp_PluginStateImpl.h | 14 +- .../Backend/chowdsp_StateValue.h | 36 ----- .../chowdsp_plugin_state.h | 4 - .../StateSerializationTest.cpp | 132 +++++++++++++++++- .../VersionStreamingTest.cpp | 22 ++- 11 files changed, 247 insertions(+), 212 deletions(-) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index 670dd1173..640201cb0 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -41,42 +41,24 @@ inline void NonParamState::validateStateValues() const #endif } -template -typename Serializer::SerializedType NonParamState::serialize (const NonParamState& state) +inline json NonParamState::serialize (const NonParamState& state) { -#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION auto serial = nlohmann::json::object(); for (const auto& value : state.values) serial[value->name] = value->serialize(); return serial; -#else - auto serial = Serializer::createBaseElement(); - for (const auto& value : state.values) - value->serialize (serial); - return serial; -#endif } -template -void NonParamState::deserialize (typename Serializer::DeserializedType deserial, const NonParamState& state) +inline void NonParamState::legacy_deserialize (const json& deserial, const NonParamState& state) { -#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - for (auto& value : state.values) - { - auto iter = deserial.find (value->name); - if (iter != deserial.end()) - value->deserialize (*iter); - else - value->reset(); - } -#else + using Serializer = JSONSerializer; std::vector namesThatHaveBeenDeserialized {}; if (const auto numNamesAndVals = Serializer::getNumChildElements (deserial); numNamesAndVals % 2 == 0) { namesThatHaveBeenDeserialized.reserve (static_cast (numNamesAndVals) / 2); for (int i = 0; i < numNamesAndVals; i += 2) { - const auto name = Serializer::getChildElement (deserial, i).template get(); + const auto name = Serializer::getChildElement (deserial, i).get(); const auto& valueDeserial = Serializer::getChildElement (deserial, i + 1); for (auto& value : state.values) { @@ -102,6 +84,17 @@ void NonParamState::deserialize (typename Serializer::DeserializedType deserial, value->reset(); } } -#endif +} + +inline void NonParamState::deserialize (const json& deserial, const NonParamState& state) +{ + for (auto& value : state.values) + { + auto iter = deserial.find (value->name); + if (iter != deserial.end()) + value->deserialize (*iter); + else + value->reset(); + } } } // namespace chowdsp diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h index 4f222b499..edfe95857 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h @@ -23,12 +23,13 @@ class NonParamState void addStateValues (ContainerType& container); /** Custom serializer */ - template - static typename Serializer::SerializedType serialize (const NonParamState& state); + static json serialize (const NonParamState& state); /** Custom deserializer */ - template - static void deserialize (typename Serializer::DeserializedType deserial, const NonParamState& state); + static void deserialize (const json& deserial, const NonParamState& state); + + /** Custom deserializer */ + static void legacy_deserialize (const json& deserial, const NonParamState& state); /** Assign this function to apply version streaming to your non-parameter state. */ std::function versionStreamingCallback = nullptr; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index d2daca053..9d367232f 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -13,9 +13,6 @@ inline ParamHolder::ParamHolder (ParamHolder* parent, std::string_view phName, b return OptionalPointer { static_cast (1024) }; }(), }, -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - allParamsMap { MapAllocator { arena } }, -#endif name { arena::alloc_string (*arena, phName) }, isOwning { phIsOwning } { @@ -23,9 +20,6 @@ inline ParamHolder::ParamHolder (ParamHolder* parent, std::string_view phName, b inline ParamHolder::ParamHolder (ChainedArenaAllocator& alloc, std::string_view phName, bool phIsOwning) : arena { &alloc, false }, -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - allParamsMap { MapAllocator { arena } }, -#endif name { arena::alloc_string (*arena, phName) }, isOwning { phIsOwning } { @@ -59,13 +53,8 @@ template std::enable_if_t, void> ParamHolder::add (OptionalPointer& floatParam, OtherParams&... others) { - ThingPtr paramPtr { reinterpret_cast (isOwning ? floatParam.release() : floatParam.get()), - getFlags (FloatParam, isOwning) }; -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - const auto paramID = toStringView (floatParam->paramID); - allParamsMap.insert ({ paramID, paramPtr }); -#endif - things.insert (std::move (paramPtr)); + things.insert (ThingPtr { reinterpret_cast (isOwning ? floatParam.release() : floatParam.get()), + getFlags (FloatParam, isOwning) }); add (others...); } @@ -73,13 +62,8 @@ template std::enable_if_t, void> ParamHolder::add (OptionalPointer& choiceParam, OtherParams&... others) { - ThingPtr paramPtr { reinterpret_cast (isOwning ? choiceParam.release() : choiceParam.get()), - getFlags (ChoiceParam, isOwning) }; -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - const auto paramID = toStringView (choiceParam->paramID); - allParamsMap.insert ({ paramID, paramPtr }); -#endif - things.insert (std::move (paramPtr)); + things.insert (ThingPtr { reinterpret_cast (isOwning ? choiceParam.release() : choiceParam.get()), + getFlags (ChoiceParam, isOwning) }); add (others...); } @@ -87,13 +71,8 @@ template std::enable_if_t, void> ParamHolder::add (OptionalPointer& boolParam, OtherParams&... others) { - ThingPtr paramPtr { reinterpret_cast (isOwning ? boolParam.release() : boolParam.get()), - getFlags (BoolParam, isOwning) }; -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - const auto paramID = toStringView (boolParam->paramID); - allParamsMap.insert ({ paramID, paramPtr }); -#endif - things.insert (std::move (paramPtr)); + things.insert (ThingPtr { reinterpret_cast (isOwning ? boolParam.release() : boolParam.get()), + getFlags (BoolParam, isOwning) }); add (others...); } @@ -124,13 +103,6 @@ std::enable_if_t, void> template void ParamHolder::add (ParamHolder& paramHolder, OtherParams&... others) { -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - // This should be the parent of the holder being added. - jassert (arena == paramHolder.arena); - - allParamsMap.merge (paramHolder.allParamsMap); - jassert (paramHolder.allParamsMap.empty()); // assuming no duplicate parameter IDs, all the parameters should be moved in the merge! -#endif things.insert (ThingPtr { reinterpret_cast (¶mHolder), Holder }); add (others...); } @@ -144,7 +116,6 @@ std::enable_if_t, void> add (others...); } - [[nodiscard]] inline int ParamHolder::count() const noexcept { auto count = static_cast (things.count()); @@ -275,8 +246,7 @@ size_t ParamHolder::doForAllParameters (Callable&& callable, size_t index) const return index; } -template -typename Serializer::SerializedType ParamHolder::serialize (const ParamHolder& paramHolder) +inline json ParamHolder::serialize (const ParamHolder& paramHolder) { #if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION auto serial = nlohmann::json::object(); @@ -298,55 +268,37 @@ typename Serializer::SerializedType ParamHolder::serialize (const ParamHolder& p #endif } -template -void ParamHolder::deserialize (typename Serializer::DeserializedType deserial, ParamHolder& paramHolder) +inline void ParamHolder::deserialize (const json& deserial, ParamHolder& paramHolder) { -#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION paramHolder.doForAllParameters ( [&deserial] (auto& param, size_t) { const auto paramID = toStringView (param.paramID); ParameterTypeHelpers::setValue (deserial.value (paramID, ParameterTypeHelpers::getDefaultValue (param)), param); }); -#else +} + +inline void ParamHolder::legacy_deserialize (const json& deserial, ParamHolder& paramHolder) +{ + using Serializer = JSONSerializer; std::vector paramIDsThatHaveBeenDeserialized {}; if (const auto numParamIDsAndVals = Serializer::getNumChildElements (deserial); numParamIDsAndVals % 2 == 0) { paramIDsThatHaveBeenDeserialized.reserve (static_cast (numParamIDsAndVals) / 2); for (int i = 0; i < numParamIDsAndVals; i += 2) { - const auto paramID = Serializer::getChildElement (deserial, i).template get(); + const auto paramID = Serializer::getChildElement (deserial, i).get(); const auto& paramDeserial = Serializer::getChildElement (deserial, i + 1); - auto paramPtrIter = paramHolder.allParamsMap.find (std::string { paramID }); - if (paramPtrIter == paramHolder.allParamsMap.end()) - continue; - - paramIDsThatHaveBeenDeserialized.push_back (paramID); - [¶mDeserial] (ThingPtr& paramPtr) - { - const auto deserializeParam = [] (auto* param, auto& pd) + paramHolder.doForAllParameters ( + [&] (auto& param, size_t) { - ParameterTypeHelpers::deserializeParameter (pd, *param); - }; + if (toStringView (param.paramID) != paramID) + return; - const auto type = getType (paramPtr); - switch (type) - { - case FloatParam: - deserializeParam (reinterpret_cast (paramPtr.get_ptr()), paramDeserial); - break; - case ChoiceParam: - deserializeParam (reinterpret_cast (paramPtr.get_ptr()), paramDeserial); - break; - case BoolParam: - deserializeParam (reinterpret_cast (paramPtr.get_ptr()), paramDeserial); - break; - default: - jassertfalse; - break; - } - }(paramPtrIter->second); + paramIDsThatHaveBeenDeserialized.push_back (paramID); + ParameterTypeHelpers::deserializeParameter (paramDeserial, param); + }); } } else @@ -367,7 +319,6 @@ void ParamHolder::deserialize (typename Serializer::DeserializedType deserial, P ParameterTypeHelpers::resetParameter (param); }); } -#endif } inline void ParamHolder::applyVersionStreaming (const Version& version) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h index 85e498c52..3622e151e 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h @@ -92,12 +92,13 @@ class ParamHolder size_t doForAllParameters (Callable&& callable, size_t index = 0) const; /** Custom serializer */ - template - static typename Serializer::SerializedType serialize (const ParamHolder& paramHolder); + static json serialize (const ParamHolder& paramHolder); /** Custom deserializer */ - template - static void deserialize (typename Serializer::DeserializedType deserial, ParamHolder& paramHolder); + static void deserialize (const json& deserial, ParamHolder& paramHolder); + + /** Legacy deserializer */ + static void legacy_deserialize (const json& deserial, ParamHolder& paramHolder); /** Recursively applies version streaming to the parameters herein. */ void applyVersionStreaming (const Version&); @@ -139,14 +140,6 @@ class ParamHolder return static_cast (type | (shouldDelete ? ShouldDelete : 0)); } -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - using MapKey = std::string_view; - using MapValue = ThingPtr; - using MapAllocator = STLArenaAllocator, ChainedArenaAllocator>; - using AllParamsMap = std::unordered_map, std::equal_to<>, MapAllocator>; - AllParamsMap allParamsMap; -#endif - std::string_view name; bool isOwning; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h index 02646abf7..d0e903f2f 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h @@ -32,7 +32,7 @@ class PluginState virtual void serialize (juce::MemoryBlock& data) const = 0; /** Deserializes the plugin state from the given MemoryBlock */ - virtual void deserialize (const juce::MemoryBlock& data) = 0; + virtual void deserialize (juce::MemoryBlock&& data) = 0; /** * Adds a parameter listener which will be called on either the message @@ -96,7 +96,7 @@ class PluginState struct DummyPluginState : PluginState { void serialize (juce::MemoryBlock&) const override {} - void deserialize (const juce::MemoryBlock&) override {} + void deserialize (juce::MemoryBlock&&) override {} NonParamState non_params {}; [[nodiscard]] NonParamState& getNonParameters() override { return non_params; } diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 7bfe07d20..4c8cf30ff 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -1,30 +1,31 @@ namespace chowdsp { -template -PluginStateImpl::PluginStateImpl (juce::UndoManager* um) +template +PluginStateImpl::PluginStateImpl (juce::UndoManager* um) { initialise (params, nullptr, um); } -template -PluginStateImpl::PluginStateImpl (juce::AudioProcessor& proc, juce::UndoManager* um) +template +PluginStateImpl::PluginStateImpl (juce::AudioProcessor& proc, juce::UndoManager* um) { initialise (params, &proc, um); } -template -void PluginStateImpl::serialize (juce::MemoryBlock& data) const +template +void PluginStateImpl::serialize (juce::MemoryBlock& data) const { - Serialization::serialize (*this, data); + const auto serial = serialize (*this); + JSONUtils::toMemoryBlock (serial, data); } -template -void PluginStateImpl::deserialize (const juce::MemoryBlock& data) +template +void PluginStateImpl::deserialize (juce::MemoryBlock&& dataBlock) { callOnMainThread ( - [this, data] + [this, data = std::move (dataBlock)] { - Serialization::deserialize (data, *this); + deserialize (JSONUtils::fromMemoryBlock (data), *this); params.applyVersionStreaming (pluginStateVersion); if (nonParams.versionStreamingCallback != nullptr) @@ -38,45 +39,25 @@ void PluginStateImpl::deserialize } /** Serializer */ -template -template -typename Serializer::SerializedType PluginStateImpl::serialize (const PluginStateImpl& object) +template +json PluginStateImpl::serialize (const PluginStateImpl& object) { -#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION return { #if defined JucePlugin_VersionString { "version", currentPluginVersion }, #else { "version", chowdsp::Version {} }, #endif - { "params", Serializer::template serialize (object.params) }, - { "non-params", Serializer::template serialize (object.nonParams) }, + { "params", ParamHolder::serialize (object.params) }, + { "non-params", NonParamState::serialize (object.nonParams) }, }; -#else - auto serial = Serializer::createBaseElement(); - -#if defined JucePlugin_VersionString - Serializer::addChildElement (serial, Serializer::template serialize (currentPluginVersion)); -#endif - - Serializer::addChildElement (serial, Serializer::template serialize (object.nonParams)); - Serializer::addChildElement (serial, Serializer::template serialize (object.params)); - return serial; -#endif } -/** Deserializer */ -template -template -void PluginStateImpl::deserialize (typename Serializer::DeserializedType serial, PluginStateImpl& object) +/** Legacy Deserializer */ +template +void PluginStateImpl::legacy_deserialize (const json& serial, PluginStateImpl& object) { -#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - jassert (serial.find ("version") != serial.end()); - object.pluginStateVersion = serial.value ("version", Version {}); - - Serializer::template deserialize (serial.at ("non-params"), object.nonParams); - Serializer::template deserialize (serial.at ("params"), object.params); -#else + using Serializer = JSONSerializer; enum { #if defined JucePlugin_VersionString @@ -94,25 +75,41 @@ void PluginStateImpl::deserialize } #if defined JucePlugin_VersionString - Serializer::template deserialize (Serializer::getChildElement (serial, versionChildIndex), object.pluginStateVersion); + Serializer::deserialize (Serializer::getChildElement (serial, versionChildIndex), object.pluginStateVersion); #else using namespace version_literals; object.pluginStateVersion = "0.0.0"_v; #endif - Serializer::template deserialize (Serializer::getChildElement (serial, nonParamStateChildIndex), object.nonParams); - Serializer::template deserialize (Serializer::getChildElement (serial, paramStateChildIndex), object.params); -#endif + NonParamState::legacy_deserialize (Serializer::getChildElement (serial, nonParamStateChildIndex), object.nonParams); + ParamHolder::legacy_deserialize (Serializer::getChildElement (serial, paramStateChildIndex), object.params); +} + +/** Deserializer */ +template +void PluginStateImpl::deserialize (const json& serial, PluginStateImpl& object) +{ + if (serial.is_array()) + { + legacy_deserialize (serial, object); + return; + } + + jassert (serial.find ("version") != serial.end()); + object.pluginStateVersion = serial.value ("version", Version {}); + + NonParamState::deserialize (serial.at ("non-params"), object.nonParams); + ParamHolder::deserialize (serial.at ("params"), object.params); } -template -NonParamState& PluginStateImpl::getNonParameters() +template +NonParamState& PluginStateImpl::getNonParameters() { return nonParams; } -template -const NonParamState& PluginStateImpl::getNonParameters() const +template +const NonParamState& PluginStateImpl::getNonParameters() const { return nonParams; } diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h index 76b5ee5fc..95bc7eedf 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h @@ -7,9 +7,8 @@ namespace chowdsp * * @tparam ParameterState Struct containing all of the plugin's parameters as chowdsp::OptionalPointer's. * @tparam NonParameterState Struct containing all of the plugin's non-parameter state as StateValue objects. - * @tparam Serializer A type that implements chowdsp::BaseSerializer (JSONSerializer by default) */ -template +template class PluginStateImpl : public PluginState { static_assert (std::is_base_of_v, "ParameterState must be a chowdsp::ParamHolder!"); @@ -26,15 +25,16 @@ class PluginStateImpl : public PluginState void serialize (juce::MemoryBlock& data) const override; /** Deserializes the plugin state from the given MemoryBlock */ - void deserialize (const juce::MemoryBlock& data) override; + void deserialize (juce::MemoryBlock&& data) override; /** Serializer */ - template - static typename Serializer::SerializedType serialize (const PluginStateImpl& object); + static json serialize (const PluginStateImpl& object); /** Deserializer */ - template - static void deserialize (typename Serializer::DeserializedType serial, PluginStateImpl& object); + static void deserialize (const json& serial, PluginStateImpl& object); + + /** Legacy Deserializer */ + static void legacy_deserialize (const json& serial, PluginStateImpl& object); /** Returns the plugin non-parameter state */ [[nodiscard]] NonParamState& getNonParameters() override; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h index da0ca1c55..97ceabed9 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h @@ -11,13 +11,8 @@ struct StateValueBase virtual void reset() {} -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - virtual void serialize (JSONSerializer::SerializedType&) const {} - virtual void deserialize (JSONSerializer::DeserializedType) {} -#else [[nodiscard]] virtual nlohmann::json serialize () const { return {}; } virtual void deserialize (const nlohmann::json&) {} -#endif const std::string_view name {}; Broadcaster changeBroadcaster {}; @@ -74,19 +69,6 @@ struct StateValue : StateValueBase /** Resets the value to its default state */ void reset() override { set (defaultValue); } -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - /** JSON Serializer */ - void serialize (JSONSerializer::SerializedType& serial) const override - { - serialize (serial, *this); - } - - /** JSON Deserializer */ - void deserialize (JSONSerializer::DeserializedType deserial) override - { - deserialize (deserial, *this); - } -#else /** JSON Serializer */ [[nodiscard]] nlohmann::json serialize () const override { @@ -98,28 +80,10 @@ struct StateValue : StateValueBase { set (deserial.get()); } -#endif const element_type defaultValue; private: -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - template - static void serialize (typename Serializer::SerializedType& serial, const StateValue& value) - { - Serializer::addChildElement (serial, value.name); - Serializer::addChildElement (serial, Serialization::serialize (value.get())); - } - - template - static void deserialize (typename Serializer::DeserializedType deserial, StateValue& value) - { - element_type val {}; - Serialization::deserialize (deserial, val); - value.set (val); - } -#endif - T currentValue; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StateValue) diff --git a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h index 54d39c458..22919c92c 100644 --- a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h +++ b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h @@ -20,10 +20,6 @@ BEGIN_JUCE_MODULE_DECLARATION #pragma once -#ifndef CHOWDSP_USE_LEGACY_STATE_SERIALIZATION -#define CHOWDSP_USE_LEGACY_STATE_SERIALIZATION 0 -#endif - #include #include #include diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp index c7dbafa95..86856686c 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp @@ -184,7 +184,7 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") State state { &um }; um.beginNewTransaction(); um.perform (new DummyAction {}); - state.deserialize (block); + state.deserialize (std::move (block)); REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); @@ -210,7 +210,7 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") } State state; - state.deserialize (block); + state.deserialize (std::move (block)); REQUIRE_MESSAGE (state.nonParams.editorWidth.get() == width, "Editor width is incorrect"); REQUIRE_MESSAGE (state.nonParams.editorHeight.get() == height, "Editor height is incorrect"); REQUIRE_MESSAGE (state.nonParams.atomicThing.get() == atomic, "Atomic thing is incorrect"); @@ -229,7 +229,7 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") StateWithNewParam state; static_cast (state.params.newParam) = newGainVal; - state.deserialize (block); + state.deserialize (std::move (block)); REQUIRE_MESSAGE (state.params.newParam->get() == Catch::Approx (3.3f).margin (1.0e-6f), "Added param value is incorrect"); } @@ -245,7 +245,7 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") StateWithNewGroup state; static_cast (state.params.newGroup.newParam) = newGainVal; - state.deserialize (block); + state.deserialize (std::move (block)); REQUIRE_MESSAGE (state.params.newGroup.newParam->get() == Catch::Approx (3.3f).margin (1.0e-6f), "Added param value is incorrect"); } @@ -271,7 +271,7 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") } StateWithTripleOfSameType state {}; - state.deserialize (block); + state.deserialize (std::move (block)); REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams1.percent->get(), percentVal1), "Percent value 1 is incorrect"); REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams1.gain->get(), gainVal1), "Gain value 1 is incorrect"); REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams2.percent->get(), percentVal2), "Percent value 2 is incorrect"); @@ -296,7 +296,127 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") } StateWithNewNonParameterField state; - state.deserialize (block); + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (state.nonParams.editorWidth.get() == width, "Editor width is incorrect"); + REQUIRE_MESSAGE (state.nonParams.editorHeight.get() == height, "Editor height is incorrect"); + REQUIRE_MESSAGE (state.nonParams.randomString.get() == juce::String { "default" }, "Added field is incorrect"); + } +} + +TEST_CASE ("Legacy State Serialization Test", "[plugin][state]") +{ + SECTION ("Save/Load Parameters Test") + { + static constexpr float percentVal = 0.25f; + static constexpr float gainVal = -22.0f; + static constexpr int choiceVal = 0; + static constexpr bool boolVal = false; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",["editor_width",300,"editor_height",500,"something_atomic",12,"json_thing",{"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Niels","nothing":null,"object":{"currency":"USD","value":42.99},"pi":3.141},"yes_no0",1,"yes_no1",1,"yes_no2",1,"yes_no3",1],["percent",0.25,"gain",-22.0,"mode",0,"on_off",false]])"); + juce::MemoryBlock block; + chowdsp::JSONUtils::toMemoryBlock (jsonState, block); + + struct DummyAction : juce::UndoableAction + { + bool perform() override { return true; } + bool undo() override { return true; } + }; + + juce::UndoManager um { 100 }; + State state { &um }; + um.beginNewTransaction(); + um.perform (new DummyAction {}); + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); + REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); + REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); + REQUIRE_MESSAGE (! um.canUndo(), "Undo manager was not cleared after loading new state!"); + } + + SECTION ("Save/Load Non-Parameters Test") + { + static constexpr int width = 200; + static constexpr int height = 150; + static constexpr int atomic = 24; + const auto testJSON = nlohmann::json { { "new", 20 } }; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",["editor_width",200,"editor_height",150,"something_atomic",24,"json_thing",{"new":20},"yes_no0",1,"yes_no1",1,"yes_no2",1,"yes_no3",1],["percent",0.5,"gain",0.0,"mode",2,"on_off",true]])"); + juce::MemoryBlock block; + chowdsp::JSONUtils::toMemoryBlock (jsonState, block); + + State state; + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (state.nonParams.editorWidth.get() == width, "Editor width is incorrect"); + REQUIRE_MESSAGE (state.nonParams.editorHeight.get() == height, "Editor height is incorrect"); + REQUIRE_MESSAGE (state.nonParams.atomicThing.get() == atomic, "Atomic thing is incorrect"); + REQUIRE_MESSAGE (state.nonParams.jsonThing.get() == testJSON, "JSON thing is incorrect"); + } + + SECTION ("Added Parameter Test") + { + static constexpr float newGainVal = -22.0f; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",["editor_width",300,"editor_height",500,"something_atomic",12,"json_thing",{"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Niels","nothing":null,"object":{"currency":"USD","value":42.99},"pi":3.141},"yes_no0",1,"yes_no1",1,"yes_no2",1,"yes_no3",1],["percent",0.5,"gain",0.0,"mode",2,"on_off",true]])"); + juce::MemoryBlock block; + chowdsp::JSONUtils::toMemoryBlock (jsonState, block); + + StateWithNewParam state; + static_cast (state.params.newParam) = newGainVal; + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (state.params.newParam->get() == Catch::Approx (3.3f).margin (1.0e-6f), "Added param value is incorrect"); + } + + SECTION ("Added Parameter Group Test") + { + static constexpr float newGainVal = -22.0f; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",["editor_width",300,"editor_height",500,"something_atomic",12,"json_thing",{"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Niels","nothing":null,"object":{"currency":"USD","value":42.99},"pi":3.141},"yes_no0",1,"yes_no1",1,"yes_no2",1,"yes_no3",1],["percent",0.5,"gain",0.0,"mode",2,"on_off",true]])"); + juce::MemoryBlock block; + chowdsp::JSONUtils::toMemoryBlock (jsonState, block); + + StateWithNewGroup state; + static_cast (state.params.newGroup.newParam) = newGainVal; + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (state.params.newGroup.newParam->get() == Catch::Approx (3.3f).margin (1.0e-6f), "Added param value is incorrect"); + } + + SECTION ("Double of Same Type Test") + { + static constexpr float percentVal1 = 0.25f; + static constexpr float gainVal1 = -22.0f; + static constexpr float percentVal2 = 0.85f; + static constexpr float gainVal2 = -29.0f; + static constexpr int choiceVal = 0; + static constexpr bool boolVal = false; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",[],["level_params1percent",0.25,"level_params1gain",-22.0,"level_params2percent",0.8500000238418579,"level_params2gain",-29.0,"mode",0,"on_off",false]])"); + juce::MemoryBlock block; + chowdsp::JSONUtils::toMemoryBlock (jsonState, block); + + StateWithTripleOfSameType state {}; + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams1.percent->get(), percentVal1), "Percent value 1 is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams1.gain->get(), gainVal1), "Gain value 1 is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams2.percent->get(), percentVal2), "Percent value 2 is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams2.gain->get(), gainVal2), "Gain value 2 is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams3.percent->get(), 0.5f), "Percent value 3 is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams3.gain->get(), 0.0f), "Gain value 3 is incorrect"); + REQUIRE_MESSAGE (state.params.mode->getIndex() == choiceVal, "Choice value is incorrect"); + REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); + } + + SECTION ("Added Non-Parameter Field Test") + { + static constexpr int width = 200; + static constexpr int height = 150; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",["editor_width",200,"editor_height",150,"something_atomic",12,"json_thing",{"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Niels","nothing":null,"object":{"currency":"USD","value":42.99},"pi":3.141},"yes_no0",1,"yes_no1",1,"yes_no2",1,"yes_no3",1],["percent",0.5,"gain",0.0,"mode",2,"on_off",true]])"); + juce::MemoryBlock block; + chowdsp::JSONUtils::toMemoryBlock (jsonState, block); + + StateWithNewNonParameterField state; + state.deserialize (std::move (block)); REQUIRE_MESSAGE (state.nonParams.editorWidth.get() == width, "Editor width is incorrect"); REQUIRE_MESSAGE (state.nonParams.editorHeight.get() == height, "Editor height is incorrect"); REQUIRE_MESSAGE (state.nonParams.randomString.get() == juce::String { "default" }, "Added field is incorrect"); diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp index aa5e43f99..a9e8a7c93 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp @@ -83,13 +83,33 @@ TEST_CASE ("Version Streaming Test", "[plugin][state][version]") // Save initial state State state {}; state.serialize (stateBlock); + std::cout << chowdsp::JSONUtils::fromMemoryBlock (stateBlock).dump() << std::endl; } // check new state State state {}; REQUIRE (state.params.innerParams.param->get() == false); REQUIRE (juce::approximatelyEqual (state.nonParams.editorSize.get(), 1.0f)); - state.deserialize (stateBlock); + state.deserialize (std::move (stateBlock)); + REQUIRE (state.params.innerParams.param->get() == true); + REQUIRE (juce::approximatelyEqual (state.nonParams.editorSize.get(), 1.5f)); + } + + SECTION ("Version Streaming with Legacy State Serialization") + { + static_assert (chowdsp::currentPluginVersion == "9.9.9"_v, "Tests are tuned for JucePlugin_VersionString = 9.9.9"); + + using State = chowdsp::PluginStateImpl; + + const auto jsonState = nlohmann::json::parse (R"(["9.9.9",["editor_size",1.0],["bool",false,"bool2",false]])"); + juce::MemoryBlock stateBlock; + chowdsp::JSONUtils::toMemoryBlock (jsonState, stateBlock); + + // check new state + State state {}; + REQUIRE (state.params.innerParams.param->get() == false); + REQUIRE (juce::approximatelyEqual (state.nonParams.editorSize.get(), 1.0f)); + state.deserialize (std::move (stateBlock)); REQUIRE (state.params.innerParams.param->get() == true); REQUIRE (juce::approximatelyEqual (state.nonParams.editorSize.get(), 1.5f)); } From 6d8b920f5da1bca291e5aafae522246492db7773 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 29 Nov 2024 20:19:59 +0000 Subject: [PATCH 04/20] Apply clang-format --- .../Backend/chowdsp_PluginStateImpl.cpp | 9 +++++---- .../Backend/chowdsp_PluginStateImpl.h | 2 +- .../chowdsp_plugin_state/Backend/chowdsp_StateValue.h | 4 ++-- .../chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp | 2 +- .../chowdsp_presets_v2/Backend/chowdsp_PresetState.h | 2 +- tests/common_tests/chowdsp_json_test/JSONTest.cpp | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 4c8cf30ff..68c5054a1 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -42,14 +42,15 @@ void PluginStateImpl::deserialize (juce::Memo template json PluginStateImpl::serialize (const PluginStateImpl& object) { - return { + return + { #if defined JucePlugin_VersionString { "version", currentPluginVersion }, #else { "version", chowdsp::Version {} }, #endif - { "params", ParamHolder::serialize (object.params) }, - { "non-params", NonParamState::serialize (object.nonParams) }, + { "params", ParamHolder::serialize (object.params) }, + { "non-params", NonParamState::serialize (object.nonParams) }, }; } @@ -87,7 +88,7 @@ void PluginStateImpl::legacy_deserialize (con /** Deserializer */ template -void PluginStateImpl::deserialize (const json& serial, PluginStateImpl& object) +void PluginStateImpl::deserialize (const json& serial, PluginStateImpl& object) { if (serial.is_array()) { diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h index 95bc7eedf..1a0d119ff 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h @@ -31,7 +31,7 @@ class PluginStateImpl : public PluginState static json serialize (const PluginStateImpl& object); /** Deserializer */ - static void deserialize (const json& serial, PluginStateImpl& object); + static void deserialize (const json& serial, PluginStateImpl& object); /** Legacy Deserializer */ static void legacy_deserialize (const json& serial, PluginStateImpl& object); diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h index 97ceabed9..0f299ee94 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h @@ -11,7 +11,7 @@ struct StateValueBase virtual void reset() {} - [[nodiscard]] virtual nlohmann::json serialize () const { return {}; } + [[nodiscard]] virtual nlohmann::json serialize() const { return {}; } virtual void deserialize (const nlohmann::json&) {} const std::string_view name {}; @@ -70,7 +70,7 @@ struct StateValue : StateValueBase void reset() override { set (defaultValue); } /** JSON Serializer */ - [[nodiscard]] nlohmann::json serialize () const override + [[nodiscard]] nlohmann::json serialize() const override { return get(); } diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp index af27fe8e7..71b8768b2 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp @@ -58,7 +58,7 @@ void PresetState::deserialize (JSONSerializer::DeserializedType deserial) set (PresetPtr { deserial }); } #else -nlohmann::json PresetState::serialize () const +nlohmann::json PresetState::serialize() const { if (preset != nullptr) return preset->toJson(); diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h index 431573a53..910c56909 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h @@ -49,7 +49,7 @@ class PresetState : public StateValueBase void deserialize (JSONSerializer::DeserializedType deserial) override; #else /** Internal use only! */ - [[nodiscard]] nlohmann::json serialize () const override; + [[nodiscard]] nlohmann::json serialize() const override; /** Internal use only! */ void deserialize (const nlohmann::json& deserial) override; diff --git a/tests/common_tests/chowdsp_json_test/JSONTest.cpp b/tests/common_tests/chowdsp_json_test/JSONTest.cpp index 445644c63..b972c7d46 100644 --- a/tests/common_tests/chowdsp_json_test/JSONTest.cpp +++ b/tests/common_tests/chowdsp_json_test/JSONTest.cpp @@ -25,7 +25,7 @@ TEST_CASE ("JSON Test", "[common][json]") } { const auto badVersion = chowdsp::json::object().get(); - REQUIRE (badVersion == chowdsp::Version{}); + REQUIRE (badVersion == chowdsp::Version {}); } } From b3f9bc24a037521d156618293c08403abf4fae5a Mon Sep 17 00:00:00 2001 From: jatin Date: Fri, 29 Nov 2024 16:38:05 -0800 Subject: [PATCH 05/20] Serialization work --- .../chowdsp_ChainedArenaAllocator.h | 6 ++ .../Serialization/chowdsp_ByteSerializer.cpp | 22 ++++ .../Serialization/chowdsp_ByteSerializer.h | 100 ++++++++++++++++++ .../chowdsp_serialization.cpp | 3 + .../chowdsp_serialization.h | 4 +- .../Backend/chowdsp_ParamHolder.h | 1 - .../Backend/chowdsp_PluginStateImpl.cpp | 5 + .../chowdsp_version/Version/chowdsp_Version.h | 9 ++ .../ByteSerializationTest.cpp | 89 ++++++++++++++++ .../chowdsp_serialization_test/CMakeLists.txt | 1 + .../chowdsp_version_test/VersionUtilsTest.cpp | 4 + 11 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp create mode 100644 modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h create mode 100644 modules/common/chowdsp_serialization/chowdsp_serialization.cpp create mode 100644 tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp diff --git a/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h b/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h index b48c85be3..0ae197b75 100644 --- a/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h +++ b/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h @@ -114,6 +114,12 @@ class ChainedArenaAllocator return get_current_arena().template data (offset_bytes); } + /** Returns the default size for an individual arena */ + [[nodiscard]] size_t get_default_arena_size() const noexcept + { + return arena_size_bytes; + } + /** Returns the arena currently being used */ ArenaAllocatorView& get_current_arena() { diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp new file mode 100644 index 000000000..799362cbc --- /dev/null +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp @@ -0,0 +1,22 @@ +namespace chowdsp +{ +nonstd::span get_bytes_for_deserialization (nonstd::span& bytes) +{ + using namespace bytes_detail; + + size_type bytes_count; + std::memcpy (&bytes_count, bytes.data(), sizeof_s); + jassert (sizeof_s + bytes_count <= bytes.size()); + + const auto serial_bytes = bytes.subspan (sizeof_s, bytes_count); + bytes = bytes.subspan (sizeof_s + bytes_count); + + return serial_bytes; +} + +std::string_view deserialize_string (nonstd::span& bytes) +{ + const auto serial_bytes = get_bytes_for_deserialization (bytes); + return { reinterpret_cast (serial_bytes.data()), serial_bytes.size() }; +} +} // namespace chowdsp diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h new file mode 100644 index 000000000..0135424b3 --- /dev/null +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -0,0 +1,100 @@ +#pragma once + +namespace chowdsp +{ +#ifndef DOXYGEN +namespace bytes_detail +{ + using size_type = uint16_t; + static constexpr auto sizeof_s = sizeof (size_type); +} // namespace bytes_detail +#endif + +template +static std::byte* get_bytes_for_serialization (size_t bytes_count, ArenaType& arena) +{ + using namespace bytes_detail; + jassert (bytes_count <= std::numeric_limits::max()); + + if constexpr (std::is_same_v) + jassert (sizeof_s + bytes_count <= arena.get_default_arena_size()); + + auto* bytes = static_cast (arena.allocate_bytes (sizeof_s + bytes_count, 1)); + jassert (bytes != nullptr); + + const auto bytes_count_cast = static_cast (bytes_count); + std::memcpy (bytes, &bytes_count_cast, sizeof_s); + + return bytes + sizeof_s; // NOLINT +} + +nonstd::span get_bytes_for_deserialization (nonstd::span& bytes); + +template +static void serialize_object (const T& object, ArenaType& arena) +{ + auto* bytes = get_bytes_for_serialization (sizeof (T), arena); + std::memcpy (bytes, &object, sizeof (T)); // NOLINT +} + +template +static void serialize_span (nonstd::span data, ArenaType& arena) +{ + const auto num_bytes = sizeof (T) * data.size(); + auto* bytes = get_bytes_for_serialization (num_bytes, arena); + std::memcpy (bytes, data.data(), num_bytes); // NOLINT +} + +template +static void serialize_string (std::string_view str, ArenaType& arena) +{ + const auto num_bytes = sizeof (char) * str.size(); + auto* bytes = get_bytes_for_serialization (num_bytes, arena); + std::memcpy (bytes, str.data(), num_bytes); // NOLINT +} + +template +static nonstd::span dump_serialized_bytes (ArenaAllocator& arena, + const typename ArenaAllocator::Frame* frame = nullptr) +{ + const auto bytes_offset = frame == nullptr ? 0 : frame->bytes_used_at_start; + const auto bytes_count = arena.get_bytes_used() - bytes_offset; + return { arena.template data (bytes_offset), bytes_count }; +} + +template +static T deserialize_object (nonstd::span& bytes) +{ + const auto serial_bytes = get_bytes_for_deserialization (bytes); + jassert (serial_bytes.size() == sizeof (T)); + + T object; + std::memcpy (&object, serial_bytes.data(), serial_bytes.size()); + return object; +} + +template +static size_t get_span_length (const nonstd::span& bytes) +{ + using namespace bytes_detail; + + size_type sizeof_span_bytes; + std::memcpy (&sizeof_span_bytes, bytes.data(), sizeof_s); + + jassert (sizeof_span_bytes % sizeof (T) == 0); + return static_cast (sizeof_span_bytes) / sizeof (T); +} + +template +static void deserialize_span (nonstd::span span, nonstd::span& bytes) +{ + const auto serial_bytes = get_bytes_for_deserialization (bytes); + + jassert (serial_bytes.size() % sizeof (T) == 0); + jassert (serial_bytes.size() / sizeof (T) == span.size()); + + std::memcpy (span.data(), serial_bytes.data(), serial_bytes.size()); +} + +std::string_view deserialize_string (nonstd::span& bytes); +} // namespace chowdsp diff --git a/modules/common/chowdsp_serialization/chowdsp_serialization.cpp b/modules/common/chowdsp_serialization/chowdsp_serialization.cpp new file mode 100644 index 000000000..4cf9bcffc --- /dev/null +++ b/modules/common/chowdsp_serialization/chowdsp_serialization.cpp @@ -0,0 +1,3 @@ +#include "chowdsp_serialization.h" + +#include "Serialization/chowdsp_ByteSerializer.cpp" diff --git a/modules/common/chowdsp_serialization/chowdsp_serialization.h b/modules/common/chowdsp_serialization/chowdsp_serialization.h index a4872c10d..f1e6b85c4 100644 --- a/modules/common/chowdsp_serialization/chowdsp_serialization.h +++ b/modules/common/chowdsp_serialization/chowdsp_serialization.h @@ -8,7 +8,7 @@ BEGIN_JUCE_MODULE_DECLARATION version: 2.3.0 name: ChowDSP Serialization Utilities description: Utility methods for serializing data structures into XML, JSON, or some other format - dependencies: juce_core, chowdsp_core, chowdsp_json, chowdsp_reflection + dependencies: juce_core, chowdsp_core, chowdsp_json, chowdsp_reflection, chowdsp_data_structures website: https://ccrma.stanford.edu/~jatin/chowdsp license: BSD 3-Clause @@ -25,8 +25,10 @@ BEGIN_JUCE_MODULE_DECLARATION #include #include #include +#include #include "Serialization/chowdsp_BaseSerializer.h" #include "Serialization/chowdsp_Serialization.h" #include "Serialization/chowdsp_JSONSerializer.h" #include "Serialization/chowdsp_XMLSerializer.h" +#include "Serialization/chowdsp_ByteSerializer.h" diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h index 3622e151e..d249592ec 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h @@ -106,7 +106,6 @@ class ParamHolder /** Assign this function to apply version streaming to your non-parameter state. */ FixedSizeFunction<8, void (const Version&)> versionStreamingCallback {}; -protected: OptionalPointer arena {}; private: diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 68c5054a1..1a0b659ee 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -15,6 +15,11 @@ PluginStateImpl::PluginStateImpl (juce::Audio template void PluginStateImpl::serialize (juce::MemoryBlock& data) const { + auto& arena = const_cast (*params.arena); + const auto frame = arena.create_frame(); + + + const auto serial = serialize (*this); JSONUtils::toMemoryBlock (serial, data); } diff --git a/modules/plugin/chowdsp_version/Version/chowdsp_Version.h b/modules/plugin/chowdsp_version/Version/chowdsp_Version.h index f189e32e8..69b482ec8 100644 --- a/modules/plugin/chowdsp_version/Version/chowdsp_Version.h +++ b/modules/plugin/chowdsp_version/Version/chowdsp_Version.h @@ -59,6 +59,15 @@ class Version /** Returns an integer hint for this version value. */ [[nodiscard]] constexpr int getVersionHint() const { return major * 10000 + minor * 100 + patch; } + /** Returns a version from a version hint */ + static constexpr Version fromVersionHint (int versionHint) + { + const auto major = versionHint / 10000; + const auto minor = (versionHint / 100) % 100; + const auto patch = versionHint % 100; + return { major, minor, patch }; + } + /** Custom serializer */ template static typename Serializer::SerializedType serialize (const Version& object) diff --git a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp new file mode 100644 index 000000000..9dea8e130 --- /dev/null +++ b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp @@ -0,0 +1,89 @@ +#include +#include + +TEST_CASE ("Byte Serialization Test", "[common][serialization]") +{ + struct Test + { + int x = 44; + float y = 128.0f; + double z = -19.0f; + bool b = false; + }; + + SECTION ("Basic Arena") + { + chowdsp::ArenaAllocator> arena {}; + + const auto arr = chowdsp::make_array_lambda ([] (auto idx) + { + return static_cast (idx); + }); + const auto str = std::string { "Hello world" }; + + chowdsp::serialize_object (int { 42 }, arena); + chowdsp::serialize_object (float { 99.0f }, arena); + chowdsp::serialize_object (Test {}, arena); + chowdsp::serialize_object (Test { 1, 2.0f, 3.0, true }, arena); + chowdsp::serialize_span (arr, arena); + chowdsp::serialize_string (str, arena); + + auto bytes = chowdsp::dump_serialized_bytes (arena); + + const auto int_test = chowdsp::deserialize_object (bytes); + REQUIRE (int_test == 42); + const auto float_test = chowdsp::deserialize_object (bytes); + REQUIRE (float_test == 99.0f); + auto struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test {})); + struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test { 1, 2.0f, 3.0, true })); + + const auto arr_test = chowdsp::deserialize_object> (bytes); + for (const auto& [x, y] : chowdsp::zip (arr, arr_test)) + REQUIRE (x == y); + + const auto str_test = chowdsp::deserialize_string (bytes); + REQUIRE (str_test == str); + REQUIRE (bytes.empty()); + } + + SECTION ("Arena w/ previous usage") + { + chowdsp::ArenaAllocator> arena {}; + arena.allocate (30); + + std::vector vec (12); + std::iota (vec.begin(), vec.end(), 100); + const auto str = juce::String { "Hello world" }; + + const auto frame = arena.create_frame(); + chowdsp::serialize_object (int { 42 }, arena); + chowdsp::serialize_object (float { 99.0f }, arena); + chowdsp::serialize_object (Test {}, arena); + chowdsp::serialize_object (Test { 1, 2.0f, 3.0, true }, arena); + chowdsp::serialize_span (vec, arena); + chowdsp::serialize_string (chowdsp::toStringView (str), arena); + + auto bytes = chowdsp::dump_serialized_bytes (arena, &frame); + + const auto int_test = chowdsp::deserialize_object (bytes); + REQUIRE (int_test == 42); + const auto float_test = chowdsp::deserialize_object (bytes); + REQUIRE (float_test == 99.0f); + auto struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test {})); + struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test { 1, 2.0f, 3.0, true })); + + std::vector vec_test (chowdsp::get_span_length (bytes)); + REQUIRE (vec_test.size() == vec.size()); + chowdsp::deserialize_span (vec_test, bytes); + for (const auto& [x, y] : chowdsp::zip (vec, vec_test)) + REQUIRE (x == y); + + const auto str_test = chowdsp::deserialize_string (bytes); + REQUIRE (str_test == chowdsp::toStringView (str)); + REQUIRE (bytes.empty()); + } +} diff --git a/tests/common_tests/chowdsp_serialization_test/CMakeLists.txt b/tests/common_tests/chowdsp_serialization_test/CMakeLists.txt index 65a58b7be..58c233005 100644 --- a/tests/common_tests/chowdsp_serialization_test/CMakeLists.txt +++ b/tests/common_tests/chowdsp_serialization_test/CMakeLists.txt @@ -2,5 +2,6 @@ setup_catch_lib_test(chowdsp_serialization_test common_tests_lib) target_sources(chowdsp_serialization_test PRIVATE SerializationTest.cpp + ByteSerializationTest.cpp TestSerialBinaryData.cpp ) diff --git a/tests/plugin_tests/chowdsp_version_test/VersionUtilsTest.cpp b/tests/plugin_tests/chowdsp_version_test/VersionUtilsTest.cpp index 13d352f46..98480d5c9 100644 --- a/tests/plugin_tests/chowdsp_version_test/VersionUtilsTest.cpp +++ b/tests/plugin_tests/chowdsp_version_test/VersionUtilsTest.cpp @@ -113,15 +113,19 @@ TEST_CASE ("Version Test", "[plugin][version]") using namespace chowdsp::version_literals; static constexpr auto v1_2_3 = "1.2.3"_v; static_assert (v1_2_3.getVersionHint() == 10203); + static_assert (chowdsp::Version::fromVersionHint (v1_2_3.getVersionHint()) == v1_2_3); static constexpr auto v50_49_5 = chowdsp::Version { 50, 49, 5 }; static_assert (v50_49_5.getVersionHint() == 504905); + static_assert (chowdsp::Version::fromVersionHint (v50_49_5.getVersionHint()) == v50_49_5); const auto v99_99_99 = "99.99.99"_v; REQUIRE (v99_99_99.getVersionHint() == 999999); + REQUIRE (chowdsp::Version::fromVersionHint (v99_99_99.getVersionHint()) == v99_99_99); const auto v5_49_15 = chowdsp::Version { 5, 49, 15 }; REQUIRE (v5_49_15.getVersionHint() == 54915); + REQUIRE (chowdsp::Version::fromVersionHint (v5_49_15.getVersionHint()) == v5_49_15); REQUIRE (chowdsp::Version {}.getVersionHint() == 0); } From e2e7466a2f613f06a073e694e0a319b79f840fea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 30 Nov 2024 00:38:38 +0000 Subject: [PATCH 06/20] Apply clang-format --- .../Serialization/chowdsp_ByteSerializer.h | 2 +- .../chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp | 2 -- .../chowdsp_serialization_test/ByteSerializationTest.cpp | 4 +--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index 0135424b3..951039121 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -55,7 +55,7 @@ static void serialize_string (std::string_view str, ArenaType& arena) template static nonstd::span dump_serialized_bytes (ArenaAllocator& arena, - const typename ArenaAllocator::Frame* frame = nullptr) + const typename ArenaAllocator::Frame* frame = nullptr) { const auto bytes_offset = frame == nullptr ? 0 : frame->bytes_used_at_start; const auto bytes_count = arena.get_bytes_used() - bytes_offset; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 1a0b659ee..938b308d0 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -18,8 +18,6 @@ void PluginStateImpl::serialize (juce::Memory auto& arena = const_cast (*params.arena); const auto frame = arena.create_frame(); - - const auto serial = serialize (*this); JSONUtils::toMemoryBlock (serial, data); } diff --git a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp index 9dea8e130..76c326e0a 100644 --- a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp +++ b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp @@ -16,9 +16,7 @@ TEST_CASE ("Byte Serialization Test", "[common][serialization]") chowdsp::ArenaAllocator> arena {}; const auto arr = chowdsp::make_array_lambda ([] (auto idx) - { - return static_cast (idx); - }); + { return static_cast (idx); }); const auto str = std::string { "Hello world" }; chowdsp::serialize_object (int { 42 }, arena); From 5b9e933a7e2e861d6b81ff128ad44a811c48d614 Mon Sep 17 00:00:00 2001 From: jatin Date: Sat, 30 Nov 2024 00:06:28 -0800 Subject: [PATCH 07/20] More byte serialization work --- .../Allocators/chowdsp_ArenaAllocator.h | 7 ++ .../Serialization/chowdsp_ByteSerializer.cpp | 38 +++++++++ .../Serialization/chowdsp_ByteSerializer.h | 7 +- .../ByteSerializationTest.cpp | 79 ++++++++++++++++++- 4 files changed, 127 insertions(+), 4 deletions(-) diff --git a/modules/common/chowdsp_data_structures/Allocators/chowdsp_ArenaAllocator.h b/modules/common/chowdsp_data_structures/Allocators/chowdsp_ArenaAllocator.h index e71b9df19..d88b1ed6a 100644 --- a/modules/common/chowdsp_data_structures/Allocators/chowdsp_ArenaAllocator.h +++ b/modules/common/chowdsp_data_structures/Allocators/chowdsp_ArenaAllocator.h @@ -103,6 +103,13 @@ class ArenaAllocator return reinterpret_cast (raw_data.data() + offset_bytes); } + /** Returns a pointer to the internal buffer with a given offset in bytes */ + template + const T* data (IntType offset_bytes) const noexcept + { + return reinterpret_cast (raw_data.data() + offset_bytes); + } + /** * Creates a "frame" for the allocator. * Once the frame goes out of scope, the allocator will be reset diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp index 799362cbc..2ca18b0c9 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp @@ -14,6 +14,44 @@ nonstd::span get_bytes_for_deserialization (nonstd::span dump_serialized_bytes (ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame) +{ + size_t num_bytes = 0; + auto* start_arena = frame == nullptr ? arena.get_arenas().head : frame->arena_at_start; + const auto add_bytes_count = [&num_bytes, start_arena, frame] (const ArenaAllocatorView& arena_node) + { + size_t bytes_offset = 0; + if (start_arena == &arena_node && frame != nullptr) + bytes_offset = frame->arena_frame.bytes_used_at_start; + num_bytes += arena_node.get_bytes_used() - bytes_offset; + }; + + for (auto* arena_node = start_arena; arena_node != &arena.get_current_arena(); arena_node = arena_node->next) + add_bytes_count (*arena_node); + add_bytes_count (arena.get_current_arena()); + + auto serial = arena::make_span (arena, num_bytes, 8); + size_t bytes_counter = 0; + const auto copy_bytes = [num_bytes, start_arena, frame, &serial, &bytes_counter] (const ArenaAllocatorView& arena_node) + { + size_t bytes_offset = 0; + if (start_arena == &arena_node && frame != nullptr) + bytes_offset = frame->arena_frame.bytes_used_at_start; + + const auto bytes_to_copy = std::min (arena_node.get_bytes_used() - bytes_offset, + num_bytes - bytes_counter); + std::memcpy (serial.data() + bytes_counter, arena_node.data (bytes_offset), bytes_to_copy); + + bytes_counter += bytes_to_copy; + }; + for (auto* arena_node = start_arena; arena_node != &arena.get_current_arena(); arena_node = arena_node->next) + copy_bytes (*arena_node); + copy_bytes (arena.get_current_arena()); + + return serial; +} + std::string_view deserialize_string (nonstd::span& bytes) { const auto serial_bytes = get_bytes_for_deserialization (bytes); diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index 951039121..e235bf153 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -54,14 +54,17 @@ static void serialize_string (std::string_view str, ArenaType& arena) } template -static nonstd::span dump_serialized_bytes (ArenaAllocator& arena, - const typename ArenaAllocator::Frame* frame = nullptr) +static nonstd::span dump_serialized_bytes (const ArenaAllocator& arena, + const typename ArenaAllocator::Frame* frame = nullptr) { const auto bytes_offset = frame == nullptr ? 0 : frame->bytes_used_at_start; const auto bytes_count = arena.get_bytes_used() - bytes_offset; return { arena.template data (bytes_offset), bytes_count }; } +nonstd::span dump_serialized_bytes (ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame = nullptr); + template static T deserialize_object (nonstd::span& bytes) { diff --git a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp index 76c326e0a..45bc1af64 100644 --- a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp +++ b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp @@ -39,7 +39,7 @@ TEST_CASE ("Byte Serialization Test", "[common][serialization]") const auto arr_test = chowdsp::deserialize_object> (bytes); for (const auto& [x, y] : chowdsp::zip (arr, arr_test)) - REQUIRE (x == y); + REQUIRE (juce::exactlyEqual (x, y)); const auto str_test = chowdsp::deserialize_string (bytes); REQUIRE (str_test == str); @@ -78,7 +78,82 @@ TEST_CASE ("Byte Serialization Test", "[common][serialization]") REQUIRE (vec_test.size() == vec.size()); chowdsp::deserialize_span (vec_test, bytes); for (const auto& [x, y] : chowdsp::zip (vec, vec_test)) - REQUIRE (x == y); + REQUIRE (juce::exactlyEqual (x, y)); + + const auto str_test = chowdsp::deserialize_string (bytes); + REQUIRE (str_test == chowdsp::toStringView (str)); + REQUIRE (bytes.empty()); + } + + SECTION ("Basic Chained Arena") + { + chowdsp::ChainedArenaAllocator arena { 64 }; + + const auto arr = chowdsp::make_array_lambda ([] (auto idx) + { return static_cast (idx); }); + const auto str = std::string { "Hello world" }; + + chowdsp::serialize_object (int { 42 }, arena); + chowdsp::serialize_object (float { 99.0f }, arena); + chowdsp::serialize_object (Test {}, arena); + chowdsp::serialize_object (Test { 1, 2.0f, 3.0, true }, arena); + chowdsp::serialize_span (arr, arena); + chowdsp::serialize_string (str, arena); + + auto bytes = chowdsp::dump_serialized_bytes (arena); + + const auto int_test = chowdsp::deserialize_object (bytes); + REQUIRE (int_test == 42); + const auto float_test = chowdsp::deserialize_object (bytes); + REQUIRE (float_test == 99.0f); + auto struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test {})); + struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test { 1, 2.0f, 3.0, true })); + + const auto arr_test = chowdsp::deserialize_object> (bytes); + for (const auto& [x, y] : chowdsp::zip (arr, arr_test)) + REQUIRE (juce::exactlyEqual (x, y)); + + const auto str_test = chowdsp::deserialize_string (bytes); + REQUIRE (str_test == str); + REQUIRE (bytes.empty()); + } + + SECTION ("Chained Arena w/ previous usage") + { + chowdsp::ChainedArenaAllocator arena { 64 }; + arena.allocate (12); + arena.allocate (12); + + std::vector vec (12); + std::iota (vec.begin(), vec.end(), 100); + const auto str = juce::String { "Hello world" }; + + const auto frame = arena.create_frame(); + chowdsp::serialize_object (int { 42 }, arena); + chowdsp::serialize_object (float { 99.0f }, arena); + chowdsp::serialize_object (Test {}, arena); + chowdsp::serialize_object (Test { 1, 2.0f, 3.0, true }, arena); + chowdsp::serialize_span (vec, arena); + chowdsp::serialize_string (chowdsp::toStringView (str), arena); + + auto bytes = chowdsp::dump_serialized_bytes (arena, &frame); + + const auto int_test = chowdsp::deserialize_object (bytes); + REQUIRE (int_test == 42); + const auto float_test = chowdsp::deserialize_object (bytes); + REQUIRE (float_test == 99.0f); + auto struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test {})); + struct_test = chowdsp::deserialize_object (bytes); + REQUIRE (pfr::eq_fields (struct_test, Test { 1, 2.0f, 3.0, true })); + + std::vector vec_test (chowdsp::get_span_length (bytes)); + REQUIRE (vec_test.size() == vec.size()); + chowdsp::deserialize_span (vec_test, bytes); + for (const auto& [x, y] : chowdsp::zip (vec, vec_test)) + REQUIRE (juce::exactlyEqual (x, y)); const auto str_test = chowdsp::deserialize_string (bytes); REQUIRE (str_test == chowdsp::toStringView (str)); From 4581c51fd99d843a3da50bc1366b6220eeca53df Mon Sep 17 00:00:00 2001 From: jatin Date: Sat, 30 Nov 2024 02:50:23 -0800 Subject: [PATCH 08/20] Serialization for non-parameter state --- cmake/test/SetupCatchTest.cmake | 6 +- .../Serialization/chowdsp_ByteSerializer.h | 25 +- .../Backend/chowdsp_NonParamState.cpp | 97 ++++++-- .../Backend/chowdsp_NonParamState.h | 31 ++- .../Backend/chowdsp_PluginStateImpl.cpp | 4 +- .../Backend/chowdsp_StateValue.h | 71 +++++- .../chowdsp_plugin_state.cpp | 1 + .../chowdsp_plugin_state_test/CMakeLists.txt | 1 + .../NonParamTest.cpp | 222 ++++++++++++++++++ 9 files changed, 421 insertions(+), 37 deletions(-) create mode 100644 tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp diff --git a/cmake/test/SetupCatchTest.cmake b/cmake/test/SetupCatchTest.cmake index 5513d4ca9..880e03342 100644 --- a/cmake/test/SetupCatchTest.cmake +++ b/cmake/test/SetupCatchTest.cmake @@ -21,9 +21,9 @@ function(setup_catch_test_base target) ) if(NOT (CMAKE_GENERATOR STREQUAL Xcode)) - catch_discover_tests(${target} - TEST_PREFIX ${target}_ - ) + # catch_discover_tests(${target} + # TEST_PREFIX ${target}_ + # ) endif() endfunction(setup_catch_test_base) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index e235bf153..3a88cbb0f 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -30,27 +30,37 @@ static std::byte* get_bytes_for_serialization (size_t bytes_count, ArenaType& ar nonstd::span get_bytes_for_deserialization (nonstd::span& bytes); +template +void serialize_direct (TDest* ptr, const TSource& source) +{ + const auto source_cast = static_cast (source); + std::memcpy (ptr, &source_cast, sizeof (TDest)); +} + template -static void serialize_object (const T& object, ArenaType& arena) +[[nodiscard]] static size_t serialize_object (const T& object, ArenaType& arena) { auto* bytes = get_bytes_for_serialization (sizeof (T), arena); std::memcpy (bytes, &object, sizeof (T)); // NOLINT + return bytes_detail::sizeof_s + sizeof (T); } template -static void serialize_span (nonstd::span data, ArenaType& arena) +[[nodiscard]] static size_t serialize_span (nonstd::span data, ArenaType& arena) { const auto num_bytes = sizeof (T) * data.size(); auto* bytes = get_bytes_for_serialization (num_bytes, arena); std::memcpy (bytes, data.data(), num_bytes); // NOLINT + return bytes_detail::sizeof_s + num_bytes; } template -static void serialize_string (std::string_view str, ArenaType& arena) +[[nodiscard]] static size_t serialize_string (std::string_view str, ArenaType& arena) { const auto num_bytes = sizeof (char) * str.size(); auto* bytes = get_bytes_for_serialization (num_bytes, arena); std::memcpy (bytes, str.data(), num_bytes); // NOLINT + return bytes_detail::sizeof_s + num_bytes; } template @@ -65,6 +75,15 @@ static nonstd::span dump_serialized_bytes (const ArenaAllocator nonstd::span dump_serialized_bytes (ChainedArenaAllocator& arena, const ChainedArenaAllocator::Frame* frame = nullptr); +template +T deserialize_direct (nonstd::span& bytes) +{ + T x; + std::memcpy (&x, bytes.data(), sizeof (T)); + bytes = bytes.subspan (sizeof (T)); + return x; +} + template static T deserialize_object (nonstd::span& bytes) { diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index 640201cb0..d5f1cf216 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -1,28 +1,12 @@ namespace chowdsp { -inline void NonParamState::addStateValues (const std::initializer_list& newStateValues) +void NonParamState::addStateValues (const std::initializer_list& newStateValues) { values.insert (values.end(), newStateValues.begin(), newStateValues.end()); validateStateValues(); } -template -inline void NonParamState::addStateValues (nonstd::span> newStateValues) -{ - for (auto& val : newStateValues) - values.push_back (&val); - validateStateValues(); -} - -template -inline void NonParamState::addStateValues (ContainerType& container) -{ - for (auto& val : container) - values.push_back (&val); - validateStateValues(); -} - -inline void NonParamState::validateStateValues() const +void NonParamState::validateStateValues() const { #if JUCE_DEBUG std::vector stateValueNames; @@ -41,7 +25,78 @@ inline void NonParamState::validateStateValues() const #endif } -inline json NonParamState::serialize (const NonParamState& state) +void NonParamState::reset() +{ + for (auto* value : values) + value->reset(); +} + +void NonParamState::serialize (ChainedArenaAllocator& arena, const NonParamState& state) +{ + auto* serialize_num_bytes = arena.allocate (1, 1); + size_t num_bytes = 0; + for (const auto& value : state.values) + { + num_bytes += serialize_string (value->name, arena); + num_bytes += value->serialize (arena); + } + serialize_direct (serialize_num_bytes, num_bytes); +} + +void NonParamState::deserialize (nonstd::span serial_data, NonParamState& state) +{ + auto num_bytes = deserialize_direct (serial_data); + if (num_bytes == 0 || serial_data.size() < num_bytes) + { + state.reset(); + return; + } + + auto values_copy = state.values; + auto values_iter = values_copy.begin(); + const auto get_value_ptr = [&] (std::string_view name) -> StateValueBase* + { + for (auto iter = values_iter; iter != values_copy.end(); ++iter) + { + if ((*iter)->name == name) + { + auto* ptr = *iter; + values_iter = values_copy.erase (iter); + return ptr; + } + } + + for (auto iter = values_copy.begin(); iter != values_iter; ++iter) + { + if ((*iter)->name == name) + { + auto* ptr = *iter; + values_iter = values_copy.erase (iter); + return ptr; + } + } + return nullptr; + }; + + while (serial_data.size() > 0) + { + const auto value_name = deserialize_string (serial_data); + auto* value = get_value_ptr (value_name); + if (value == nullptr) + { + const auto value_num_bytes = deserialize_direct (serial_data); + serial_data = serial_data.subspan (value_num_bytes); + continue; + } + + value->deserialize (serial_data); + } + + for (auto* value : values_copy) + value->reset(); +} + +json NonParamState::serialize_json (const NonParamState& state) { auto serial = nlohmann::json::object(); for (const auto& value : state.values) @@ -49,7 +104,7 @@ inline json NonParamState::serialize (const NonParamState& state) return serial; } -inline void NonParamState::legacy_deserialize (const json& deserial, const NonParamState& state) +void NonParamState::legacy_deserialize (const json& deserial, const NonParamState& state) { using Serializer = JSONSerializer; std::vector namesThatHaveBeenDeserialized {}; @@ -86,7 +141,7 @@ inline void NonParamState::legacy_deserialize (const json& deserial, const NonPa } } -inline void NonParamState::deserialize (const json& deserial, const NonParamState& state) +void NonParamState::deserialize_json (const json& deserial, const NonParamState& state) { for (auto& value : state.values) { diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h index edfe95857..01bb6a4fb 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h @@ -16,17 +16,36 @@ class NonParamState /** Adds more state values to this state. */ template - void addStateValues (nonstd::span> newStateValues); + void addStateValues (nonstd::span> newStateValues) + { + for (auto& val : newStateValues) + values.push_back (&val); + validateStateValues(); + } /** Adds more state values to this state. */ template - void addStateValues (ContainerType& container); + void addStateValues (ContainerType& container) + { + for (auto& val : container) + values.push_back (&val); + validateStateValues(); + } + + /** Resets all the state values to their defaults */ + void reset(); + + /** Custom serializer */ + static void serialize (ChainedArenaAllocator& arena, const NonParamState& state); + + /** Custom deserializer */ + static void deserialize (nonstd::span serial_data, NonParamState& state); /** Custom serializer */ - static json serialize (const NonParamState& state); + static json serialize_json (const NonParamState& state); /** Custom deserializer */ - static void deserialize (const json& deserial, const NonParamState& state); + static void deserialize_json (const json& deserial, const NonParamState& state); /** Custom deserializer */ static void legacy_deserialize (const json& deserial, const NonParamState& state); @@ -37,10 +56,8 @@ class NonParamState private: void validateStateValues() const; - std::vector values; + std::vector values {}; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonParamState) }; } // namespace chowdsp - -#include "chowdsp_NonParamState.cpp" diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 938b308d0..152424c8c 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -53,7 +53,7 @@ json PluginStateImpl::serialize (const Plugin { "version", chowdsp::Version {} }, #endif { "params", ParamHolder::serialize (object.params) }, - { "non-params", NonParamState::serialize (object.nonParams) }, + { "non-params", NonParamState::serialize_json (object.nonParams) }, }; } @@ -102,7 +102,7 @@ void PluginStateImpl::deserialize (const json jassert (serial.find ("version") != serial.end()); object.pluginStateVersion = serial.value ("version", Version {}); - NonParamState::deserialize (serial.at ("non-params"), object.nonParams); + NonParamState::deserialize_json (serial.at ("non-params"), object.nonParams); ParamHolder::deserialize (serial.at ("params"), object.params); } diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h index 0f299ee94..55095df4c 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h @@ -1,4 +1,5 @@ #pragma once +#include namespace chowdsp { @@ -14,6 +15,9 @@ struct StateValueBase [[nodiscard]] virtual nlohmann::json serialize() const { return {}; } virtual void deserialize (const nlohmann::json&) {} + [[nodiscard]] virtual size_t serialize (ChainedArenaAllocator&) const { return 0; } + virtual void deserialize (nonstd::span&) {} + const std::string_view name {}; Broadcaster changeBroadcaster {}; }; @@ -78,7 +82,72 @@ struct StateValue : StateValueBase /** JSON Deserializer */ void deserialize (const nlohmann::json& deserial) override { - set (deserial.get()); + set (deserial.get()); + } + + /** Binary serializer */ + [[nodiscard]] size_t serialize (ChainedArenaAllocator& arena) const override + { + static constexpr auto is_span = TypeTraits::IsIterable && ! TypeTraits::IsMapLike; + + // Values need to track how many bytes they're serializing so the parent can know also. + auto* serialize_num_bytes = arena.allocate (1, 1); + + size_t num_bytes = 0; + if constexpr (std::is_same_v) + { + num_bytes = serialize_string (get().dump(), arena); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + num_bytes = serialize_string (currentValue, arena); + } + else if constexpr (std::is_same_v) + { + num_bytes = serialize_string (toStringView (currentValue), arena); + } + else if constexpr (is_span) + { + num_bytes = serialize_span (currentValue, arena); + } + else + { + num_bytes = serialize_object (get(), arena); + } + + serialize_direct (serialize_num_bytes, num_bytes); + return bytes_detail::sizeof_s + num_bytes; + } + + void deserialize (nonstd::span& data) override + { + static constexpr auto is_span = TypeTraits::IsIterable && ! TypeTraits::IsMapLike; + + [[maybe_unused]] const auto num_bytes = deserialize_direct (data); + if constexpr (std::is_same_v) + { + set (json::parse (deserialize_string (data))); + } + else if constexpr (std::is_same_v) + { + set (std::string { deserialize_string (data) }); + } + else if constexpr (std::is_same_v) + { + set (deserialize_string (data)); + } + else if constexpr (std::is_same_v) + { + set (toString (deserialize_string (data))); + } + else if constexpr (is_span) + { + deserialize_span (currentValue, data); + } + else + { + set (deserialize_object (data)); + } } const element_type defaultValue; diff --git a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.cpp b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.cpp index ccdb63065..943c889c7 100644 --- a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.cpp +++ b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.cpp @@ -1,5 +1,6 @@ #include "chowdsp_plugin_state.h" +#include "Backend/chowdsp_NonParamState.cpp" #include "Backend/chowdsp_ParameterListeners.cpp" #include "Frontend/chowdsp_SliderAttachment.cpp" diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/CMakeLists.txt b/tests/plugin_tests/chowdsp_plugin_state_test/CMakeLists.txt index c3d848edb..67a7b3a5d 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/CMakeLists.txt +++ b/tests/plugin_tests/chowdsp_plugin_state_test/CMakeLists.txt @@ -8,4 +8,5 @@ target_sources(chowdsp_plugin_state_test ParamHolderTest.cpp StatePluginInterfaceTest.cpp VersionStreamingTest.cpp + NonParamTest.cpp ) diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp new file mode 100644 index 000000000..fc7097eed --- /dev/null +++ b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp @@ -0,0 +1,222 @@ +#include +#include + +TEST_CASE ("Non-Param Test", "[plugin][state]") +{ + SECTION ("JSON Serialization") + { + chowdsp::StateValue int_val { "int", 42 }; + chowdsp::StateValue, int> atomic_int_val { "atomic_int", 99 }; + chowdsp::StateValue> bool_vals { "bools", { true, false, true, true } }; + chowdsp::StateValue string_val { "string", "blah" }; + chowdsp::StateValue string_view_val { "string_view", "fff" }; + chowdsp::StateValue juce_string_val { "juce_string", "juce" }; + chowdsp::StateValue json_val { "json", { { "val1", 100 }, { "val2", "test" } } }; + + chowdsp::json serial; + { + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &bool_vals, &string_val, &string_view_val, &juce_string_val, &json_val }); + int_val = 101; + atomic_int_val.set (102); + bool_vals.set ({ false, true, false, true }); + string_val = "blah blah"; + string_view_val = "ggg"; + juce_string_val = "ecuj"; + json_val.set ({}); + serial = chowdsp::NonParamState::serialize_json (state); + } + + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &bool_vals, &string_val, &string_view_val, &juce_string_val, &json_val }); + state.reset(); + REQUIRE (int_val.get() == 42); + REQUIRE (atomic_int_val.get() == 99); + REQUIRE (bool_vals.get()[0]); + REQUIRE (! bool_vals.get()[1]); + REQUIRE (bool_vals.get()[2]); + REQUIRE (bool_vals.get()[3]); + REQUIRE (string_val.get() == "blah"); + REQUIRE (string_view_val.get() == "fff"); + REQUIRE (juce_string_val.get() == "juce"); + REQUIRE (json_val.get() == chowdsp::json { { "val1", 100 }, { "val2", "test" } }); + + chowdsp::NonParamState::deserialize_json (serial, state); + REQUIRE (int_val.get() == 101); + REQUIRE (atomic_int_val.get() == 102); + REQUIRE (! bool_vals.get()[0]); + REQUIRE (bool_vals.get()[1]); + REQUIRE (! bool_vals.get()[2]); + REQUIRE (bool_vals.get()[3]); + REQUIRE (string_val.get() == "blah blah"); + REQUIRE (string_view_val.get() == "ggg"); + REQUIRE (juce_string_val.get() == "ecuj"); + REQUIRE (json_val.get().is_null()); + } + + SECTION ("Bytes Serialization") + { + chowdsp::StateValue int_val { "int", 42 }; + chowdsp::StateValue, int> atomic_int_val { "atomic_int", 99 }; + chowdsp::StateValue> bool_vals { "bools", { true, false, true, true } }; + chowdsp::StateValue string_val { "string", "blah" }; + chowdsp::StateValue string_view_val { "string_view", "fff" }; + chowdsp::StateValue juce_string_val { "juce_string", "juce" }; + chowdsp::StateValue json_val { "json", { { "val1", 100 }, { "val2", "test" } } }; + + chowdsp::ChainedArenaAllocator arena { 1024 }; + { + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &bool_vals, &string_val, &string_view_val, &juce_string_val, &json_val }); + int_val = 101; + atomic_int_val.set (102); + bool_vals.set ({ false, true, false, true }); + string_val = "blah blah"; + string_view_val = "ggg"; + juce_string_val = "ecuj"; + json_val.set ({}); + chowdsp::NonParamState::serialize (arena, state); + } + + const auto serial = chowdsp::dump_serialized_bytes (arena); + + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &bool_vals, &string_val, &string_view_val, &juce_string_val, &json_val }); + state.reset(); + REQUIRE (int_val.get() == 42); + REQUIRE (atomic_int_val.get() == 99); + REQUIRE (bool_vals.get()[0]); + REQUIRE (! bool_vals.get()[1]); + REQUIRE (bool_vals.get()[2]); + REQUIRE (bool_vals.get()[3]); + REQUIRE (string_val.get() == "blah"); + REQUIRE (string_view_val.get() == "fff"); + REQUIRE (juce_string_val.get() == "juce"); + REQUIRE (json_val.get() == chowdsp::json { { "val1", 100 }, { "val2", "test" } }); + + chowdsp::NonParamState::deserialize (serial, state); + REQUIRE (int_val.get() == 101); + REQUIRE (atomic_int_val.get() == 102); + REQUIRE (! bool_vals.get()[0]); + REQUIRE (bool_vals.get()[1]); + REQUIRE (! bool_vals.get()[2]); + REQUIRE (bool_vals.get()[3]); + REQUIRE (string_val.get() == "blah blah"); + REQUIRE (string_view_val.get() == "ggg"); + REQUIRE (juce_string_val.get() == "ecuj"); + REQUIRE (json_val.get().is_null()); + } + + SECTION ("Bytes Serialization Re-order") + { + chowdsp::StateValue int_val { "int", 42 }; + chowdsp::StateValue, int> atomic_int_val { "atomic_int", 99 }; + chowdsp::StateValue string_val { "string", "blah" }; + + chowdsp::ChainedArenaAllocator arena { 1024 }; + { + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &string_val }); + int_val = 101; + atomic_int_val.set (102); + string_val = "blah blah"; + chowdsp::NonParamState::serialize (arena, state); + } + + const auto serial = chowdsp::dump_serialized_bytes (arena); + + chowdsp::NonParamState state {}; + state.addStateValues ({ &string_val, &atomic_int_val, &int_val }); + state.reset(); + REQUIRE (int_val.get() == 42); + REQUIRE (atomic_int_val.get() == 99); + REQUIRE (string_val.get() == "blah"); + + chowdsp::NonParamState::deserialize (serial, state); + REQUIRE (int_val.get() == 101); + REQUIRE (atomic_int_val.get() == 102); + REQUIRE (string_val.get() == "blah blah"); + } + + SECTION ("Bytes Serialization Adding Value") + { + chowdsp::StateValue int_val { "int", 42 }; + chowdsp::StateValue string_val { "string", "blah" }; + + chowdsp::ChainedArenaAllocator arena { 1024 }; + { + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &string_val }); + int_val = 101; + string_val = "blah blah"; + chowdsp::NonParamState::serialize (arena, state); + } + + const auto serial = chowdsp::dump_serialized_bytes (arena); + + chowdsp::StateValue float_val { "float", 90.0f }; + chowdsp::NonParamState state {}; + state.addStateValues ({ &string_val, &float_val, &int_val }); + state.reset(); + float_val = 100.0f; + REQUIRE (int_val.get() == 42); + REQUIRE (float_val.get() == 100.0f); + REQUIRE (string_val.get() == "blah"); + + chowdsp::NonParamState::deserialize (serial, state); + REQUIRE (int_val.get() == 101); + REQUIRE (float_val.get() == 90.0f); + REQUIRE (string_val.get() == "blah blah"); + } + + SECTION ("Bytes Serialization Removing Value") + { + chowdsp::StateValue int_val { "int", 42 }; + chowdsp::StateValue, int> atomic_int_val { "atomic_int", 99 }; + chowdsp::StateValue string_val { "string", "blah" }; + + chowdsp::ChainedArenaAllocator arena { 1024 }; + { + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &string_val }); + int_val = 101; + atomic_int_val.set (102); + string_val = "blah blah"; + chowdsp::NonParamState::serialize (arena, state); + } + + const auto serial = chowdsp::dump_serialized_bytes (arena); + + chowdsp::NonParamState state {}; + state.addStateValues ({ &string_val, &int_val }); + state.reset(); + REQUIRE (int_val.get() == 42); + REQUIRE (string_val.get() == "blah"); + + chowdsp::NonParamState::deserialize (serial, state); + REQUIRE (int_val.get() == 101); + REQUIRE (string_val.get() == "blah blah"); + } + + SECTION ("Bytes Serialization Removing Value") + { + chowdsp::StateValue int_val { "int", 42 }; + chowdsp::StateValue, int> atomic_int_val { "atomic_int", 99 }; + chowdsp::StateValue string_val { "string", "blah" }; + + chowdsp::NonParamState state {}; + state.addStateValues ({ &int_val, &atomic_int_val, &string_val }); + int_val = 101; + atomic_int_val.set (102); + string_val = "blah blah"; + + chowdsp::ChainedArenaAllocator arena { 1024 }; + chowdsp::NonParamState::serialize (arena, state); + const auto serial = chowdsp::dump_serialized_bytes (arena).subspan (0, 25); + + chowdsp::NonParamState::deserialize (serial, state); + REQUIRE (int_val.get() == 42); + REQUIRE (atomic_int_val.get() == 99); + REQUIRE (string_val.get() == "blah"); + } +} From 876a53ef8267631de6492726bbdd47bdaaa6a44e Mon Sep 17 00:00:00 2001 From: jatin Date: Sat, 30 Nov 2024 03:51:37 -0800 Subject: [PATCH 09/20] Binary serialization mostly working --- cmake/test/SetupCatchTest.cmake | 6 +- .../Serialization/chowdsp_ByteSerializer.h | 6 +- .../Backend/chowdsp_NonParamState.cpp | 15 +- .../Backend/chowdsp_ParamHolder.cpp | 155 ++++++++++++++++-- .../Backend/chowdsp_ParamHolder.h | 15 +- .../Backend/chowdsp_PluginStateImpl.cpp | 36 +++- .../NonParamTest.cpp | 4 +- .../VersionStreamingTest.cpp | 1 - 8 files changed, 200 insertions(+), 38 deletions(-) diff --git a/cmake/test/SetupCatchTest.cmake b/cmake/test/SetupCatchTest.cmake index 880e03342..5513d4ca9 100644 --- a/cmake/test/SetupCatchTest.cmake +++ b/cmake/test/SetupCatchTest.cmake @@ -21,9 +21,9 @@ function(setup_catch_test_base target) ) if(NOT (CMAKE_GENERATOR STREQUAL Xcode)) - # catch_discover_tests(${target} - # TEST_PREFIX ${target}_ - # ) + catch_discover_tests(${target} + TEST_PREFIX ${target}_ + ) endif() endfunction(setup_catch_test_base) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index 3a88cbb0f..20d9cff47 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -38,7 +38,7 @@ void serialize_direct (TDest* ptr, const TSource& source) } template -[[nodiscard]] static size_t serialize_object (const T& object, ArenaType& arena) +static size_t serialize_object (const T& object, ArenaType& arena) { auto* bytes = get_bytes_for_serialization (sizeof (T), arena); std::memcpy (bytes, &object, sizeof (T)); // NOLINT @@ -46,7 +46,7 @@ template } template -[[nodiscard]] static size_t serialize_span (nonstd::span data, ArenaType& arena) +static size_t serialize_span (nonstd::span data, ArenaType& arena) { const auto num_bytes = sizeof (T) * data.size(); auto* bytes = get_bytes_for_serialization (num_bytes, arena); @@ -55,7 +55,7 @@ template } template -[[nodiscard]] static size_t serialize_string (std::string_view str, ArenaType& arena) +static size_t serialize_string (std::string_view str, ArenaType& arena) { const auto num_bytes = sizeof (char) * str.size(); auto* bytes = get_bytes_for_serialization (num_bytes, arena); diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index d5f1cf216..7bc0157dc 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -46,12 +46,15 @@ void NonParamState::serialize (ChainedArenaAllocator& arena, const NonParamState void NonParamState::deserialize (nonstd::span serial_data, NonParamState& state) { auto num_bytes = deserialize_direct (serial_data); - if (num_bytes == 0 || serial_data.size() < num_bytes) + if (num_bytes == 0) { state.reset(); return; } + auto data = serial_data.subspan (0, num_bytes); + serial_data = serial_data.subspan (num_bytes); + auto values_copy = state.values; auto values_iter = values_copy.begin(); const auto get_value_ptr = [&] (std::string_view name) -> StateValueBase* @@ -78,18 +81,18 @@ void NonParamState::deserialize (nonstd::span serial_data, NonP return nullptr; }; - while (serial_data.size() > 0) + while (data.size() > 0) { - const auto value_name = deserialize_string (serial_data); + const auto value_name = deserialize_string (data); auto* value = get_value_ptr (value_name); if (value == nullptr) { - const auto value_num_bytes = deserialize_direct (serial_data); - serial_data = serial_data.subspan (value_num_bytes); + const auto value_num_bytes = deserialize_direct (data); + data = data.subspan (value_num_bytes); continue; } - value->deserialize (serial_data); + value->deserialize (data); } for (auto* value : values_copy) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index 9d367232f..f7cda0aa6 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -246,29 +246,160 @@ size_t ParamHolder::doForAllParameters (Callable&& callable, size_t index) const return index; } -inline json ParamHolder::serialize (const ParamHolder& paramHolder) +inline void ParamHolder::getParameterPointers (ParamHolder& holder, ParamDeserialVec& parameters) { -#if ! CHOWDSP_USE_LEGACY_STATE_SERIALIZATION - auto serial = nlohmann::json::object(); + for (auto& thing : holder.things) + { + const auto type = getType (thing); + if (type == Holder) + { + getParameterPointers (*reinterpret_cast (thing.get_ptr()), parameters); + continue; + } + + std::string_view paramID {}; + switch (type) + { + case FloatParam: + paramID = toStringView (reinterpret_cast (thing.get_ptr())->paramID); + break; + case ChoiceParam: + paramID = toStringView (reinterpret_cast (thing.get_ptr())->paramID); + break; + case BoolParam: + paramID = toStringView (reinterpret_cast (thing.get_ptr())->paramID); + break; + default: + break; + } + + parameters.emplace_back (paramID, thing); + } +} + +inline void ParamHolder::serialize (ChainedArenaAllocator& arena, const ParamHolder& paramHolder) +{ + auto* serialize_num_bytes = arena.allocate (1, 1); + size_t num_bytes = 0; paramHolder.doForAllParameters ( - [&serial] (auto& param, size_t) + [&] (auto& param, size_t) { - const auto paramID = toStringView (param.paramID); - serial[paramID] = ParameterTypeHelpers::getValue (param); + num_bytes += serialize_string (toStringView (param.paramID), arena); + num_bytes += serialize_object (ParameterTypeHelpers::getValue (param), arena); }); - return serial; -#else - auto serial = Serializer::createBaseElement(); + serialize_direct (serialize_num_bytes, num_bytes); +} + +inline void ParamHolder::deserialize (nonstd::span& serial_data, ParamHolder& paramHolder) +{ + using namespace ParameterTypeHelpers; + auto num_bytes = deserialize_direct (serial_data); + if (num_bytes == 0) + { + paramHolder.doForAllParameters ( + [&] (auto& param, size_t) + { + ParameterTypeHelpers::resetParameter (param); + }); + return; + } + + auto data = serial_data.subspan (0, num_bytes); + serial_data = serial_data.subspan (num_bytes); + + ParamDeserialVec parameters { ParamDeserialAlloc { *paramHolder.arena } }; + parameters.reserve ((size_t) paramHolder.count()); + getParameterPointers (paramHolder, parameters); + + auto params_iter = parameters.begin(); + const auto get_param_ptr = [&] (std::string_view paramID) -> ThingPtr + { + for (auto iter = params_iter; iter != parameters.end(); ++iter) + { + if (iter->first == paramID) + { + auto ptr = iter->second; + params_iter = parameters.erase (iter); + return ptr; + } + } + + for (auto iter = parameters.begin(); iter != params_iter; ++iter) + { + if (iter->first == paramID) + { + auto ptr = iter->second; + params_iter = parameters.erase (iter); + return ptr; + } + } + return {}; + }; + + while (data.size() > 0) + { + const auto param_id = deserialize_string (data); + auto param_ptr = get_param_ptr (param_id); + if (param_ptr == nullptr) + { + const auto param_num_bytes = deserialize_direct (data); + data = data.subspan (param_num_bytes); + continue; + } + + const auto type = getType (param_ptr); + switch (type) + { + case FloatParam: + setValue (deserialize_object> (data), + *reinterpret_cast (param_ptr.get_ptr())); + break; + case ChoiceParam: + setValue (deserialize_object> (data), + *reinterpret_cast (param_ptr.get_ptr())); + break; + case BoolParam: + setValue (deserialize_object> (data), + *reinterpret_cast (param_ptr.get_ptr())); + break; + default: + break; + } + } + + for (auto [_, param_ptr] : parameters) + { + const auto type = getType (param_ptr); + switch (type) + { + case FloatParam: + resetParameter (*reinterpret_cast (param_ptr.get_ptr())); + break; + case ChoiceParam: + resetParameter (*reinterpret_cast (param_ptr.get_ptr())); + break; + case BoolParam: + resetParameter (*reinterpret_cast (param_ptr.get_ptr())); + break; + default: + break; + } + } +} + +inline json ParamHolder::serialize_json (const ParamHolder& paramHolder) +{ + auto serial = nlohmann::json::object(); paramHolder.doForAllParameters ( [&serial] (auto& param, size_t) { - ParameterTypeHelpers::serializeParameter (serial, param); + const auto paramID = toStringView (param.paramID); + serial[paramID] = ParameterTypeHelpers::getValue (param); }); return serial; -#endif } -inline void ParamHolder::deserialize (const json& deserial, ParamHolder& paramHolder) +inline void ParamHolder::deserialize_json (const json& deserial, ParamHolder& paramHolder) { paramHolder.doForAllParameters ( [&deserial] (auto& param, size_t) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h index d249592ec..acb55d07f 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h @@ -92,10 +92,16 @@ class ParamHolder size_t doForAllParameters (Callable&& callable, size_t index = 0) const; /** Custom serializer */ - static json serialize (const ParamHolder& paramHolder); + static void serialize (ChainedArenaAllocator& arena, const ParamHolder& paramHolder); /** Custom deserializer */ - static void deserialize (const json& deserial, ParamHolder& paramHolder); + static void deserialize (nonstd::span& serial_data, ParamHolder& paramHolder); + + /** Custom serializer */ + static json serialize_json (const ParamHolder& paramHolder); + + /** Custom deserializer */ + static void deserialize_json (const json& deserial, ParamHolder& paramHolder); /** Legacy deserializer */ static void legacy_deserialize (const json& deserial, ParamHolder& paramHolder); @@ -139,6 +145,11 @@ class ParamHolder return static_cast (type | (shouldDelete ? ShouldDelete : 0)); } + using ParamDeserial = std::pair; + using ParamDeserialAlloc = STLArenaAllocator; + using ParamDeserialVec = std::vector; + static void getParameterPointers (ParamHolder& holder, ParamDeserialVec& parameters); + std::string_view name; bool isOwning; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 152424c8c..d9c5e1bf9 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -18,8 +18,17 @@ void PluginStateImpl::serialize (juce::Memory auto& arena = const_cast (*params.arena); const auto frame = arena.create_frame(); - const auto serial = serialize (*this); - JSONUtils::toMemoryBlock (serial, data); +#if defined JucePlugin_VersionString + serialize_object (currentPluginVersion.getVersionHint(), arena); +#else + serialize_object (int {}, arena); +#endif + + ParamHolder::serialize (arena, params); + NonParamState::serialize (arena, nonParams); + + const auto serial = dump_serialized_bytes (arena, &frame); + data.append (serial.begin(), serial.size()); } template @@ -28,7 +37,19 @@ void PluginStateImpl::deserialize (juce::Memo callOnMainThread ( [this, data = std::move (dataBlock)] { - deserialize (JSONUtils::fromMemoryBlock (data), *this); + nonstd::span serial_data { static_cast (data.getData()), data.getSize() }; + if (serial_data.size() > 8 + && (static_cast (serial_data[2]) == '[' + || static_cast (serial_data[2]) == '{')) + { + deserialize (JSONUtils::fromMemoryBlock (data), *this); + } + else + { + pluginStateVersion = Version::fromVersionHint (deserialize_object (serial_data)); + ParamHolder::deserialize (serial_data, params); + NonParamState::deserialize (serial_data, nonParams); + } params.applyVersionStreaming (pluginStateVersion); if (nonParams.versionStreamingCallback != nullptr) @@ -45,15 +66,14 @@ void PluginStateImpl::deserialize (juce::Memo template json PluginStateImpl::serialize (const PluginStateImpl& object) { - return - { + return { #if defined JucePlugin_VersionString { "version", currentPluginVersion }, #else { "version", chowdsp::Version {} }, #endif - { "params", ParamHolder::serialize (object.params) }, - { "non-params", NonParamState::serialize_json (object.nonParams) }, + { "params", ParamHolder::serialize_json (object.params) }, + { "non-params", NonParamState::serialize_json (object.nonParams) }, }; } @@ -103,7 +123,7 @@ void PluginStateImpl::deserialize (const json object.pluginStateVersion = serial.value ("version", Version {}); NonParamState::deserialize_json (serial.at ("non-params"), object.nonParams); - ParamHolder::deserialize (serial.at ("params"), object.params); + ParamHolder::deserialize_json (serial.at ("params"), object.params); } template diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp index fc7097eed..b7261b7c6 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp @@ -210,9 +210,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") atomic_int_val.set (102); string_val = "blah blah"; - chowdsp::ChainedArenaAllocator arena { 1024 }; - chowdsp::NonParamState::serialize (arena, state); - const auto serial = chowdsp::dump_serialized_bytes (arena).subspan (0, 25); + const auto serial = std::array {}; chowdsp::NonParamState::deserialize (serial, state); REQUIRE (int_val.get() == 42); diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp index a9e8a7c93..0656c4ccc 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/VersionStreamingTest.cpp @@ -83,7 +83,6 @@ TEST_CASE ("Version Streaming Test", "[plugin][state][version]") // Save initial state State state {}; state.serialize (stateBlock); - std::cout << chowdsp::JSONUtils::fromMemoryBlock (stateBlock).dump() << std::endl; } // check new state From b10f994598c3fd11a1f9a37314ff9c940cb69f2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 30 Nov 2024 11:52:09 +0000 Subject: [PATCH 10/20] Apply clang-format --- .../Serialization/chowdsp_ByteSerializer.h | 2 +- .../Backend/chowdsp_PluginStateImpl.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index 20d9cff47..ff0b221a8 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -65,7 +65,7 @@ static size_t serialize_string (std::string_view str, ArenaType& arena) template static nonstd::span dump_serialized_bytes (const ArenaAllocator& arena, - const typename ArenaAllocator::Frame* frame = nullptr) + const typename ArenaAllocator::Frame* frame = nullptr) { const auto bytes_offset = frame == nullptr ? 0 : frame->bytes_used_at_start; const auto bytes_count = arena.get_bytes_used() - bytes_offset; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index d9c5e1bf9..2cb1e05e5 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -66,14 +66,15 @@ void PluginStateImpl::deserialize (juce::Memo template json PluginStateImpl::serialize (const PluginStateImpl& object) { - return { + return + { #if defined JucePlugin_VersionString { "version", currentPluginVersion }, #else { "version", chowdsp::Version {} }, #endif - { "params", ParamHolder::serialize_json (object.params) }, - { "non-params", NonParamState::serialize_json (object.nonParams) }, + { "params", ParamHolder::serialize_json (object.params) }, + { "non-params", NonParamState::serialize_json (object.nonParams) }, }; } From e0833035203dcdcf0924d4aae344c395060562aa Mon Sep 17 00:00:00 2001 From: jatin Date: Sat, 30 Nov 2024 03:54:21 -0800 Subject: [PATCH 11/20] Mossing arena frame --- .../plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index f7cda0aa6..60c03cd5b 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -307,6 +307,7 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data auto data = serial_data.subspan (0, num_bytes); serial_data = serial_data.subspan (num_bytes); + const auto _ = paramHolder.arena->create_frame(); ParamDeserialVec parameters { ParamDeserialAlloc { *paramHolder.arena } }; parameters.reserve ((size_t) paramHolder.count()); getParameterPointers (paramHolder, parameters); From 47dc588f787cdc572dd93ae4ae3c3e19ba1b763e Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 1 Dec 2024 14:25:51 -0800 Subject: [PATCH 12/20] Tweaking plugin state arena usage --- .../Allocators/chowdsp_ChainedArenaAllocator.h | 5 +++++ .../Backend/chowdsp_ParamHolder.cpp | 11 +++++++++-- .../Backend/chowdsp_PluginState.h | 4 +++- .../Backend/chowdsp_PluginStateImpl.cpp | 7 +++++++ .../Backend/chowdsp_PluginStateImpl.h | 2 ++ .../chowdsp_plugin_state/chowdsp_plugin_state.h | 4 ++++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h b/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h index 0ae197b75..688d27af9 100644 --- a/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h +++ b/modules/common/chowdsp_data_structures/Allocators/chowdsp_ChainedArenaAllocator.h @@ -139,6 +139,11 @@ class ChainedArenaAllocator return arena_list.count; } + [[nodiscard]] auto* get_extra_alloc_list() const noexcept + { + return extra_alloc_list; + } + /** * Returns the total number of bytes currently being used * by this allocator. diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index 60c03cd5b..d23977b2a 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -10,7 +10,7 @@ inline ParamHolder::ParamHolder (ParamHolder* parent, std::string_view phName, b jassert (parent->arena != nullptr); return OptionalPointer { parent->arena.get(), false }; } - return OptionalPointer { static_cast (1024) }; + return OptionalPointer { static_cast (24 * CHOWDSP_PLUGIN_STATE_MAX_PARAM_COUNT) }; }(), }, name { arena::alloc_string (*arena, phName) }, @@ -47,6 +47,13 @@ inline ParamHolder::~ParamHolder() } } } + + if (arena.isOwner()) + { + // If you're hitting this assertion, you probably want to increase + // CHOWDSP_PLUGIN_STATE_MAX_PARAM_COUNT. + jassert (arena->get_extra_alloc_list() == nullptr); + } } template @@ -368,7 +375,7 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data } } - for (auto [_, param_ptr] : parameters) + for (auto [param_id, param_ptr] : parameters) { const auto type = getType (param_ptr); switch (type) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h index d0e903f2f..a0e1ecd59 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginState.h @@ -84,8 +84,10 @@ class PluginState juce::AudioProcessor* processor = nullptr; juce::UndoManager* undoManager = nullptr; -private: +protected: std::optional listeners; + +private: ParamHolder* params = nullptr; DeferredAction mainThreadAction; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 2cb1e05e5..7379d0708 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -12,6 +12,13 @@ PluginStateImpl::PluginStateImpl (juce::Audio initialise (params, &proc, um); } +template +PluginStateImpl::~PluginStateImpl() +{ + // Otherwise the listeners won't be deleted until after the parameters themselves. + listeners.reset(); +} + template void PluginStateImpl::serialize (juce::MemoryBlock& data) const { diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h index 1a0d119ff..dbbdd3e10 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.h @@ -21,6 +21,8 @@ class PluginStateImpl : public PluginState /** Constructs the state and adds all the state parameters to the given processor */ explicit PluginStateImpl (juce::AudioProcessor& proc, juce::UndoManager* um = nullptr); + ~PluginStateImpl() override; + /** Serializes the plugin state to the given MemoryBlock */ void serialize (juce::MemoryBlock& data) const override; diff --git a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h index 22919c92c..05ae4252d 100644 --- a/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h +++ b/modules/plugin/chowdsp_plugin_state/chowdsp_plugin_state.h @@ -20,6 +20,10 @@ BEGIN_JUCE_MODULE_DECLARATION #pragma once +#ifndef CHOWDSP_PLUGIN_STATE_MAX_PARAM_COUNT +#define CHOWDSP_PLUGIN_STATE_MAX_PARAM_COUNT 128 +#endif + #include #include #include From e9d76467216e62e334ef91b94eba29f990f88852 Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 1 Dec 2024 14:39:17 -0800 Subject: [PATCH 13/20] Small fixes --- .../chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp | 5 +++-- .../chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index d23977b2a..37d40a5a6 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -10,7 +10,7 @@ inline ParamHolder::ParamHolder (ParamHolder* parent, std::string_view phName, b jassert (parent->arena != nullptr); return OptionalPointer { parent->arena.get(), false }; } - return OptionalPointer { static_cast (24 * CHOWDSP_PLUGIN_STATE_MAX_PARAM_COUNT) }; + return OptionalPointer { static_cast (32 * CHOWDSP_PLUGIN_STATE_MAX_PARAM_COUNT) }; }(), }, name { arena::alloc_string (*arena, phName) }, @@ -374,7 +374,7 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data break; } } - +/* for (auto [param_id, param_ptr] : parameters) { const auto type = getType (param_ptr); @@ -393,6 +393,7 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data break; } } + */ } inline json ParamHolder::serialize_json (const ParamHolder& paramHolder) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 7379d0708..0cc36d38e 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -35,7 +35,7 @@ void PluginStateImpl::serialize (juce::Memory NonParamState::serialize (arena, nonParams); const auto serial = dump_serialized_bytes (arena, &frame); - data.append (serial.begin(), serial.size()); + data.append (serial.data(), serial.size()); } template From 38f1427743b076bfe6d2e3bb69d8cd61376e1605 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 Dec 2024 22:39:49 +0000 Subject: [PATCH 14/20] Apply clang-format --- .../plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index 37d40a5a6..d21c4332f 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -374,7 +374,7 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data break; } } -/* + /* for (auto [param_id, param_ptr] : parameters) { const auto type = getType (param_ptr); From 75cc51c06a97fd8fd5497d8f7cb9b3d4a73ccbe8 Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 1 Dec 2024 17:45:44 -0800 Subject: [PATCH 15/20] Using ChunkList for parameter deserialization --- .../Serialization/chowdsp_ByteSerializer.cpp | 26 +++++-- .../Serialization/chowdsp_ByteSerializer.h | 12 +++- .../Backend/chowdsp_ParamHolder.cpp | 71 ++++++++++--------- .../Backend/chowdsp_ParamHolder.h | 12 ++-- .../Backend/chowdsp_PluginStateImpl.cpp | 3 +- .../ByteSerializationTest.cpp | 8 ++- .../NonParamTest.cpp | 20 +++--- .../StateSerializationTest.cpp | 45 +++++++++++- 8 files changed, 142 insertions(+), 55 deletions(-) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp index 2ca18b0c9..41a9468ef 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.cpp @@ -14,8 +14,8 @@ nonstd::span get_bytes_for_deserialization (nonstd::span dump_serialized_bytes (ChainedArenaAllocator& arena, - const ChainedArenaAllocator::Frame* frame) +size_t get_serial_num_bytes (ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame) { size_t num_bytes = 0; auto* start_arena = frame == nullptr ? arena.get_arenas().head : frame->arena_at_start; @@ -31,7 +31,17 @@ nonstd::span dump_serialized_bytes (ChainedArenaAllocator& aren add_bytes_count (*arena_node); add_bytes_count (arena.get_current_arena()); - auto serial = arena::make_span (arena, num_bytes, 8); + return num_bytes; +} + +void dump_serialized_bytes (nonstd::span serial, + ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame) +{ + const auto num_bytes = serial.size(); + jassert (num_bytes == get_serial_num_bytes (arena, frame)); + + auto* start_arena = frame == nullptr ? arena.get_arenas().head : frame->arena_at_start; size_t bytes_counter = 0; const auto copy_bytes = [num_bytes, start_arena, frame, &serial, &bytes_counter] (const ArenaAllocatorView& arena_node) { @@ -48,8 +58,16 @@ nonstd::span dump_serialized_bytes (ChainedArenaAllocator& aren for (auto* arena_node = start_arena; arena_node != &arena.get_current_arena(); arena_node = arena_node->next) copy_bytes (*arena_node); copy_bytes (arena.get_current_arena()); +} - return serial; +void dump_serialized_bytes (juce::MemoryBlock& data, + ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame) +{ + const auto initial_size = data.getSize(); + const auto num_bytes = get_serial_num_bytes (arena, frame); + data.setSize (initial_size + num_bytes); + dump_serialized_bytes ({ static_cast (data.getData()) + initial_size, num_bytes }, arena, frame); } std::string_view deserialize_string (nonstd::span& bytes) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index ff0b221a8..3695e294f 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -72,8 +72,16 @@ static nonstd::span dump_serialized_bytes (const ArenaAllocator return { arena.template data (bytes_offset), bytes_count }; } -nonstd::span dump_serialized_bytes (ChainedArenaAllocator& arena, - const ChainedArenaAllocator::Frame* frame = nullptr); +size_t get_serial_num_bytes (ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame = nullptr); + +void dump_serialized_bytes (nonstd::span serial, + ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame = nullptr); + +void dump_serialized_bytes (juce::MemoryBlock& data, + ChainedArenaAllocator& arena, + const ChainedArenaAllocator::Frame* frame = nullptr); template T deserialize_direct (nonstd::span& bytes) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index d21c4332f..39528c041 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -253,7 +253,7 @@ size_t ParamHolder::doForAllParameters (Callable&& callable, size_t index) const return index; } -inline void ParamHolder::getParameterPointers (ParamHolder& holder, ParamDeserialVec& parameters) +inline void ParamHolder::getParameterPointers (ParamHolder& holder, ParamDeserialList& parameters) { for (auto& thing : holder.things) { @@ -280,7 +280,7 @@ inline void ParamHolder::getParameterPointers (ParamHolder& holder, ParamDeseria break; } - parameters.emplace_back (paramID, thing); + parameters.insert (ParamDeserial { .id = paramID, .ptr = thing, .found = false }); } } @@ -315,31 +315,33 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data serial_data = serial_data.subspan (num_bytes); const auto _ = paramHolder.arena->create_frame(); - ParamDeserialVec parameters { ParamDeserialAlloc { *paramHolder.arena } }; - parameters.reserve ((size_t) paramHolder.count()); + ParamDeserialList parameters { *paramHolder.arena }; getParameterPointers (paramHolder, parameters); auto params_iter = parameters.begin(); + size_t counter = 0; const auto get_param_ptr = [&] (std::string_view paramID) -> ThingPtr { + const auto returner = [&] (auto& iter) + { + (*iter).found = true; + auto ptr = (*iter).ptr; + ++iter; + params_iter = iter; + counter++; + return ptr; + }; + for (auto iter = params_iter; iter != parameters.end(); ++iter) { - if (iter->first == paramID) - { - auto ptr = iter->second; - params_iter = parameters.erase (iter); - return ptr; - } + if ((*iter).id == paramID) + return returner (iter); } for (auto iter = parameters.begin(); iter != params_iter; ++iter) { - if (iter->first == paramID) - { - auto ptr = iter->second; - params_iter = parameters.erase (iter); - return ptr; - } + if ((*iter).id == paramID) + return returner (iter); } return {}; }; @@ -374,26 +376,31 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data break; } } - /* - for (auto [param_id, param_ptr] : parameters) + + if (counter < parameters.count()) { - const auto type = getType (param_ptr); - switch (type) + for (auto [param_id, param_ptr, found] : parameters) { - case FloatParam: - resetParameter (*reinterpret_cast (param_ptr.get_ptr())); - break; - case ChoiceParam: - resetParameter (*reinterpret_cast (param_ptr.get_ptr())); - break; - case BoolParam: - resetParameter (*reinterpret_cast (param_ptr.get_ptr())); - break; - default: - break; + if (found) + continue; + + const auto type = getType (param_ptr); + switch (type) + { + case FloatParam: + resetParameter (*reinterpret_cast (param_ptr.get_ptr())); + break; + case ChoiceParam: + resetParameter (*reinterpret_cast (param_ptr.get_ptr())); + break; + case BoolParam: + resetParameter (*reinterpret_cast (param_ptr.get_ptr())); + break; + default: + break; + } } } - */ } inline json ParamHolder::serialize_json (const ParamHolder& paramHolder) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h index acb55d07f..b7582f0a7 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.h @@ -145,10 +145,14 @@ class ParamHolder return static_cast (type | (shouldDelete ? ShouldDelete : 0)); } - using ParamDeserial = std::pair; - using ParamDeserialAlloc = STLArenaAllocator; - using ParamDeserialVec = std::vector; - static void getParameterPointers (ParamHolder& holder, ParamDeserialVec& parameters); + struct ParamDeserial + { + std::string_view id {}; + ThingPtr ptr {}; + bool found = false; + }; + using ParamDeserialList = ChunkList; + static void getParameterPointers (ParamHolder& holder, ParamDeserialList& parameters); std::string_view name; bool isOwning; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 0cc36d38e..94fda6db1 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -34,8 +34,7 @@ void PluginStateImpl::serialize (juce::Memory ParamHolder::serialize (arena, params); NonParamState::serialize (arena, nonParams); - const auto serial = dump_serialized_bytes (arena, &frame); - data.append (serial.data(), serial.size()); + dump_serialized_bytes (data, arena, &frame); } template diff --git a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp index 45bc1af64..0002b4876 100644 --- a/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp +++ b/tests/common_tests/chowdsp_serialization_test/ByteSerializationTest.cpp @@ -100,7 +100,9 @@ TEST_CASE ("Byte Serialization Test", "[common][serialization]") chowdsp::serialize_span (arr, arena); chowdsp::serialize_string (str, arena); - auto bytes = chowdsp::dump_serialized_bytes (arena); + std::vector raw_bytes (chowdsp::get_serial_num_bytes (arena)); + chowdsp::dump_serialized_bytes (raw_bytes, arena); + nonstd::span bytes { raw_bytes }; const auto int_test = chowdsp::deserialize_object (bytes); REQUIRE (int_test == 42); @@ -138,7 +140,9 @@ TEST_CASE ("Byte Serialization Test", "[common][serialization]") chowdsp::serialize_span (vec, arena); chowdsp::serialize_string (chowdsp::toStringView (str), arena); - auto bytes = chowdsp::dump_serialized_bytes (arena, &frame); + juce::MemoryBlock raw_data {}; + chowdsp::dump_serialized_bytes (raw_data, arena, &frame); + nonstd::span bytes { static_cast (raw_data.getData()), raw_data.getSize() }; const auto int_test = chowdsp::deserialize_object (bytes); REQUIRE (int_test == 42); diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp index b7261b7c6..ce6857340 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp @@ -78,7 +78,8 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") chowdsp::NonParamState::serialize (arena, state); } - const auto serial = chowdsp::dump_serialized_bytes (arena); + std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); + chowdsp::dump_serialized_bytes (serial_bytes, arena); chowdsp::NonParamState state {}; state.addStateValues ({ &int_val, &atomic_int_val, &bool_vals, &string_val, &string_view_val, &juce_string_val, &json_val }); @@ -94,7 +95,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (juce_string_val.get() == "juce"); REQUIRE (json_val.get() == chowdsp::json { { "val1", 100 }, { "val2", "test" } }); - chowdsp::NonParamState::deserialize (serial, state); + chowdsp::NonParamState::deserialize (serial_bytes, state); REQUIRE (int_val.get() == 101); REQUIRE (atomic_int_val.get() == 102); REQUIRE (! bool_vals.get()[0]); @@ -123,7 +124,8 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") chowdsp::NonParamState::serialize (arena, state); } - const auto serial = chowdsp::dump_serialized_bytes (arena); + std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); + chowdsp::dump_serialized_bytes (serial_bytes, arena); chowdsp::NonParamState state {}; state.addStateValues ({ &string_val, &atomic_int_val, &int_val }); @@ -132,7 +134,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (atomic_int_val.get() == 99); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial, state); + chowdsp::NonParamState::deserialize (serial_bytes, state); REQUIRE (int_val.get() == 101); REQUIRE (atomic_int_val.get() == 102); REQUIRE (string_val.get() == "blah blah"); @@ -152,7 +154,8 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") chowdsp::NonParamState::serialize (arena, state); } - const auto serial = chowdsp::dump_serialized_bytes (arena); + std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); + chowdsp::dump_serialized_bytes (serial_bytes, arena); chowdsp::StateValue float_val { "float", 90.0f }; chowdsp::NonParamState state {}; @@ -163,7 +166,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (float_val.get() == 100.0f); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial, state); + chowdsp::NonParamState::deserialize (serial_bytes, state); REQUIRE (int_val.get() == 101); REQUIRE (float_val.get() == 90.0f); REQUIRE (string_val.get() == "blah blah"); @@ -185,7 +188,8 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") chowdsp::NonParamState::serialize (arena, state); } - const auto serial = chowdsp::dump_serialized_bytes (arena); + std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); + chowdsp::dump_serialized_bytes (serial_bytes, arena); chowdsp::NonParamState state {}; state.addStateValues ({ &string_val, &int_val }); @@ -193,7 +197,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (int_val.get() == 42); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial, state); + chowdsp::NonParamState::deserialize (serial_bytes, state); REQUIRE (int_val.get() == 101); REQUIRE (string_val.get() == "blah blah"); } diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp index 86856686c..fac60ec21 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/StateSerializationTest.cpp @@ -72,17 +72,32 @@ struct PluginParameterStateNewParam : chowdsp::ParamHolder { PluginParameterStateNewParam() { - add (levelParams, mode, onOff, newParam); + add (newParam2, onOff, mode, newParam3, levelParams, newParam); } LevelParams levelParams { this }; chowdsp::ChoiceParameter::Ptr mode { "mode", "Mode", juce::StringArray { "Percent", "Gain", "Percent / Gain", "Gain / Percent" }, 2 }; chowdsp::BoolParameter::Ptr onOff { "on_off", "On/Off", true }; chowdsp::GainDBParameter::Ptr newParam { "gain_new", "New Gain", juce::NormalisableRange { -45.0f, 12.0f }, 3.3f }; + chowdsp::ChoiceParameter::Ptr newParam2 { "choice_new", "New Choice", juce::StringArray { "a", "b", "c" }, 2 }; + chowdsp::BoolParameter::Ptr newParam3 { "bool_new", "New Bool", false }; }; using StateWithNewParam = chowdsp::PluginStateImpl; +struct PluginParameterStateRemovedParam : chowdsp::ParamHolder +{ + PluginParameterStateRemovedParam() + { + add (levelParams, onOff); + } + + LevelParams levelParams { this }; + chowdsp::BoolParameter::Ptr onOff { "on_off", "On/Off", true }; +}; + +using StateWithRemovedParam = chowdsp::PluginStateImpl; + struct PluginParameterStateDoubleOfSameType : chowdsp::ParamHolder { PluginParameterStateDoubleOfSameType() @@ -220,6 +235,8 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") SECTION ("Added Parameter Test") { static constexpr float newGainVal = -22.0f; + static constexpr int newChoiceVal = 0; + static constexpr bool newBoolVal = true; juce::MemoryBlock block; { @@ -229,8 +246,34 @@ TEST_CASE ("State Serialization Test", "[plugin][state]") StateWithNewParam state; static_cast (state.params.newParam) = newGainVal; + static_cast (state.params.newParam2) = newChoiceVal; + static_cast (state.params.newParam3) = newBoolVal; state.deserialize (std::move (block)); REQUIRE_MESSAGE (state.params.newParam->get() == Catch::Approx (3.3f).margin (1.0e-6f), "Added param value is incorrect"); + REQUIRE_MESSAGE (state.params.newParam2->getIndex() == 2, "Added param value is incorrect"); + REQUIRE_MESSAGE (state.params.newParam3->get() == false, "Added param value is incorrect"); + } + + SECTION ("Removed Parameter Test") + { + static constexpr float percentVal = 0.25f; + static constexpr float gainVal = -22.0f; + static constexpr bool boolVal = false; + + juce::MemoryBlock block; + { + State state; + static_cast (state.params.levelParams.percent) = percentVal; + static_cast (state.params.levelParams.gain) = gainVal; + static_cast (state.params.onOff) = boolVal; + state.serialize (block); + } + + StateWithRemovedParam state {}; + state.deserialize (std::move (block)); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.percent->get(), percentVal), "Percent value is incorrect"); + REQUIRE_MESSAGE (juce::approximatelyEqual (state.params.levelParams.gain->get(), gainVal), "Gain value is incorrect"); + REQUIRE_MESSAGE (state.params.onOff->get() == boolVal, "Bool value is incorrect"); } SECTION ("Added Parameter Group Test") From 4a4b5f50a05b9ebc485cc8c1eb11c751ed3c29f3 Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 1 Dec 2024 21:02:38 -0800 Subject: [PATCH 16/20] Updating non-param state --- .../Backend/chowdsp_NonParamState.cpp | 42 +++++++++++-------- .../Backend/chowdsp_NonParamState.h | 2 +- .../Backend/chowdsp_ParamHolder.cpp | 1 - .../Backend/chowdsp_PluginStateImpl.cpp | 2 +- .../NonParamTest.cpp | 11 ++--- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index 7bc0157dc..9557dd918 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -43,7 +43,7 @@ void NonParamState::serialize (ChainedArenaAllocator& arena, const NonParamState serialize_direct (serialize_num_bytes, num_bytes); } -void NonParamState::deserialize (nonstd::span serial_data, NonParamState& state) +void NonParamState::deserialize (nonstd::span serial_data, NonParamState& state, ChainedArenaAllocator& arena) { auto num_bytes = deserialize_direct (serial_data); if (num_bytes == 0) @@ -55,28 +55,32 @@ void NonParamState::deserialize (nonstd::span serial_data, NonP auto data = serial_data.subspan (0, num_bytes); serial_data = serial_data.subspan (num_bytes); - auto values_copy = state.values; + const auto _ = arena.create_frame(); + auto values_copy = arena::make_span (arena, state.values.size()); + std::copy (state.values.begin(), state.values.end(), values_copy.begin()); auto values_iter = values_copy.begin(); + size_t counter = 0; const auto get_value_ptr = [&] (std::string_view name) -> StateValueBase* { + const auto returner = [&] (auto& iter) + { + auto* ptr = *iter; + *iter = nullptr; + ++iter; + values_iter = iter; + counter++; + return ptr; + }; + for (auto iter = values_iter; iter != values_copy.end(); ++iter) { - if ((*iter)->name == name) - { - auto* ptr = *iter; - values_iter = values_copy.erase (iter); - return ptr; - } + if (*iter != nullptr && (*iter)->name == name) + return returner (iter); } - for (auto iter = values_copy.begin(); iter != values_iter; ++iter) { - if ((*iter)->name == name) - { - auto* ptr = *iter; - values_iter = values_copy.erase (iter); - return ptr; - } + if (*iter != nullptr && (*iter)->name == name) + return returner (iter); } return nullptr; }; @@ -95,8 +99,12 @@ void NonParamState::deserialize (nonstd::span serial_data, NonP value->deserialize (data); } - for (auto* value : values_copy) - value->reset(); + if (counter < values_copy.size()) + { + for (auto* value : values_copy) + if (value != nullptr) + value->reset(); + } } json NonParamState::serialize_json (const NonParamState& state) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h index 01bb6a4fb..dbdc865c5 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h @@ -39,7 +39,7 @@ class NonParamState static void serialize (ChainedArenaAllocator& arena, const NonParamState& state); /** Custom deserializer */ - static void deserialize (nonstd::span serial_data, NonParamState& state); + static void deserialize (nonstd::span serial_data, NonParamState& state, ChainedArenaAllocator& arena); /** Custom serializer */ static json serialize_json (const NonParamState& state); diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index 39528c041..2782ae787 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -337,7 +337,6 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data if ((*iter).id == paramID) return returner (iter); } - for (auto iter = parameters.begin(); iter != params_iter; ++iter) { if ((*iter).id == paramID) diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index 94fda6db1..fb46cfc3d 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -54,7 +54,7 @@ void PluginStateImpl::deserialize (juce::Memo { pluginStateVersion = Version::fromVersionHint (deserialize_object (serial_data)); ParamHolder::deserialize (serial_data, params); - NonParamState::deserialize (serial_data, nonParams); + NonParamState::deserialize (serial_data, nonParams, *params.arena); } params.applyVersionStreaming (pluginStateVersion); diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp index ce6857340..60bf7c173 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp @@ -95,7 +95,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (juce_string_val.get() == "juce"); REQUIRE (json_val.get() == chowdsp::json { { "val1", 100 }, { "val2", "test" } }); - chowdsp::NonParamState::deserialize (serial_bytes, state); + chowdsp::NonParamState::deserialize (serial_bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (atomic_int_val.get() == 102); REQUIRE (! bool_vals.get()[0]); @@ -134,7 +134,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (atomic_int_val.get() == 99); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial_bytes, state); + chowdsp::NonParamState::deserialize (serial_bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (atomic_int_val.get() == 102); REQUIRE (string_val.get() == "blah blah"); @@ -166,7 +166,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (float_val.get() == 100.0f); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial_bytes, state); + chowdsp::NonParamState::deserialize (serial_bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (float_val.get() == 90.0f); REQUIRE (string_val.get() == "blah blah"); @@ -197,7 +197,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (int_val.get() == 42); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial_bytes, state); + chowdsp::NonParamState::deserialize (serial_bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (string_val.get() == "blah blah"); } @@ -216,7 +216,8 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") const auto serial = std::array {}; - chowdsp::NonParamState::deserialize (serial, state); + chowdsp::ChainedArenaAllocator arena { 1024 }; + chowdsp::NonParamState::deserialize (serial, state, arena); REQUIRE (int_val.get() == 42); REQUIRE (atomic_int_val.get() == 99); REQUIRE (string_val.get() == "blah"); From fc0c577693aa567404a77bc48f1967d234096004 Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 1 Dec 2024 22:54:08 -0800 Subject: [PATCH 17/20] Fixes --- examples/StatefulPlugin/PluginEditor.cpp | 4 ++-- examples/StatefulPlugin/StatefulPlugin.h | 2 +- .../chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp | 2 +- .../chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp | 4 ++-- .../chowdsp_presets_v2_test/PresetManagerTest.cpp | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/StatefulPlugin/PluginEditor.cpp b/examples/StatefulPlugin/PluginEditor.cpp index 6138d88dc..835b6b38c 100644 --- a/examples/StatefulPlugin/PluginEditor.cpp +++ b/examples/StatefulPlugin/PluginEditor.cpp @@ -28,7 +28,7 @@ PluginEditor::PluginEditor (StatefulPlugin& plug) : juce::AudioProcessorEditor ( const auto setSizeFromState = [this] { const auto& stateSize = plugin.getState().nonParams.editorBounds.get(); - setSize (stateSize.x, stateSize.y); + setSize (stateSize.first, stateSize.second); }; setSizeFromState(); @@ -65,5 +65,5 @@ void PluginEditor::resized() undoButton.setBounds (bounds.removeFromLeft (80)); redoButton.setBounds (bounds.removeFromLeft (80)); - plugin.getState().nonParams.editorBounds = getLocalBounds().getBottomRight(); + plugin.getState().nonParams.editorBounds = std::make_pair (getLocalBounds().getRight(), getLocalBounds().getBottom()); } diff --git a/examples/StatefulPlugin/StatefulPlugin.h b/examples/StatefulPlugin/StatefulPlugin.h index ce91d092d..d60dec202 100644 --- a/examples/StatefulPlugin/StatefulPlugin.h +++ b/examples/StatefulPlugin/StatefulPlugin.h @@ -34,7 +34,7 @@ struct PluginNonParameterState : chowdsp::NonParamState addStateValues ({ &editorBounds }); } - chowdsp::StateValue> editorBounds { "editor_bounds", { 300, 500 } }; + chowdsp::StateValue> editorBounds { "editor_bounds", { 300, 500 } }; }; using State = chowdsp::PluginStateImpl; diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index 9557dd918..f238cc8d7 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -85,7 +85,7 @@ void NonParamState::deserialize (nonstd::span serial_data, NonP return nullptr; }; - while (data.size() > 0) + while (! data.empty()) { const auto value_name = deserialize_string (data); auto* value = get_value_ptr (value_name); diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp index 2782ae787..f37d9d845 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_ParamHolder.cpp @@ -280,7 +280,7 @@ inline void ParamHolder::getParameterPointers (ParamHolder& holder, ParamDeseria break; } - parameters.insert (ParamDeserial { .id = paramID, .ptr = thing, .found = false }); + parameters.insert (ParamDeserial { paramID, thing, false }); } } @@ -345,7 +345,7 @@ inline void ParamHolder::deserialize (nonstd::span& serial_data return {}; }; - while (data.size() > 0) + while (! data.empty()) { const auto param_id = deserialize_string (data); auto param_ptr = get_param_ptr (param_id); diff --git a/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp b/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp index 21db6f62c..ce81ae411 100644 --- a/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp +++ b/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp @@ -235,7 +235,7 @@ TEST_CASE ("Preset Manager Test", "[plugin][presets][state]") } ScopedPresetManager presetMgr {}; - presetMgr.state.deserialize (state); + presetMgr.state.deserialize (std::move (state)); REQUIRE_MESSAGE (juce::approximatelyEqual (presetMgr.getFloatParam(), otherValue), "Preset state is overriding parameter state!"); REQUIRE (presetMgr->getCurrentPreset() == nullptr); REQUIRE (presetMgr->getIsPresetDirty()); @@ -261,7 +261,7 @@ TEST_CASE ("Preset Manager Test", "[plugin][presets][state]") } ScopedPresetManager presetMgr {}; - presetMgr.state.deserialize (state); + presetMgr.state.deserialize (std::move (state)); REQUIRE_MESSAGE (juce::approximatelyEqual (presetMgr.getFloatParam(), otherValue), "Preset state is overriding parameter state!"); REQUIRE (*presetMgr->getCurrentPreset() == preset); REQUIRE (presetMgr->getIsPresetDirty()); From 946ebcd611a94534ff8c3c5424a92ae1786c77e7 Mon Sep 17 00:00:00 2001 From: jatin Date: Sun, 1 Dec 2024 23:43:52 -0800 Subject: [PATCH 18/20] More fixes --- .../Serialization/chowdsp_ByteSerializer.h | 2 + .../Backend/chowdsp_NonParamState.cpp | 8 ++-- .../Backend/chowdsp_NonParamState.h | 2 +- .../Backend/chowdsp_PluginStateImpl.cpp | 4 +- .../Backend/chowdsp_StateValue.h | 8 ++-- .../Backend/chowdsp_PresetState.cpp | 43 +++++++++++-------- .../Backend/chowdsp_PresetState.h | 12 +++--- .../NonParamTest.cpp | 15 ++++--- 8 files changed, 52 insertions(+), 42 deletions(-) diff --git a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h index 3695e294f..d6b3a742a 100644 --- a/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h +++ b/modules/common/chowdsp_serialization/Serialization/chowdsp_ByteSerializer.h @@ -98,9 +98,11 @@ static T deserialize_object (nonstd::span& bytes) const auto serial_bytes = get_bytes_for_deserialization (bytes); jassert (serial_bytes.size() == sizeof (T)); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wclass-memaccess") T object; std::memcpy (&object, serial_bytes.data(), serial_bytes.size()); return object; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } template diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp index f238cc8d7..9f19d6a9e 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.cpp @@ -43,7 +43,7 @@ void NonParamState::serialize (ChainedArenaAllocator& arena, const NonParamState serialize_direct (serialize_num_bytes, num_bytes); } -void NonParamState::deserialize (nonstd::span serial_data, NonParamState& state, ChainedArenaAllocator& arena) +void NonParamState::deserialize (nonstd::span& serial_data, NonParamState& state, ChainedArenaAllocator& arena) { auto num_bytes = deserialize_direct (serial_data); if (num_bytes == 0) @@ -111,7 +111,7 @@ json NonParamState::serialize_json (const NonParamState& state) { auto serial = nlohmann::json::object(); for (const auto& value : state.values) - serial[value->name] = value->serialize(); + serial[value->name] = value->serialize_json(); return serial; } @@ -130,7 +130,7 @@ void NonParamState::legacy_deserialize (const json& deserial, const NonParamStat { if (name == value->name) { - value->deserialize (valueDeserial); + value->deserialize_json (valueDeserial); namesThatHaveBeenDeserialized.push_back (name); } } @@ -158,7 +158,7 @@ void NonParamState::deserialize_json (const json& deserial, const NonParamState& { auto iter = deserial.find (value->name); if (iter != deserial.end()) - value->deserialize (*iter); + value->deserialize_json (*iter); else value->reset(); } diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h index dbdc865c5..52ff4138e 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_NonParamState.h @@ -39,7 +39,7 @@ class NonParamState static void serialize (ChainedArenaAllocator& arena, const NonParamState& state); /** Custom deserializer */ - static void deserialize (nonstd::span serial_data, NonParamState& state, ChainedArenaAllocator& arena); + static void deserialize (nonstd::span& serial_data, NonParamState& state, ChainedArenaAllocator& arena); /** Custom serializer */ static json serialize_json (const NonParamState& state); diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp index fb46cfc3d..ac70ee4e8 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_PluginStateImpl.cpp @@ -31,8 +31,8 @@ void PluginStateImpl::serialize (juce::Memory serialize_object (int {}, arena); #endif - ParamHolder::serialize (arena, params); NonParamState::serialize (arena, nonParams); + ParamHolder::serialize (arena, params); dump_serialized_bytes (data, arena, &frame); } @@ -53,8 +53,8 @@ void PluginStateImpl::deserialize (juce::Memo else { pluginStateVersion = Version::fromVersionHint (deserialize_object (serial_data)); - ParamHolder::deserialize (serial_data, params); NonParamState::deserialize (serial_data, nonParams, *params.arena); + ParamHolder::deserialize (serial_data, params); } params.applyVersionStreaming (pluginStateVersion); diff --git a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h index 55095df4c..cc858c8e5 100644 --- a/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h +++ b/modules/plugin/chowdsp_plugin_state/Backend/chowdsp_StateValue.h @@ -12,8 +12,8 @@ struct StateValueBase virtual void reset() {} - [[nodiscard]] virtual nlohmann::json serialize() const { return {}; } - virtual void deserialize (const nlohmann::json&) {} + [[nodiscard]] virtual nlohmann::json serialize_json() const { return {}; } + virtual void deserialize_json (const nlohmann::json&) {} [[nodiscard]] virtual size_t serialize (ChainedArenaAllocator&) const { return 0; } virtual void deserialize (nonstd::span&) {} @@ -74,13 +74,13 @@ struct StateValue : StateValueBase void reset() override { set (defaultValue); } /** JSON Serializer */ - [[nodiscard]] nlohmann::json serialize() const override + [[nodiscard]] nlohmann::json serialize_json() const override { return get(); } /** JSON Deserializer */ - void deserialize (const nlohmann::json& deserial) override + void deserialize_json (const nlohmann::json& deserial) override { set (deserial.get()); } diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp index 71b8768b2..d16324113 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.cpp @@ -37,17 +37,15 @@ void PresetState::reset() set ({}); } -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION -void PresetState::serialize (JSONSerializer::SerializedType& serial) const +nlohmann::json PresetState::serialize_json() const { - JSONSerializer::addChildElement (serial, name); - if (preset == nullptr) - JSONSerializer::addChildElement (serial, {}); - else - JSONSerializer::addChildElement (serial, preset->toJson()); + if (preset != nullptr) + return preset->toJson(); + + return {}; } -void PresetState::deserialize (JSONSerializer::DeserializedType deserial) +void PresetState::deserialize_json (const nlohmann::json& deserial) { if (deserial.is_null()) { @@ -57,26 +55,33 @@ void PresetState::deserialize (JSONSerializer::DeserializedType deserial) set (PresetPtr { deserial }); } -#else -nlohmann::json PresetState::serialize() const + +[[nodiscard]] size_t PresetState::serialize (ChainedArenaAllocator& arena) const { - if (preset != nullptr) - return preset->toJson(); + size_t num_bytes = 0; + if (preset == nullptr) + { + num_bytes += serialize_string ("", arena); + return num_bytes; + } - return {}; + num_bytes += serialize_string (preset->toJson().dump(), arena); + return num_bytes; } -void PresetState::deserialize (const nlohmann::json& deserial) +void PresetState::deserialize (nonstd::span& bytes) { - if (deserial.is_null()) + try { + const auto stateJson = json::parse (deserialize_string (bytes)); + set (PresetPtr { stateJson }); + } + catch (const std::exception& e) + { + juce::Logger::writeToLog (std::string { "Unable to load preset state: " } + e.what()); reset(); - return; } - - set (PresetPtr { deserial }); } -#endif bool operator== (const PresetState& presetState, std::nullptr_t) { diff --git a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h index 910c56909..06746ae18 100644 --- a/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h +++ b/modules/plugin/chowdsp_presets_v2/Backend/chowdsp_PresetState.h @@ -41,19 +41,17 @@ class PresetState : public StateValueBase /** Internal use only! */ void reset() override; -#if CHOWDSP_USE_LEGACY_STATE_SERIALIZATION /** Internal use only! */ - void serialize (JSONSerializer::SerializedType& serial) const override; + [[nodiscard]] nlohmann::json serialize_json() const override; /** Internal use only! */ - void deserialize (JSONSerializer::DeserializedType deserial) override; -#else + void deserialize_json (const nlohmann::json& deserial) override; + /** Internal use only! */ - [[nodiscard]] nlohmann::json serialize() const override; + [[nodiscard]] size_t serialize (ChainedArenaAllocator&) const override; /** Internal use only! */ - void deserialize (const nlohmann::json& deserial) override; -#endif + void deserialize (nonstd::span&) override; private: PresetPtr preset {}; diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp index 60bf7c173..d06cab6f0 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/NonParamTest.cpp @@ -80,6 +80,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); chowdsp::dump_serialized_bytes (serial_bytes, arena); + nonstd::span bytes { serial_bytes }; chowdsp::NonParamState state {}; state.addStateValues ({ &int_val, &atomic_int_val, &bool_vals, &string_val, &string_view_val, &juce_string_val, &json_val }); @@ -95,7 +96,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (juce_string_val.get() == "juce"); REQUIRE (json_val.get() == chowdsp::json { { "val1", 100 }, { "val2", "test" } }); - chowdsp::NonParamState::deserialize (serial_bytes, state, arena); + chowdsp::NonParamState::deserialize (bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (atomic_int_val.get() == 102); REQUIRE (! bool_vals.get()[0]); @@ -126,6 +127,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); chowdsp::dump_serialized_bytes (serial_bytes, arena); + nonstd::span bytes { serial_bytes }; chowdsp::NonParamState state {}; state.addStateValues ({ &string_val, &atomic_int_val, &int_val }); @@ -134,7 +136,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (atomic_int_val.get() == 99); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial_bytes, state, arena); + chowdsp::NonParamState::deserialize (bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (atomic_int_val.get() == 102); REQUIRE (string_val.get() == "blah blah"); @@ -156,6 +158,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); chowdsp::dump_serialized_bytes (serial_bytes, arena); + nonstd::span bytes { serial_bytes }; chowdsp::StateValue float_val { "float", 90.0f }; chowdsp::NonParamState state {}; @@ -166,7 +169,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (float_val.get() == 100.0f); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial_bytes, state, arena); + chowdsp::NonParamState::deserialize (bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (float_val.get() == 90.0f); REQUIRE (string_val.get() == "blah blah"); @@ -190,6 +193,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") std::vector serial_bytes (chowdsp::get_serial_num_bytes (arena)); chowdsp::dump_serialized_bytes (serial_bytes, arena); + nonstd::span bytes { serial_bytes }; chowdsp::NonParamState state {}; state.addStateValues ({ &string_val, &int_val }); @@ -197,7 +201,7 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") REQUIRE (int_val.get() == 42); REQUIRE (string_val.get() == "blah"); - chowdsp::NonParamState::deserialize (serial_bytes, state, arena); + chowdsp::NonParamState::deserialize (bytes, state, arena); REQUIRE (int_val.get() == 101); REQUIRE (string_val.get() == "blah blah"); } @@ -215,9 +219,10 @@ TEST_CASE ("Non-Param Test", "[plugin][state]") string_val = "blah blah"; const auto serial = std::array {}; + nonstd::span bytes { serial }; chowdsp::ChainedArenaAllocator arena { 1024 }; - chowdsp::NonParamState::deserialize (serial, state, arena); + chowdsp::NonParamState::deserialize (bytes, state, arena); REQUIRE (int_val.get() == 42); REQUIRE (atomic_int_val.get() == 99); REQUIRE (string_val.get() == "blah"); From 76a5633de880eb18120cfc92f3801ace51ae7384 Mon Sep 17 00:00:00 2001 From: jatin Date: Mon, 2 Dec 2024 00:27:48 -0800 Subject: [PATCH 19/20] Adding a couple of tests --- .../ParamHolderTest.cpp | 76 +++++++++++++++---- .../PresetManagerTest.cpp | 47 ++++++++++++ 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp index a872c6ed7..fb1e629b0 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp @@ -3,21 +3,20 @@ TEST_CASE ("ParamHolder Test", "[plugin][state]") { - SECTION ("add()") - { - chowdsp::ParamHolder params; - std::array floatParams { - chowdsp::PercentParameter::Ptr { "param3", "Param", 0.5f }, - chowdsp::PercentParameter::Ptr { "param4", "Param", 0.5f }, - }; - - chowdsp::BoolParameter::Ptr boolNested { "param1", "Param", false }; - chowdsp::ChoiceParameter::Ptr choiceNested { "param2", "Param", juce::StringArray { "One", "Two" }, 0 }; - chowdsp::ParamHolder nestedParams { ¶ms }; - nestedParams.add (boolNested, choiceNested); + std::array floatParams { + chowdsp::PercentParameter::Ptr { "param3", "Param", 0.5f }, + chowdsp::PercentParameter::Ptr { "param4", "Param", 0.5f }, + }; + chowdsp::BoolParameter::Ptr boolNested { "param1", "Param", false }; + chowdsp::ChoiceParameter::Ptr choiceNested { "param2", "Param", juce::StringArray { "One", "Two" }, 0 }; - params.add (nestedParams, floatParams); + chowdsp::ParamHolder params; + chowdsp::ParamHolder nestedParams { ¶ms }; + nestedParams.add (boolNested, choiceNested); + params.add (nestedParams, floatParams); + SECTION ("add()") + { auto allParamIDs = [¶ms] { juce::StringArray allIDs; @@ -31,4 +30,55 @@ TEST_CASE ("ParamHolder Test", "[plugin][state]") REQUIRE (allParamIDs.contains (floatParams[0]->paramID)); REQUIRE (allParamIDs.contains (floatParams[1]->paramID)); } + + SECTION ("Serialize JSON") + { + using namespace chowdsp::ParameterTypeHelpers; + setValue (0.0f, *floatParams[0]); + setValue (1.0f, *floatParams[1]); + setValue (true, *boolNested); + setValue (1, *choiceNested); + + const auto json_state = chowdsp::ParamHolder::serialize_json (params); + params.doForAllParameters ([] (auto& param, size_t) { setValue (getDefaultValue (param), param); }); + + REQUIRE (getValue (*floatParams[0]) == 0.5f); + REQUIRE (getValue (*floatParams[1]) == 0.5f); + REQUIRE (getValue (*boolNested) == false); + REQUIRE (getValue (*choiceNested) == 0); + + chowdsp::ParamHolder::deserialize_json (json_state, params); + REQUIRE (getValue (*floatParams[0]) == 0.0f); + REQUIRE (getValue (*floatParams[1]) == 1.0f); + REQUIRE (getValue (*boolNested) == true); + REQUIRE (getValue (*choiceNested) == 1); + } + + SECTION ("Serialize Bytes") + { + using namespace chowdsp::ParameterTypeHelpers; + setValue (0.0f, *floatParams[0]); + setValue (1.0f, *floatParams[1]); + setValue (true, *boolNested); + setValue (1, *choiceNested); + + chowdsp::ChainedArenaAllocator arena { 128 }; + chowdsp::ParamHolder::serialize (arena, params); + juce::MemoryBlock state {}; + chowdsp::dump_serialized_bytes (state, arena); + + params.doForAllParameters ([] (auto& param, size_t) { setValue (getDefaultValue (param), param); }); + + REQUIRE (getValue (*floatParams[0]) == 0.5f); + REQUIRE (getValue (*floatParams[1]) == 0.5f); + REQUIRE (getValue (*boolNested) == false); + REQUIRE (getValue (*choiceNested) == 0); + + nonstd::span state_data = { (const std::byte*) state.getData(), state.getSize() }; + chowdsp::ParamHolder::deserialize (state_data, params); + REQUIRE (getValue (*floatParams[0]) == 0.0f); + REQUIRE (getValue (*floatParams[1]) == 1.0f); + REQUIRE (getValue (*boolNested) == true); + REQUIRE (getValue (*choiceNested) == 1); + } } diff --git a/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp b/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp index ce81ae411..35114c4b6 100644 --- a/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp +++ b/tests/plugin_tests/chowdsp_presets_v2_test/PresetManagerTest.cpp @@ -241,6 +241,27 @@ TEST_CASE ("Preset Manager Test", "[plugin][presets][state]") REQUIRE (presetMgr->getIsPresetDirty()); } + SECTION ("Null State JSON Test") + { + static constexpr float otherValue = 0.15f; + + chowdsp::json json_state; + { + ScopedPresetManager presetMgr {}; + + presetMgr.setFloatParam (otherValue); + REQUIRE (juce::approximatelyEqual (presetMgr.getFloatParam(), otherValue)); + + json_state = decltype (presetMgr.state)::serialize (presetMgr.state); + } + + ScopedPresetManager presetMgr {}; + decltype (presetMgr.state)::deserialize (json_state, presetMgr.state); + REQUIRE_MESSAGE (juce::approximatelyEqual (presetMgr.getFloatParam(), otherValue), "Preset state is overriding parameter state!"); + REQUIRE (presetMgr->getCurrentPreset() == nullptr); + REQUIRE (presetMgr->getIsPresetDirty()); + } + SECTION ("Preset State Test") { static constexpr float testValue = 0.05f; @@ -267,6 +288,32 @@ TEST_CASE ("Preset Manager Test", "[plugin][presets][state]") REQUIRE (presetMgr->getIsPresetDirty()); } + SECTION ("Preset State JSON Test") + { + static constexpr float testValue = 0.05f; + static constexpr float otherValue = 0.15f; + auto preset = saveUserPreset ("test.preset", testValue); + + chowdsp::json json_state; + { + ScopedPresetManager presetMgr {}; + presetMgr->addPresets ({ chowdsp::presets::Preset { preset } }); + presetMgr->loadPreset (presetMgr->getPresetTree().getRootNode().first_child->value.leaf()); + REQUIRE (juce::approximatelyEqual (presetMgr.getFloatParam(), testValue)); + + presetMgr.setFloatParam (otherValue); + REQUIRE (juce::approximatelyEqual (presetMgr.getFloatParam(), otherValue)); + + json_state = decltype (presetMgr.state)::serialize (presetMgr.state); + } + + ScopedPresetManager presetMgr {}; + decltype (presetMgr.state)::deserialize (json_state, presetMgr.state); + REQUIRE_MESSAGE (juce::approximatelyEqual (presetMgr.getFloatParam(), otherValue), "Preset state is overriding parameter state!"); + REQUIRE (*presetMgr->getCurrentPreset() == preset); + REQUIRE (presetMgr->getIsPresetDirty()); + } + SECTION ("Preset Undo/Redo Test") { static constexpr float testValue = 0.05f; From c642319ab8168502c2648c9e9f0d174b152fb173 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 2 Dec 2024 08:29:27 +0000 Subject: [PATCH 20/20] Apply clang-format --- .../chowdsp_plugin_state_test/ParamHolderTest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp b/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp index fb1e629b0..7434bc972 100644 --- a/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp +++ b/tests/plugin_tests/chowdsp_plugin_state_test/ParamHolderTest.cpp @@ -40,7 +40,8 @@ TEST_CASE ("ParamHolder Test", "[plugin][state]") setValue (1, *choiceNested); const auto json_state = chowdsp::ParamHolder::serialize_json (params); - params.doForAllParameters ([] (auto& param, size_t) { setValue (getDefaultValue (param), param); }); + params.doForAllParameters ([] (auto& param, size_t) + { setValue (getDefaultValue (param), param); }); REQUIRE (getValue (*floatParams[0]) == 0.5f); REQUIRE (getValue (*floatParams[1]) == 0.5f); @@ -67,7 +68,8 @@ TEST_CASE ("ParamHolder Test", "[plugin][state]") juce::MemoryBlock state {}; chowdsp::dump_serialized_bytes (state, arena); - params.doForAllParameters ([] (auto& param, size_t) { setValue (getDefaultValue (param), param); }); + params.doForAllParameters ([] (auto& param, size_t) + { setValue (getDefaultValue (param), param); }); REQUIRE (getValue (*floatParams[0]) == 0.5f); REQUIRE (getValue (*floatParams[1]) == 0.5f);