commit 7e6e3cc84b60727599120a8756396caa2919d46e
parent e9f41ad9979d35d7f747ec25652e5ff25da8e853
Author: falkTX <falktx@falktx.com>
Date: Tue, 1 Feb 2022 12:32:04 +0000
Initial work for host-visible state
Signed-off-by: falkTX <falktx@falktx.com>
Diffstat:
6 files changed, 167 insertions(+), 88 deletions(-)
diff --git a/distrho/DistrhoPlugin.hpp b/distrho/DistrhoPlugin.hpp
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -136,6 +136,29 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean;
/** @} */
/* ------------------------------------------------------------------------------------------------------------
+ * State Hints */
+
+/**
+ @defgroup StateHints State Hints
+
+ Various state hints.
+ @see Plugin::getStateHints
+ @{
+ */
+
+/**
+ State is available for the host to see and change.
+ */
+static const uint32_t kStateIsHostVisible = 0x01;
+
+/**
+ State is a filename path.
+ */
+static const uint32_t kStateIsFilenamePath = 0x02 | kStateIsHostVisible;
+
+/** @} */
+
+/* ------------------------------------------------------------------------------------------------------------
* Base Plugin structs */
/**
@@ -993,13 +1016,17 @@ protected:
Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled.
*/
virtual void initState(uint32_t index, String& stateKey, String& defaultStateValue);
-#endif
-#if DISTRHO_PLUGIN_WANT_STATEFILES
/**
- TODO API under construction
+ TODO API under construction, should likely be put in a new State struct with key, name defValue and hints
+ Returns StateHints mask.
*/
- virtual bool isStateFile(uint32_t index) = 0;
+ virtual uint32_t getStateHints(uint32_t index);
+#endif
+
+#if DISTRHO_PLUGIN_WANT_STATEFILES
+ DISTRHO_DEPRECATED_BY("getStateHints")
+ virtual bool isStateFile(uint32_t index) { return false; }
#endif
/* --------------------------------------------------------------------------------------------------------
diff --git a/distrho/src/DistrhoPlugin.cpp b/distrho/src/DistrhoPlugin.cpp
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -200,6 +200,16 @@ String Plugin::getState(const char*) const { return String(); }
#endif
#if DISTRHO_PLUGIN_WANT_STATE
+uint32_t Plugin::getStateHints(const uint32_t index)
+{
+ #if DISTRHO_PLUGIN_WANT_STATEFILES
+ if isStateFile(index)
+ return kStateIsFilenamePath;
+ #endif
+
+ return 0x0;
+}
+
void Plugin::setState(const char*, const char*) {}
#endif
diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp
@@ -727,6 +727,13 @@ public:
return fData->stateCount;
}
+ uint32_t getStateHints(const uint32_t index) const noexcept
+ {
+ DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0);
+
+ return fPlugin->getStateHints(index);
+ }
+
const String& getStateKey(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);
@@ -741,15 +748,6 @@ public:
return fData->stateDefValues[index];
}
-# if DISTRHO_PLUGIN_WANT_STATEFILES
- bool isStateFile(const uint32_t index) const
- {
- DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, false);
-
- return fPlugin->isStateFile(index);
- }
-# endif
-
# if DISTRHO_PLUGIN_WANT_FULL_STATE
String getState(const char* key) const
{
diff --git a/distrho/src/DistrhoPluginLV2.cpp b/distrho/src/DistrhoPluginLV2.cpp
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -17,6 +17,7 @@
#include "DistrhoPluginInternal.hpp"
#include "lv2/atom.h"
+#include "lv2/atom-forge.h"
#include "lv2/atom-util.h"
#include "lv2/buf-size.h"
#include "lv2/data-access.h"
@@ -47,8 +48,8 @@
# define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
#endif
-#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES)
-#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
+#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
+#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
START_NAMESPACE_DISTRHO
@@ -126,29 +127,29 @@ public:
#endif
#if DISTRHO_PLUGIN_WANT_STATE
+ std::memset(&fAtomForge, 0, sizeof(fAtomForge));
+ lv2_atom_forge_init(&fAtomForge, uridMap);
+
if (const uint32_t count = fPlugin.getStateCount())
{
+ fUrids = new LV2_URID[count];
fNeededUiSends = new bool[count];
for (uint32_t i=0; i < count; ++i)
{
fNeededUiSends[i] = false;
- const String& dkey(fPlugin.getStateKey(i));
- fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
+ const String& statekey(fPlugin.getStateKey(i));
+ fStateMap[statekey] = fPlugin.getStateDefaultValue(i);
-# if DISTRHO_PLUGIN_WANT_STATEFILES
- if (fPlugin.isStateFile(i))
- {
- const String dpf_lv2_key(DISTRHO_PLUGIN_URI "#" + dkey);
- const LV2_URID urid = uridMap->map(uridMap->handle, dpf_lv2_key.buffer());
- fUridStateFileMap[urid] = dkey;
- }
-# endif
+ const String lv2key(DISTRHO_PLUGIN_URI "#" + statekey);
+ const LV2_URID urid = fUrids[i] = uridMap->map(uridMap->handle, lv2key.buffer());
+ fUridStateMap[urid] = statekey;
}
}
else
{
+ fUrids = nullptr;
fNeededUiSends = nullptr;
}
#else
@@ -544,13 +545,14 @@ public:
}
#endif
- // check for messages from UI or files
-#if DISTRHO_PLUGIN_WANT_STATE && (DISTRHO_PLUGIN_HAS_UI || DISTRHO_PLUGIN_WANT_STATEFILES)
+ // check for messages from UI or host
+#if DISTRHO_PLUGIN_WANT_STATE
LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event)
{
if (event == nullptr)
break;
+ #if DISTRHO_PLUGIN_HAS_UI
if (event->body.type == fURIDs.dpfKeyValue)
{
const void* const data = (const void*)(event + 1);
@@ -567,8 +569,9 @@ public:
fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body);
}
}
-# if DISTRHO_PLUGIN_WANT_STATEFILES
- else if (event->body.type == fURIDs.atomObject && fWorker != nullptr)
+ else
+ #endif
+ if (event->body.type == fURIDs.atomObject && fWorker != nullptr)
{
const LV2_Atom_Object* const object = (const LV2_Atom_Object*)&event->body;
@@ -576,13 +579,12 @@ public:
const LV2_Atom* value = nullptr;
lv2_atom_object_get(object, fURIDs.patchProperty, &property, fURIDs.patchValue, &value, nullptr);
- if (property != nullptr && property->type == fURIDs.atomURID &&
- value != nullptr && value->type == fURIDs.atomPath)
+ if (property != nullptr && property->type == fURIDs.atomURID && value != nullptr &&
+ (value->type == fURIDs.atomPath || value->type == fURIDs.atomString))
{
fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body);
}
}
-# endif
}
#endif
@@ -681,7 +683,7 @@ public:
updateParameterOutputsAndTriggers();
-#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI
+#if DISTRHO_PLUGIN_WANT_STATE
fEventsOutData.initIfNeeded(fURIDs.atomSequence);
LV2_Atom_Event* aev;
@@ -692,6 +694,16 @@ public:
if (! fNeededUiSends[i])
continue;
+ const uint32_t hints = fPlugin.getStateHints(i);
+
+ #if ! DISTRHO_PLUGIN_HAS_UI
+ if ((hints & kStateIsHostVisible) == 0x0)
+ {
+ fNeededUiSends[i] = false;
+ continue;
+ }
+ #endif
+
const String& curKey(fPlugin.getStateKey(i));
for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
@@ -703,8 +715,19 @@ public:
const String& value(cit->second);
- // set msg size (key + value + separator + 2x null terminator)
- const uint32_t msgSize = static_cast<uint32_t>(key.length()+value.length())+3U;
+ // set msg size
+ uint32_t msgSize;
+
+ if (hints & kStateIsHostVisible)
+ {
+ // object, prop key, prop urid, value key, value
+ msgSize = sizeof(LV2_Atom_Object) + sizeof(LV2_URID) * 3 + value.length() + 1;
+ }
+ else
+ {
+ // key + value + 2x null terminator + separator
+ msgSize = static_cast<uint32_t>(key.length()+value.length())+3U;
+ }
if (sizeof(LV2_Atom_Event) + msgSize > capacity - fEventsOutData.offset)
{
@@ -715,18 +738,38 @@ public:
// put data
aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fEventsOutData.port) + fEventsOutData.offset);
aev->time.frames = 0;
- aev->body.type = fURIDs.dpfKeyValue;
- aev->body.size = msgSize;
uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body);
- std::memset(msgBuf, 0, msgSize);
- // write key and value in atom buffer
- std::memcpy(msgBuf, key.buffer(), key.length()+1);
- std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1);
+ if (hints & kStateIsHostVisible)
+ {
+ LV2_Atom_Forge atomForge = fAtomForge;
+ lv2_atom_forge_set_buffer(&atomForge, msgBuf, msgSize);
- fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize));
+ LV2_Atom_Forge_Frame forgeFrame;
+ lv2_atom_forge_object(&atomForge, &forgeFrame, 0, fURIDs.patchSet);
+
+ lv2_atom_forge_key(&atomForge, fURIDs.patchProperty);
+ lv2_atom_forge_urid(&atomForge, fUrids[i]);
+
+ lv2_atom_forge_key(&atomForge, fURIDs.patchValue);
+ lv2_atom_forge_path(&atomForge, value.buffer(), static_cast<uint32_t>(value.length()+1));
+
+ lv2_atom_forge_pop(&atomForge, &forgeFrame);
+ }
+ else
+ {
+ aev->body.type = fURIDs.dpfKeyValue;
+ aev->body.size = msgSize;
+ std::memset(msgBuf, 0, msgSize);
+
+ // write key and value in atom buffer
+ std::memcpy(msgBuf, key.buffer(), key.length()+1);
+ std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1);
+ }
+
+ fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize));
fNeededUiSends[i] = false;
break;
}
@@ -854,7 +897,7 @@ public:
}
# endif
- String dpf_lv2_key;
+ String lv2key;
LV2_URID urid;
for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
@@ -870,24 +913,24 @@ public:
const String& value(cit->second);
-# if DISTRHO_PLUGIN_WANT_STATEFILES
- if (fPlugin.isStateFile(i))
+ if (const uint32_t hints = fPlugin.getStateHints(i))
{
- dpf_lv2_key = DISTRHO_PLUGIN_URI "#";
- urid = fURIDs.atomPath;
+ lv2key = DISTRHO_PLUGIN_URI "#";
+ urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath
+ ? fURIDs.atomPath
+ : fURIDs.atomString;
}
else
-# endif
{
- dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
+ lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
urid = fURIDs.atomString;
}
- dpf_lv2_key += key;
+ lv2key += key;
// some hosts need +1 for the null terminator, even though the type is string
store(handle,
- fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
+ fUridMap->map(fUridMap->handle, lv2key.buffer()),
value.buffer(),
value.length()+1,
urid,
@@ -903,33 +946,33 @@ public:
size_t size;
uint32_t type, flags;
- String dpf_lv2_key;
+ String lv2key;
LV2_URID urid;
for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
{
const String& key(fPlugin.getStateKey(i));
-# if DISTRHO_PLUGIN_WANT_STATEFILES
- if (fPlugin.isStateFile(i))
+ if (const uint32_t hints = fPlugin.getStateHints(i))
{
- dpf_lv2_key = DISTRHO_PLUGIN_URI "#";
- urid = fURIDs.atomPath;
+ lv2key = DISTRHO_PLUGIN_URI "#";
+ urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath
+ ? fURIDs.atomPath
+ : fURIDs.atomString;
}
else
-# endif
{
- dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
+ lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
urid = fURIDs.atomString;
}
- dpf_lv2_key += key;
+ lv2key += key;
size = 0;
type = 0;
flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
const void* data = retrieve(handle,
- fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
+ fUridMap->map(fUridMap->handle, lv2key.buffer()),
&size, &type, &flags);
if (data == nullptr || size == 0)
@@ -967,7 +1010,6 @@ public:
return LV2_WORKER_SUCCESS;
}
-# if DISTRHO_PLUGIN_WANT_STATEFILES
if (eventBody->type == fURIDs.atomObject)
{
const LV2_Atom_Object* const object = (const LV2_Atom_Object*)eventBody;
@@ -978,7 +1020,8 @@ public:
DISTRHO_SAFE_ASSERT_RETURN(property != nullptr, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, LV2_WORKER_ERR_UNKNOWN);
- DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath, LV2_WORKER_ERR_UNKNOWN);
+ DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath ||
+ value->type == fURIDs.atomString, LV2_WORKER_ERR_UNKNOWN);
const LV2_URID urid = ((const LV2_Atom_URID*)property)->body;
const char* const filename = (const char*)(value + 1);
@@ -986,8 +1029,8 @@ public:
String key;
try {
- key = fUridStateFileMap[urid];
- } DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateFileMap[urid]", LV2_WORKER_ERR_UNKNOWN);
+ key = fUridStateMap[urid];
+ } DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateMap[urid]", LV2_WORKER_ERR_UNKNOWN);
setState(key, filename);
@@ -1002,7 +1045,6 @@ public:
return LV2_WORKER_SUCCESS;
}
-# endif
return LV2_WORKER_ERR_UNKNOWN;
}
@@ -1136,6 +1178,7 @@ private:
LV2_URID atomURID;
LV2_URID dpfKeyValue;
LV2_URID midiEvent;
+ LV2_URID patchSet;
LV2_URID patchProperty;
LV2_URID patchValue;
LV2_URID timePosition;
@@ -1162,6 +1205,7 @@ private:
atomURID(map(LV2_ATOM__URID)),
dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
midiEvent(map(LV2_MIDI__MidiEvent)),
+ patchSet(map(LV2_PATCH__Set)),
patchProperty(map(LV2_PATCH__property)),
patchValue(map(LV2_PATCH__value)),
timePosition(map(LV2_TIME__Position)),
@@ -1188,7 +1232,10 @@ private:
const LV2_Worker_Schedule* const fWorker;
#if DISTRHO_PLUGIN_WANT_STATE
+ LV2_Atom_Forge fAtomForge;
StringToStringMap fStateMap;
+ UridToStringMap fUridStateMap;
+ LV2_URID* fUrids;
bool* fNeededUiSends;
void setState(const char* const key, const char* const newValue)
@@ -1213,10 +1260,6 @@ private:
d_stderr("Failed to find plugin state with key \"%s\"", key);
}
-
-# if DISTRHO_PLUGIN_WANT_STATEFILES
- UridToStringMap fUridStateFileMap;
-# endif
#endif
void updateParameterOutputsAndTriggers()
diff --git a/distrho/src/DistrhoPluginLV2export.cpp b/distrho/src/DistrhoPluginLV2export.cpp
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -79,8 +79,8 @@
# define DISTRHO_LV2_UI_TYPE "UI"
#endif
-#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES)
-#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
+#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
+#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
#define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled"
@@ -152,9 +152,7 @@ static const char* const lv2ManifestUiOptionalFeatures[] =
"ui:parent",
"ui:touch",
#endif
-#if DISTRHO_PLUGIN_WANT_STATEFILES
"ui:requestValue",
-#endif
nullptr
};
@@ -370,23 +368,28 @@ void lv2_generate_ttl(const char* const basename)
pluginString += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
pluginString += "\n";
-#if DISTRHO_PLUGIN_WANT_STATEFILES
// define writable states as lv2 parameters
- bool hasStateFiles = false;
+ bool hasHostVisibleState = false;
for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
{
- if (! plugin.isStateFile(i))
+ const uint32_t hints = plugin.getStateHints(i);
+
+ if ((hints & kStateIsHostVisible) == 0x0)
continue;
const String& key(plugin.getStateKey(i));
pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n";
pluginString += " a lv2:Parameter ;\n";
pluginString += " rdfs:label \"" + key + "\" ;\n";
- pluginString += " rdfs:range atom:Path .\n\n";
- hasStateFiles = true;
+
+ if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath)
+ pluginString += " rdfs:range atom:Path .\n\n";
+ else
+ pluginString += " rdfs:range atom:String .\n\n";
+
+ hasHostVisibleState = true;
}
-#endif
// plugin
pluginString += "<" DISTRHO_PLUGIN_URI ">\n";
@@ -404,12 +407,11 @@ void lv2_generate_ttl(const char* const basename)
addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4);
addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4);
-#if DISTRHO_PLUGIN_WANT_STATEFILES
- if (hasStateFiles)
+ if (hasHostVisibleState)
{
for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
{
- if (! plugin.isStateFile(i))
+ if ((plugin.getStateHints(i) & kStateIsHostVisible) == 0x0)
continue;
const String& key(plugin.getStateKey(i));
@@ -417,7 +419,6 @@ void lv2_generate_ttl(const char* const basename)
}
pluginString += "\n";
}
-#endif
// UI
#if DISTRHO_PLUGIN_HAS_UI
diff --git a/distrho/src/lv2/atom-forge.h b/distrho/src/lv2/atom-forge.h
@@ -125,7 +125,7 @@ lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size);
not held.
*/
static inline void
-lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map)
+lv2_atom_forge_init(LV2_Atom_Forge* forge, const LV2_URID_Map* map)
{
#if defined(__clang__)
# pragma clang diagnostic push